Implement error object serializer
http://jsonapi.org/format/#error-objects http://jsonapi.org/examples/#error-objects
@pablocrivella even in those links jsonapi is very vague about what error objects needed to look like almost every attribute is a MAY have. I would appreciate ideas on what the developer interface should be for specifying error objects?
what about this for the interface:
class ErrorSerializer
include FastJsonapi::ErrorSerializer
set_id :id # defaults to :id, set to false to disable, can take a block
link(:about) {|object| "http://movies.com/errors/#{object.type}"}
status :status # defaults to :status, set to false to disable, can take a block
code :code # defaults to :code, set to false to disable, can take a block
title :title #defaults to :title, set to false to disable, can take a block
detail :detail # defaults to :detail, set to false to disable, can take a block
source_method :source # defaults to :source, set to false to disable, can take block, expected to be a hash
end
Which would produce:
{
"errors":[
{
"id": "123",
"status": "422",
"code": "Bad_Include",
"title": "You requested an include that isn't allowed",
"detail": "you requested that we include the \"tv_show\" association, however that isn't allowed",
"source":{
"pointer": "params/include",
"parameter": "include"
},
"links":{
"about": "http://movies.com/errors/bad_include"
}
}
]
}
Obviously the other option is to default to not showing and have a value of true be the auto value
@shishirmk I think the only thing needed from the gem is to be able to define root key as "errors" instead of "data", etc.
Errors themselves look just like a regular serializer
the reason I was suggesting a sperate serializer base, is that, as there is a specific list of allowed base attributes, and most attributes, are specified to be strings only, additional optimization should be possible
Here is a quick and dirty extension of ObjectSerializer that basically achieves what proposed above:
require 'fast_jsonapi'
module FastJsonapi::ErrorSerializer
extend ActiveSupport::Concern
included do
attr_accessor :with_root_key
include FastJsonapi::ObjectSerializer
set_id :title
def initialize(resource, options = {})
super
@with_root_key = options[:with_root_key] != false
end
def hash_for_one_record
serialized_hash = super[:data]&[:attributes]
return !with_root_key ? serialized_hash : {
errors: serialized_hash
}
end
def hash_for_collection
serialized_hash = super[:data]&.map{|err| err[:attributes]}
return !with_root_key ? serialized_hash : {
errors: serialized_hash
}
end
end
end
If you are using Rails, you can put this code into lib/fast_jsonapi/error_serializer.rb and require it from an initializer the usual way:
config.autoload_paths += Dir[Rails.root.join("lib/fast_jsonapi/*.rb")].each {|l| require l }
Given an error class (custom serialzier_source method omitted):
class RequestErrorSerializer
include FastJsonapi::ErrorSerializer
attributes :title,
:detail,
:code,
:status
attribute :source do |err|
err.serializer_source
end
end
It's a rather hacky solution (especially in that set_id :title) and I'd also love to see native support for this.
I would also like to see how a native support for this would look like. @coconup, thanks for the example but I'm still a novice and didn't catch what would be expected exactly in serializer_source, which you omitted. Would you please provide an example?
@malaquf I am passing instances of a custom error class to the serializer, which have a serializer_source attribute. It could also be a method if you need some extra logic. Something like this:
class CustomError
attr_accessor :code, :detail, :title, :status, :serializer_source
def initialize(source:, code:, detail: nil, title:, status: 400)
@serializer_source = source
@code = code
@status = status
...
end
end
When I want to serialize an error:
error = CustomError.new(source: 'my_source', title: 'an error occurred', code: 'my_error')
render json: RequestErrorSerializer.new(error).serialized_json, status: 422
Just wanted to mention what I already wrote in the #175, that these two issues should be handled together. Also, it would be nice to get an update from the team if there's any interest to provide rails-specific features like this one.
I come up with my own serializer
class ErrorSerializer
attr_reader :resource, :errors
alias_method :serializable_hash, :as_json
alias_method :serialized_json, :to_json
def initialize(resource, options = {})
@resource = resource
@errors = errors_for(resource).flatten
end
def id
resource.id.to_s
end
def type
resource.class.name.downcase
end
def serializable_hash(options = nil)
{}.tap do |hash|
hash[:id] = id if has_id?
hash[:type] = type if has_id?
hash[:errors] = errors
end
end
def serialized_json(options = nil)
serializable_hash.to_json(options)
end
private
def has_id?
@resource.respond_to?(:id)
end
def errors_for(resource)
resource.errors.messages.map do |field, errors|
build_hashes_for(field, errors)
end
end
def build_hashes_for(field, errors)
errors.map do |error_message|
build_hash_for(field, error_message)
end
end
def build_hash_for(field, error_message)
{}.tap do |hash|
hash[:source] = {pointer: "/data/attributes/#{field}"}
hash[:detail] = error_message
end
end
end
Then I override render method in a concern:
module ActsAsJsonApiController
extend ActiveSupport::Concern
included do
def render(options = {})
case options[:json]
when ActiveRecord::Base, ApplicaionForm
apply_item_options!(options)
when ActiveRecord::Relation
apply_collection_options!(options)
end
super(options)
end
def serializer
message = "#{self.class} must implement `serializer` method"
raise NotImplementedError, message
end
def apply_item_options!(options = {})
if options[:json].valid?
options[:json] = serializer.new(options[:json], options)
else
options[:include] = nil # if removed raises an exception
options[:status] = :bad_request
options[:json] = ErrorSerializer.new(options[:json], options).serializable_hash
end
end
def apply_collection_options!(options = {})
options[:meta] = {} unless options.key?(:meta)
options[:meta][:pagination] = build_pagination_metadata(options[:json])
options[:json] = serializer.new(options[:json], options)
options[:is_collection] = true
end
def build_pagination_metadata(collection)
{
total: collection.klass.count,
current_page: collection.current_page,
prev_page: collection.prev_page,
next_page: collection.next_page,
total_pages: collection.total_pages,
}
end
end
end
It seems Netflix has abandoned this project. The community created a new fork to continue supporting this project. Please refer
https://github.com/fast-jsonapi/fast_jsonapi
On Sat, 30 Nov, 2019, 12:55 PM ali-sennalabs, [email protected] wrote:
I come up with my own serializer
class ErrorSerializer attr_reader :resource, :errors alias_method :serializable_hash, :as_json alias_method :serialized_json, :to_json
def initialize(resource, options = {}) @resource = resource @errors = errors_for(resource).flatten end
def id resource.id.to_s end
def type resource.class.name.downcase end
def serializable_hash(options = nil) {}.tap do |hash| hash[:id] = id if has_id? hash[:type] = type if has_id? hash[:errors] = errors end end
def serialized_json(options = nil) serializable_hash.to_json(options) end
private
def has_id? @resource.respond_to?(:id) end
def errors_for(resource) resource.errors.messages.map do |field, errors| build_hashes_for(field, errors) end end
def build_hashes_for(field, errors) errors.map do |error_message| build_hash_for(field, error_message) end end
def build_hash_for(field, error_message) {}.tap do |hash| hash[:source] = {pointer: "/data/attributes/#{field}"} hash[:detail] = error_message end endend
Then I override render method in a concern:
module ActsAsJsonApiController extend ActiveSupport::Concern
included do def render(options = {}) case options[:json] when ActiveRecord::Base, ApplicaionForm apply_item_options!(options) when ActiveRecord::Relation apply_collection_options!(options) end
super(options) end def serializer message = "#{self.class} must implement `serializer` method" raise NotImplementedError, message end def apply_item_options!(options = {}) if options[:json].valid? options[:json] = serializer.new(options[:json], options) else options[:include] = nil # if removed raises an exception options[:status] = :bad_request options[:json] = ErrorSerializer.new(options[:json], options).serializable_hash end end def apply_collection_options!(options = {}) options[:meta] = {} unless options.key?(:meta) options[:meta][:pagination] = build_pagination_metadata(options[:json]) options[:json] = serializer.new(options[:json], options) options[:is_collection] = true end def build_pagination_metadata(collection) { total: collection.klass.count, current_page: collection.current_page, prev_page: collection.prev_page, next_page: collection.next_page, total_pages: collection.total_pages, } endendend
— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/Netflix/fast_jsonapi/issues/102?email_source=notifications&email_token=ACEAHZNMINBR3F6YBXZRXWTQWIIQLA5CNFSM4ESMAFE2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEFP3IFQ#issuecomment-559920150, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACEAHZNYKYOSFUVO5YSP3WLQWIIQLANCNFSM4ESMAFEQ .