Skip to content

Difficulty adding error codes to SerializableActiveModelError #86

Open
@aprescott

Description

@aprescott

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions