# TopLevelObject # ======== # # A JSON object **MUST** be at the root of every JSON API request and response # containing data. This object defines a document's "top level". module JSONAPIonify::Structure module Objects class TopLevel < Base attr_reader :origin default(:jsonapi) { Jsonapi.new version: '1.0' } # A document **MUST** contain at least one of: must_contain_one_of!( # **data:** The document's "primary data" :data, # **errors:** An array of errors :errors, # **meta:** a meta object that contains non-standard meta-information. :meta ) collects :errors, as: Collections::Errors implements :meta, as: Meta # The members `data` and `errors` **MUST NOT** coexist in the same document. must_not_coexist! :data, :errors # A document **MAY** contain any of these top-level members: may_contain!( # **links:** a links_object related to the primary data. :links, # **included:** an array of resource objects that are related to the primary. :included, # **jsonapi:** an object describing the server's implementation. :jsonapi ) implements :links, as: Maps::TopLevelLinks collects :included, as: Collections::IncludedResources implements :jsonapi, as: Jsonapi is_resource = ->(obj) { (obj.keys - %i{id type}).present? } collects_or_implements( :data, collects: Collections::Resources, implements: Resource, if: ->(obj) { obj.is_a?(Resource) || (obj.is_a?(Hash) && is_resource[obj]) }) collects_or_implements( :data, collects: Collections::ResourceIdentifiers, implements: ResourceIdentifier, if: ->(obj) { obj.is_a?(ResourceIdentifier) || (obj.is_a?(Hash) && !is_resource[obj]) }) # If a document does not contain a top-level `data` key, the `included` member # **MUST NOT** be present either. may_not_exist! :included, without: :data # The document's "primary data" is a representation of the resource or collection # of resources targeted by a request. # Primary data **MUST** be either: type_of! :data, must_be: [ # A single **ResourceObject** Resource, # A single **ResourceIdentifierObject** ResourceIdentifier, # Null NilClass, # A collection of **ResourceObjects** Collections::Resources, # A collection of **[ResourceIdentifierObjects](resource_identifier_object.html)** Collections::ResourceIdentifiers ] def compile(*) compiled = super(validate: ENV['RACK_ENV'] != 'production') compiled_errors = compiled['errors'] || [] all_errors = compiled_errors | errors.as_collection.compile if all_errors.present? self.class.new( errors: all_errors, meta: { invalid_object: to_hash } ).compile else compiled end end def as(origin) copy.tap do |obj| obj.instance_variable_set :@origin, origin end end after_initialize do @origin = :server end end end end