Hi All!
Is there a built-in way to serialize errors as per the API spec? Or is that left up to the developer - via a custom ErrorSerializer?
TY!
hx
Hey @hhff someone might correct me, if wrong, but I'm almost 100% sure there is no built-in way right now :sweat_smile:
If you call:
render json: {error: 'Result is Invalid'}
It will only convert it to json and render it, without using any serializer.
We'd need to add a special error serializer or adapter, or both, since they are different from a normal response. In the short run, returning json that you craft yourself is the best thing to do.
It might be interesting to add an 'error' renderer to the controller, or a rescue_from.
I actually subclass'd Exception, and wrote the render logic into the as_json method, so now I just do:
render json: error.as_json
@bf4 I'm doing this right know. When I'm finished I'll post a gist with the approach.
@hhff Can you share some code? That sounds both interesting and concerning :)
Sure! I'm still working on it - but for now this is working nicely. It's not yet setup for actual model attribute errors, but it's a start.
# lib/api/exception.rb
module Api
class Exception < ::Exception
attr_accessor :details
attr_accessor :code
attr_accessor :status
attr_accessor :title
attr_accessor :detail
def initialize(code, details={})
@details = details
@code = code
@status = I18n.t "exceptions.#{code}.status"
@title = I18n.t "exceptions.#{code}.title", details
@detail = I18n.t "exceptions.#{code}.detail", details
@message = @detail
end
def as_json
{
title: title,
detail: detail,
code: code,
status: status
}
end
end
end
Concern included in API Base Controller
# app/controllers/api/concerns/error_handling.rb
module Api
module ErrorHandling
extend ActiveSupport::Concern
included do
rescue_from Exception do |error|
handle_exception error
end
end
protected
def handle_exception(error=nil)
case error
when Api::Exception
render json: error.as_json, status: error.status.to_sym
# example of wrapping vendor exceptions
when CanCan::AccessDenied
handle_exception Api::Exception.new('permissions.access_denied', {
action: error.action.to_s,
subject_id: error.subject.id.to_s,
subject_class_name: error.subject.class.to_s
})
else
# TODO: Handle this case better
render json: { message: error.message }, status: :unprocessable_entity
end
end
end
end
I18n for managing error copy, interpolations & HTTP status from a single file
# config/locales/en.yml
en:
exceptions:
auth:
email_is_invalid:
title: 'Authentication: Email Address is invalid'
detail: '%{invalid_email} is not a valid email address.'
status: 'bad_request'
permissions:
access_denied:
title: 'Permissions: Access Denied'
detail: 'The Current User does not have permission to %{action} a %{subject_class_name} with id: %{subject_id}.'
status: 'unauthorized'
Then for example, in a controller I just do:
raise Api::Exception.new("auth.email_is_invalid", { invalid_email: params[:email] }) unless EmailValidator.valid?(params[:email])
Rather than rescue_from Exception do |error| you might want to set Rails.configuration.exceptions_app = Api::ErrorHandling.new(and see https://github.com/rails/rails/blob/4-2-stable/actionpack/lib/action_dispatch/middleware/public_exceptions.rb and/or maybe def handle_error(exception) and def rescue_with_handler(exception) )
Hey ppl, awesome discussion going on here :smile:
Liked the solution @hhff
Make yourself comfortable to keep the discussion itself, I'm closing the issue for now.
@hhff the correct file path should be
# app/controllers/concerns/api/error_handling.rb
Error handling still be the gap of popular gems for all API steps?
Sure! I'm still working on it - but for now this is working nicely. It's not yet setup for actual model attribute errors, but it's a start.
# lib/api/exception.rb module Api class Exception < ::Exception attr_accessor :details attr_accessor :code attr_accessor :status attr_accessor :title attr_accessor :detail def initialize(code, details={}) @details = details @code = code @status = I18n.t "exceptions.#{code}.status" @title = I18n.t "exceptions.#{code}.title", details @detail = I18n.t "exceptions.#{code}.detail", details @message = @detail end def as_json { title: title, detail: detail, code: code, status: status } end end end
Some times you raise an exception from business logic layer where you don't know the status code because it is the responsibility of the controller layer. this solution won't work in that case. Or please correct me if I am wrong please
@asad-ali-bhatti three things
Exception is a super bad idea. Use StandardError
Most helpful comment
Sure! I'm still working on it - but for now this is working nicely. It's not yet setup for actual model attribute errors, but it's a start.
Concern included in API Base Controller
I18n for managing error copy, interpolations & HTTP status from a single file
Then for example, in a controller I just do: