raml-org / raml-spec

RAML Specification
http://raml.org
3.87k stars 859 forks source link

What is the logic behind the Traits order in the 'Algorithm of Merging Traits and Methods'? #632

Open rigolepe opened 7 years ago

rigolepe commented 7 years ago

I am struggling to find the logic behind the Trait application order in "Algorithm of Merging Traits and Methods":

Finally, the resource can have its own traits, and a chain of resource types, for example > resourceType1, resourceType2, ..., can be applied. Each resource type can potentially have its own traits and define the same method. The stack is constructed as follows:

  1. Traits of method itself
  2. Traits of resource owning the method
  3. Traits of method owned by resourceType1
  4. Traits of resourceType1
  5. ...

The logical order when applying resourceTypes and traits, in my opinion, would be to apply the resourceType first on the resource and thus merging all traits defined on the resourceType in the methods and the top-level resource traits according the the collection merge rules. Next would then be to apply all the method traits (some of them may have been collection-merged with traits defined in the resourceType) on their respective method and last apply all the resource traits (that may have been collection-merged in the first step) on all of its methods. The resulting order is then:

  1. Traits of method itself
  2. Traits of method owned by resourceType1 (collection-merging puts them behind 1.)
  3. Traits of resource owning the method
  4. Traits of resourceType1 owning the method (collection-merging pus them behind 3.)

The proposed order in the specs implies that you have to separate the traits defined on the resource owning the method from its resourceType so that they can be applied interleaved with the traits defined on the methods. This isn't a very logic approach and makes an algorithmic mess of things when trying to implement just that.

What am I not seeing?

Regards, Peter

KonstantinSviridov commented 7 years ago

Hi, @rigolepe !

It is natural for a user to browse through traits in the order of their closeness to the method definition. That is to say, the natural order of browsing the traits is the following:

  1. traits of method itself and traits of resource owning the method
  2. traits of method owned by resource type 1 and traits of resource type 1
  3. traits of method owned by resource type 2 traits of resource type 2 etc

We want our algorithm of trait application to respect this natural browsing order order. Thus, we take the following order of traits application:

  1. traits of method itself
  2. traits of resource owning the method
  3. traits of method owned by resource type 1
  4. traits of resource type 1
  5. traits of method owned by resource type 2
  6. traits of resource type 2
  7. etc

Making traits of resource type methods prior to traits of resource itself may confuse a user. For example, take a look at the project:

api.raml

#%RAML 1.0

title: test API

uses:
  resourceTypes: ./libs/resourceTypes.raml
  types: ./libs/types.raml

types:
  EntriesError:
    type: types.Error
    properties:
      message: string

traits:
  entriesError:
    responses:
      501:
        body:
          application/json: EntriesError

/entries:
  type: resourceTypes.collection
  is: [ entriesError ]
  get:
    queryParameters:
      filter: string

libs/resourceTypes.raml

#%RAML 1.0 Library

uses:
  types: ./types.raml
  traits: ./traits.raml  

resourceTypes:
  collection:
    type: read-only-collection
    post:
      body:
        application/json:
          type: types.<<resourcePathName | !singularize | !uppercamelcase>>Create  # e.g. Order
      responses:
        201:
          description: Created!
          headers:
            Location:
              description: uri of new resource
              type: string
              required: true

  read-only-collection:
    type: { document : { schema : types.<<resourcePathName | !uppercamelcase>> } }
    get:
      is: [ traits.pageable ]

  document:
    get:
      is: [ traits.errorResponse ]
      responses:
        200:
          body:
            application/json:
              type: <<schema>>

libs/types.raml

#%RAML 1.0 Library

types:
  Error:
    properties:
      code: number

  Entry:
    type: EntryCreate
    properties:
      id: number

  Entries:
    properties:
      body: Entry[]

  EntryCreate:
    properties:
      p1:
      p2:

libs/traits.raml

#%RAML 1.0 Library

uses:
  types: ./types.raml

traits:
  pageable:
    queryParameters:
      pageIndex?:
        type: integer
        default: 0
      pageSize?:
        type: integer
        default: 10

  errorResponse:
    responses:
      501:
        body:
          application/json: types.Error

Our algorithm results in "EntriesError" as GET:/entries 501 response type. If traits of resource type methods were prior to traits of resource itself, the type would be "Error", which is quite unnatural to see for a user.

Regards, Konstantin