diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bd18c86..dbd22757 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ Breaking changes: Features: +- [#1004](https://github.com/rails-api/active_model_serializers/pull/1004) JSON API errors object implementation. + - Only implements `detail` and `source` as derived from `ActiveModel::Error` + - Provides checklist of remaining questions and remaining parts of the spec. - [#1515](https://github.com/rails-api/active_model_serializers/pull/1515) Adds support for symbols to the `ActiveModel::Serializer.type` method. (@groyoh) - [#1504](https://github.com/rails-api/active_model_serializers/pull/1504) Adds the changes missing from #1454 diff --git a/docs/jsonapi/errors.md b/docs/jsonapi/errors.md index 74f3b166..1d15dde0 100644 --- a/docs/jsonapi/errors.md +++ b/docs/jsonapi/errors.md @@ -1,6 +1,6 @@ [Back to Guides](../README.md) -# JSON API Errors +# [JSON API Errors](http://jsonapi.org/format/#errors) Rendering error documents requires specifying the error serializer(s): diff --git a/docs/jsonapi/schema.md b/docs/jsonapi/schema.md index 72b14948..ba718ec0 100644 --- a/docs/jsonapi/schema.md +++ b/docs/jsonapi/schema.md @@ -58,33 +58,33 @@ Example supported requests |-----------------------|----------------------------------------------------------------------------------------------------|----------|---------------------------------------| | schema | oneOf (success, failure, info) | | | success | data, included, meta, links, jsonapi | | AM::SerializableResource -| success.meta | meta | | AM::S::Adapter::Base#meta -| success.included | UniqueArray(resource) | | AM::S::Adapter::JsonApi#serializable_hash_for_collection +| success.meta | meta | | AMS::Adapter::Base#meta +| success.included | UniqueArray(resource) | | AMS::Adapter::JsonApi#serializable_hash_for_collection | success.data | data | | -| success.links | allOf (links, pagination) | | AM::S::Adapter::JsonApi#links_for +| success.links | allOf (links, pagination) | | AMS::Adapter::JsonApi#links_for | success.jsonapi | jsonapi | | -| failure | errors, meta, jsonapi | errors | -| failure.errors | UniqueArray(error) | | #1004 -| meta | Object | | -| data | oneOf (resource, UniqueArray(resource)) | | AM::S::Adapter::JsonApi#serializable_hash_for_collection,#serializable_hash_for_single_resource +| failure | errors, meta, jsonapi | errors | AMS::Adapter::JsonApi#failure_document, #1004 +| failure.errors | UniqueArray(error) | | AM::S::ErrorSerializer, #1004 +| meta | Object | | +| data | oneOf (resource, UniqueArray(resource)) | | AMS::Adapter::JsonApi#serializable_hash_for_collection,#serializable_hash_for_single_resource | resource | String(type), String(id),
attributes, relationships,
links, meta | type, id | AM::S::Adapter::JsonApi#primary_data_for | links | Uri(self), Link(related) | | #1028, #1246, #1282 | link | oneOf (linkString, linkObject) | | | link.linkString | Uri | | | link.linkObject | Uri(href), meta | href | -| attributes | patternProperties(
`"^(?!relationships$|links$)\\w[-\\w_]*$"`),
any valid JSON | | AM::Serializer#attributes, AM::S::Adapter::JsonApi#resource_object_for -| relationships | patternProperties(
`"^\\w[-\\w_]*$"`);
links, relationships.data, meta | | AM::S::Adapter::JsonApi#relationships_for -| relationships.data | oneOf (relationshipToOne, relationshipToMany) | | AM::S::Adapter::JsonApi#resource_identifier_for +| attributes | patternProperties(
`"^(?!relationships$|links$)\\w[-\\w_]*$"`),
any valid JSON | | AM::Serializer#attributes, AMS::Adapter::JsonApi#resource_object_for +| relationships | patternProperties(
`"^\\w[-\\w_]*$"`);
links, relationships.data, meta | | AMS::Adapter::JsonApi#relationships_for +| relationships.data | oneOf (relationshipToOne, relationshipToMany) | | AMS::Adapter::JsonApi#resource_identifier_for | relationshipToOne | anyOf(empty, linkage) | | | relationshipToMany | UniqueArray(linkage) | | | empty | null | | -| linkage | String(type), String(id), meta | type, id | AM::S::Adapter::JsonApi#primary_data_for -| pagination | pageObject(first), pageObject(last),
pageObject(prev), pageObject(next) | | AM::S::Adapter::JsonApi::PaginationLinks#serializable_hash +| linkage | String(type), String(id), meta | type, id | AMS::Adapter::JsonApi#primary_data_for +| pagination | pageObject(first), pageObject(last),
pageObject(prev), pageObject(next) | | AMS::Adapter::JsonApi::PaginationLinks#serializable_hash | pagination.pageObject | oneOf(Uri, null) | | -| jsonapi | String(version), meta | | AM::S::Adapter::JsonApi::ApiObjects::JsonApi -| error | String(id), links, String(status),
String(code), String(title),
String(detail), error.source, meta | | -| error.source | String(pointer), String(parameter) | | -| pointer | [JSON Pointer RFC6901](https://tools.ietf.org/html/rfc6901) | | +| jsonapi | String(version), meta | | AMS::Adapter::JsonApi::ApiObjects::JsonApi#as_json +| error | String(id), links, String(status),
String(code), String(title),
String(detail), error.source, meta | | AM::S::ErrorSerializer, AMS::Adapter::JsonApi::Error.resource_errors +| error.source | String(pointer), String(parameter) | | AMS::Adapter::JsonApi::Error.error_source +| pointer | [JSON Pointer RFC6901](https://tools.ietf.org/html/rfc6901) | | AMS::JsonPointer The [http://jsonapi.org/schema](schema/schema.json) makes a nice roadmap. @@ -102,7 +102,7 @@ The [http://jsonapi.org/schema](schema/schema.json) makes a nice roadmap. ### Failure Document - [ ] failure - - [ ] errors: array of unique items of type ` "$ref": "#/definitions/error"` + - [x] errors: array of unique items of type ` "$ref": "#/definitions/error"` - [ ] meta: `"$ref": "#/definitions/meta"` - [ ] jsonapi: `"$ref": "#/definitions/jsonapi"` @@ -137,4 +137,15 @@ The [http://jsonapi.org/schema](schema/schema.json) makes a nice roadmap. - [ ] pagination - [ ] jsonapi - [ ] meta - - [ ] error: id, links, status, code, title: detail: source [{pointer, type}, {parameter: {description, type}], meta + - [ ] error + - [ ] id: a unique identifier for this particular occurrence of the problem. + - [ ] links: a links object containing the following members: + - [ ] about: a link that leads to further details about this particular occurrence of the problem. + - [ ] status: the HTTP status code applicable to this problem, expressed as a string value. + - [ ] code: an application-specific error code, expressed as a string value. + - [ ] title: a short, human-readable summary of the problem that SHOULD NOT change from occurrence to occurrence of the problem, except for purposes of localization. + - [x] detail: a human-readable explanation specific to this occurrence of the problem. + - [x] source: an object containing references to the source of the error, optionally including any of the following members: + - [x] pointer: a JSON Pointer [RFC6901](https://tools.ietf.org/html/rfc6901) to the associated entity in the request document [e.g. "/data" for a primary data object, or "/data/attributes/title" for a specific attribute]. + - [x] parameter: a string indicating which query parameter caused the error. + - [ ] meta: a meta object containing non-standard meta-information about the error. diff --git a/lib/active_model/serializer/adapter/json_api/api_objects/relationship.rb b/lib/active_model/serializer/adapter/json_api/api_objects/relationship.rb index dfaabc39..154b71fd 100644 --- a/lib/active_model/serializer/adapter/json_api/api_objects/relationship.rb +++ b/lib/active_model/serializer/adapter/json_api/api_objects/relationship.rb @@ -4,6 +4,10 @@ module ActiveModel class JsonApi module ApiObjects class Relationship + # {http://jsonapi.org/format/#document-resource-object-related-resource-links Document Resource Object Related Resource Links} + # {http://jsonapi.org/format/#document-links Document Links} + # {http://jsonapi.org/format/#document-resource-object-linkage Document Resource Relationship Linkage} + # {http://jsonapi.org/format/#document-meta Docment Meta} def initialize(parent_serializer, serializer, options = {}, links = {}, meta = nil) @object = parent_serializer.object @scope = parent_serializer.scope diff --git a/lib/active_model/serializer/adapter/json_api/api_objects/resource_identifier.rb b/lib/active_model/serializer/adapter/json_api/api_objects/resource_identifier.rb index 058f0603..0336e0b5 100644 --- a/lib/active_model/serializer/adapter/json_api/api_objects/resource_identifier.rb +++ b/lib/active_model/serializer/adapter/json_api/api_objects/resource_identifier.rb @@ -4,6 +4,7 @@ module ActiveModel class JsonApi module ApiObjects class ResourceIdentifier + # {http://jsonapi.org/format/#document-resource-identifier-objects Resource Identifier Objects} def initialize(serializer) @id = id_for(serializer) @type = type_for(serializer) diff --git a/lib/active_model_serializers/adapter/json_api.rb b/lib/active_model_serializers/adapter/json_api.rb index 4f82835f..0407ef09 100644 --- a/lib/active_model_serializers/adapter/json_api.rb +++ b/lib/active_model_serializers/adapter/json_api.rb @@ -13,6 +13,7 @@ module ActiveModelSerializers # TODO: if we like this abstraction and other API objects to it, # then extract to its own file and require it. module ApiObjects + # {http://jsonapi.org/format/#document-jsonapi-object Jsonapi Object} module Jsonapi ActiveModelSerializers.config.jsonapi_version = '1.0' ActiveModelSerializers.config.jsonapi_toplevel_meta = {} @@ -51,6 +52,8 @@ module ActiveModelSerializers @fieldset = options[:fieldset] || ActiveModel::Serializer::Fieldset.new(options.delete(:fields)) end + # {http://jsonapi.org/format/#crud Requests are transactional, i.e. success or failure} + # {http://jsonapi.org/format/#document-top-level data and errors MUST NOT coexist in the same document.} def serializable_hash(options = nil) options ||= {} if serializer.success? @@ -60,6 +63,7 @@ module ActiveModelSerializers end end + # {http://jsonapi.org/format/#document-top-level Primary data} def success_document(options) is_collection = serializer.respond_to?(:each) serializers = is_collection ? serializer : [serializer] @@ -84,6 +88,7 @@ module ActiveModelSerializers hash end + # {http://jsonapi.org/format/#errors JSON API Errors} # TODO: look into caching # rubocop:disable Style/AsciiComments # definition: @@ -117,6 +122,7 @@ module ActiveModelSerializers private + # {http://jsonapi.org/format/#document-resource-objects Primary data} def resource_objects_for(serializers) @primary = [] @included = [] @@ -158,10 +164,12 @@ module ActiveModelSerializers process_relationships(serializer, include_tree) end + # {http://jsonapi.org/format/#document-resource-object-attributes Document Resource Object Attributes} def attributes_for(serializer, fields) serializer.attributes(fields).except(:id) end + # {http://jsonapi.org/format/#document-resource-objects Document Resource Objects} def resource_object_for(serializer) resource_object = cache_check(serializer) do resource_object = ActiveModel::Serializer::Adapter::JsonApi::ApiObjects::ResourceIdentifier.new(serializer).as_json @@ -185,6 +193,7 @@ module ActiveModelSerializers resource_object end + # {http://jsonapi.org/format/#document-resource-object-relationships Document Resource Object Relationship} def relationships_for(serializer, requested_associations) include_tree = ActiveModel::Serializer::IncludeTree.from_include_args(requested_associations) serializer.associations(include_tree).each_with_object({}) do |association, hash| @@ -198,16 +207,19 @@ module ActiveModelSerializers end end + # {http://jsonapi.org/format/#document-links Document Links} def links_for(serializer) serializer._links.each_with_object({}) do |(name, value), hash| hash[name] = Link.new(serializer, value).as_json end end + # {http://jsonapi.org/format/#fetching-pagination Pagination Links} def pagination_links_for(serializer, options) JsonApi::PaginationLinks.new(serializer.object, options[:serialization_context]).serializable_hash(options) end + # {http://jsonapi.org/format/#document-meta Docment Meta} def meta_for(serializer) ActiveModel::Serializer::Adapter::JsonApi::Meta.new(serializer).as_json end