Description
I work on an app where one of our API clients would like to customize an error message shown to the user. The server is using jsonapi-rails (version 0.3.1), with the default error serializer, SerializableActiveModelErrors
.
The Rails app in this case runs errors.add(:base, ...)
and the error value could be one of many types.
There are title
, detail
, and source
attributes on errors, but there's no natural way for the client to override a specific error message. The client could check title === "An error title value"
and customize that, but the server may change title
over time, since it's defined by the JSON API spec to be a human-readable summary.
What we'd like is to use code
, which JSON API supports:
An error object MAY have the following members:
[...]
code
: an application-specific error code, expressed as a string value.
This would let us detect, say, code === "foo"
, and even if title
changes the client would continue with its custom error message.
In our case, we plan on detecting errors.add(:base, :foo)
, seeing that :foo
is a symbol, and treating that as an error code. (If the Rails app ever used a string, as with errors.add(:base, "There was an error")
, that would not result in a code.)
This seems to be fairly tricky to accomplish.
Right now, the default error class of SerializableActiveModelErrors
is configurable, but SerializableActiveModelErrors
is private. If we were to ignore that it's private and, say, subclass SerializableActiveModelErrors
, it looks like we'd need to reimplement code related to the exposures object:
def initialize(exposures)
@errors = exposures[:object]
@reverse_mapping = exposures[:_jsonapi_pointers] || {}
freeze
end
Another issue is that SerializableActiveModelError
(singular), which is also private, doesn't have access to the original errors
object (or one of the single errors), and so it isn't possible to retrieve the :foo
corresponding to errors.add(:base, :foo)
.
As far as I can tell, we'd have to more or less implement our own error serialization to pull code
values out of ActiveModel errors.
Here's a very monkey-patch-y implementation of a serializer in the way I'm describing.
class CodedSerializableActiveModelErrors < ::JSONAPI::Rails::SerializableActiveModelErrors
def as_jsonapi
# Example: { email: [{ error: :blank }] }
@errors.details.flat_map do |key, attribute_errors|
attribute_errors.map do |error:|
# full_message(:email, generate_message(:email, :blank))
full_message = @errors.full_message(key, @errors.generate_message(key, error))
error_attributes = ::JSONAPI::Rails::SerializableActiveModelError.new(field: key, message: full_message, pointer: @reverse_mapping[key]).as_jsonapi
if error_attributes.key?(:code)
raise "Code already provided by the library. Refusing to override."
end
# Override the error_attributes this way so that we inherit all SerializableActiveModelError
# attributes, relationships, etc.
if error
error_attributes[:code] = error.to_s
end
error_attributes
end
end
end
end