New type: reference


#1

I see a widespread pattern where resources hold references to other resources in the form of URLs to API endpoints. It would be handy if I could specify those references in a less loosely way then typesetting string or string[]. So here is a draft to illustrate what I’m thinking about.

For example, given that User type is defined, I could write something like

type: reference<User>
refers: /users/{id}

refers would only be validated in RAML API root definitions document (#%RAML 1.0).
refers could also indicate that the reference format is a full URL and not just the path segment following baseUri with the following syntax:

type: reference<User>
refers: {baseUri}/users/{id}

Multiple references to a user:

type: reference[]<User>
refers: /users/{id}

One reference to multiple users:

type: reference<User[]>
refers: /users?group={groupId}

PS: Perhaps I’m also advocating for generic types! I don’t know. It could be a lightweight syntax for maps also. Something like type: map<string,integer>. It might add complexity to the parser I’m not really aware of, since nesting should be allowed, such as map<string,reference<User>>


#2

I just found out that Open API Specification 3.0 already supports a similar feature in the form of Link Object.
But in OAS, the type is inferred from the target path instead of explicitly set.

The main challenge I see is reusability : you would need the target path declared in the type…
So it would require to decouple type declaration and path-dependency. One option I see is to declare a references field in method declaration like so:

/route:
  get:
    responses:
      200:
        type: SomeTypeUsingReference
        references: 
          'SomeTypeUsingReference.users.?': /users/{id}

#3

Interesting. Although I am not sure this is not something you can already achieve with RAML 1.0.

RAML decouples the API & data layers from the implementation layer. In other words, you can declare references that are explicitly defined in your responses. E.g.

types:
  SomeTypeWithReference:
    properties:
      foo: string
      bar: integer
      _links: 
        items: 
          enum: ['/users/{id}']

/route:
  get:
    responses:
      200:
        body: SomeTypeWithReference
        example: |
          {
            "foo": "Some text",
            "bar": 1,
            "_links": ["/users/{id}"]
          }

but for implicit references “hiding” underneath your implementation, RAML has an annotation mechanism for that. E.g.

types:
  SomeTypeWithReference:
    properties:
      foo: string
      bar: 
        type: integer
        (reference):
          'SomeTypeUsingReference.users.?': /users/{id}

/route:
  get:
    responses:
      200:
        body: SomeTypeWithReference
        example: |
          {
            "foo": "Some text",
            "bar": 1
          }

#4

@jstoiko Thanks for highlighting those options!

However, I bet that if I use a test tool to assert my implementation comply with the spec, I’ll have an issue with '/users/{id}'

As per the annotation mechanism, it cannot unfortunately add semantics to the documentation unless I write a plugins to handle it.

With more experience in RAML, I believe the following syntax which mimics array + items would be more “RAML style”:

Type definition: one reference to one User

ReferenceToUser:
  type: reference
  target: User

Type definition: one reference to multiple Users

ReferenceToUsers:
  type: reference
  target: User[]

Type definition: multiple references to one User

ReferencesToUser:
  type: array
  items:
    type: reference
    target: User

Route definition using ReferenceToUser

references field is optional but enforces more strict model constrains

/route:
  get:
    responses:
      200:
        type: SomeTypeWithReferenceToUser
        references: 
          user:  /users/{id}

Route definition using ReferenceToUsers

/route:
  get:
    responses:
      200:
        type: SomeTypeWithReferenceToUsers
        references: 
          users:  /users?group={groupId}

Route definition using ReferencesToUser

/route:
  get:
    responses:
      200:
        type: SomeTypeWithReferencesToUser
        references: 
          users:
            items: /users/{id}

#5

What I find interresting with this new definition is that reference type and path are 100% decoupled.
If you just want to specify the resource paths, you can (just set type as an array of references to unknown target). If you just want to specify the referred types, you also can (just don’t add references field in response definition).

The only issue I see now it that of content types. I have no guarantee that one route exactly matches one type. And I personally use vendor prefixed content types to offer different representations of the same resource. For example, application/vnd.simple+json for a lightweight representation and application/vnd.plain+json for a full representation.