Skip to content
hoegertn edited this page Apr 19, 2012 · 11 revisions

Strawman Example #1

This is intended as a "kitchen-sink" example expressing most of the features mentioned in the original GitHub issue discussion. I've used nested JSON schemas to describe parameters and response data. Also, I'm using URI Templates to describe URLs.

The API being described

For this example I describe a simple API for setting and retrieving localized messages. This is a likely a silly example, but hopefully it's understandable.

---------------------- --------- ------------------------ --------------
Resource               Methods   Representation           Status Codes
---------------------- --------- ------------------------ --------------
/{locale}/{messageId}  GET, PUT  message format (string)  200, 201, 302
/fallback/{locale}     GET, PUT  language format (string) 200, 201
---------------------- --------- ------------------------ --------------

To update or create a new localized message you PUT a string value to it's /{locale}/{messageId} URI. To assign a fallback locale for missing messages, you PUT a locale name to /fallback/{locale}.

JSON Description

{
  resources: [
    {
      id: "LocalizedMessage",
      doc: "A localized message",
      path: "/{language}/{messageId}", // URI template
      methods: {
        GET: {
          doc: "Retrieve a message",
          statusCodes: [ 200, 302 ]
        },
        PUT: {
          doc: "Update or create a message",
          statusCodes: [ 201 ],
          body: { type: "string" },
          examples: [
            {
              path: "/en_US/greeting",
              body: "Hello, world!"
            },
          ]
        }
      },
      parameters: {
        locale: {
          description: "A standard locale string, e.g. \"en_US.utf-8\"",
          type: "string",
          pattern: "[a-z]+(_[A-Z]+)?(\\.[a-z-]+)?"
        },
        messageId: {
          description: "A free-form message string",
          type: "string",
          pattern: "[a-z_]+"
        }
      },
      representations: ['application/json', 'text/plain'],
      schema: { type: "string" },
    },
    {
      // This resource has no human-readable documentation, but still provides some info on how to use it.
      id: "FallbackLocale",
      methods: {
        GET: { statusCodes: [ 200 ] },
        PUT: { statusCodes: [ 201 ] }
      },
      parameters: {
        locale: {
          type: "string",
          pattern: "[a-z]+(_[A-Z]+)?(\\.[a-z-]+)?"
        }
      }
    }
  ]
}

JSON Description Alternative 1 (separated items per method)

The main advantage here is the ease of use. It is easy to build this list from a typical sinatra like router. You just push each method into the collection without worrying about consolidating the methods into a coherent mashed view. I like the unified view but it is a bit less workable implementation wise. How does the code which generates this JSON looks like?

[grncdr] Rather than pushing all methods to a list, you would assign them into nested hashes, where the keys are the route patterns, then the HTTP method. So in ruby for example, assuming there's some sort of object containing all documentation in it's @doc_data member:

def document_method(route_pattern, method, documentation)
  route_data = @doc_data[route_pattern] ||= {}
  route_data[method] = documentation
end

To me this seems like a fairly straight-forward pattern for any language. Additionally, a consolidated view of all the HTTP methods supported by a given resource is necessary for the OPTIONS /resource functionality).

Thinking about the tools there is strength in the fact that each item stands on it own

[grncdr] Would you expand a bit what these strengths are? I feel like I'm misunderstanding the motivation here.

[saary] At first we had it as you proposed we later switched to per method implementation. Writing tools which processed the API were a bit simpler and more generic when each method contained all necessary info for processing. without 'knowing' their parent. Nevertheless, it is not important for the specification and could easily overcome with dedicated code. I am not married to this setting in anyway. If you guys see more value in the tree like structure let's continue with it.

[hoegertn] Both ideas are right from a certain POV. I like the idea of having one description per resource and verb for better tooling integration (server and client) but from a REST point of view I think it is more logical to have a resource description and nested definitions what you can DO with this resource (HTTP verbs)

[hoegertn] After some more thoughts about it, I think we want to be technology agnostic and the RestDoc should consider REST and not some possible limitations in implementation languages. So now I prefer the tree like structure with resources and nested verbs. (Mainly I found a solution to my anticipated Java implementation design problem)

[
    {
      id: "LocalizedMessage",
      doc: "Retrieve a message",
      path: "/{locale}/{messageId}", // URI template
      method: "GET",
      statusCodes: [ 200, 302 ], // can also be found under response
      params: {
        locale: {
          doc: "A standard locale string, e.g. \"en_US.utf-8\"",
          style: "template",
          type: "string",
          validations: "[a-z]+(_[A-Z]+)?(\\.[a-z-]+)?" // this can be a string (regexp) or an array of specific rules
        },
        messageId: {
          doc: "A free-form message string",
          style: "template",
          type: "string",
          validations: "[a-z_]+"
        }
      }
    },
    {
      id: "LocalizedMessage",
      doc: "Update or create a message",
      path: "/{locale}/{messageId}", // URI template
      method: "POST",
      statusCodes: [ 201 ],
      params: {
        locale: {
          // see: "LocalizedMessage#GET" // an internal documentation link which means that this parameter as the same attributes as in the GET method
          doc: "A standard locale string, e.g. \"en_US.utf-8\"",
          style: "template",
          type: "string",
          validations: "[a-z]+(_[A-Z]+)?(\\.[a-z-]+)?" // this can be a string (regexp) or an array of specific rules
        },
        messageId: {
          doc: "A free-form message string",
          style: "template",
          type: "string",
          validations: "[a-z_]+"
        }
      },
      request: {
        representations: [ 
          { type: "application/text" },
          { 
            type: "application/json",
            schema: "http://json.schema.url.com",
            // we can have a construct resembling params
            // which will define the json fields in case the format is trivial
            body: {
              message: {
                type: "string",
                required: true
              }
            }
        ],
        examples: [
            {
              path: "/en_US/greeting",
              body: "Hello, world!"
            },
            {
              path: "/en_US/greeting",
              body: { "message": "Hello, world!" }
            }
          ]
        }
      }
]

_[grncdr]One question and two comments:

What are the potentially valid values for the "style" element of a parameter description? I don't think I was clear that "parameters" in my example referred solely to URI parameters.

+1 for having the "representation" array contain objects rather than string media types.

As a possible argument against making HTTP methods top level objects, you've duplicated the "LocalizedMessage" id here. To disambiguate the id's a client has to inspect the path and/or method for each item, or the id's need to be made unique. One could change them to e.g. "GetLocalizedMessage" and "SetLocalizedMessage", but at that point you're duplicating information that the HTTP method already expresses more clearly._

Questions/Ideas:

  • I like the noun/verb delineation of resources and methods, but perhaps it's too verbose?
  • What do others think of URI templates? The specification defines "levels", where level 1 is simple string substitution + URL encoding, and levels 2 and 3 add more features (such as expressing query parameters and hash-fragment URL portions). I feel that requiring at least level 1 templates is not too onerous a burden for implementations.

[saary] I agree with the level 1 templates, the only alternative I know of is the sinatra style paths (/resource/:resourceId)

[grncdr] On this point I think the idea I put forward on issue #3 decouples the template format from the rest of the specification quite nicely. OTOH, it's easier for implementations to require the "one true format", and level 1 URI templates are easily created from Sinatra routes.

  • What do others think of JSON schema? There's a fair bit of tooling built for it already and it would allow a client to validate (JSON) request bodies before sending them, as well as responses. The trade-off is that it's JSON-only, so some sort of tool for translating schemas to other serialization formats might be desirable.

[saary] I think JSON schema is a good option and we can allow an optional schema link for requests and responses.

[hoegertn] I like the idea of using an existing standard

  • There's a lot of schemas defined in-line here. For a larger API where certain parameters are likely to be repeated, it would be nice to provide hyperlinks instead.

[saary] not sure about this one, I definitely about the request/response schema but other than that there is a lot of benefit of having the metadata at one location without multiple links. Remember that the metadata will be returned gzipped so it will cost that much even with a lot of duplications.

_[hoegertn] may be this could be addressed by the level of details idea described in the OPTIONS page. The big picture might use hyperlinks while the individual resource docs provide the schema inline

  • Stating that a URI parameter is a string is redundant._

[saary] agreed this should be optional in this case

  • What things MUST vs. SHOULD be provided?
  • Hyperlinks to representation definitions in addition to known mime-types?

[saary] sounds good, we can add a field to the representation element

[saary] One of the things that we discovered while using docRouter was that it is beneficial to support the addition of user defined fields. By that I mean we do not validate the metadata it self. If a developer chose to add an additional custom 'authentication' item we just bubble it through as is. After running a bit with docRouter we actually removed stuff from our "spec" as we simply never used them.

[hoegertn] full ack. The spec should be extensible for future use cases

[grncdr] This seems pragmatic, though we may want to define the "reserved" fields to help developers avoid breaking client tooling.

[saary] Another point of view here is to look at this from the code perspective. Think about documenting as you as you are writing the service. In this sense you get a real live documentation of you API as the code actually helps generate the API. When coding it is hard to create consolidated documentation, the natural thing to do is to document inline and in some cases link (urls/hashtags in our case) to previously documented sections.

[hoegertn] I agree on this, for the Java world there might be an additional annotation for JAX-RS to provide RestDoc info directly in the code and the JAX-RS impl generates the RestDoc metadata

[grncdr] Agreed, this is actually how how Lazorse works. For a real-world example of an API using it and it's documentation, take a look at http://grid.betsmartmedia.com/ . All of the resource documentation is defined in the code as part of the API endpoints and formed into a ReStructured Text document with a separate tool, the JSON index itself can be retrieved by specifying an "accept: application/json" header.

Clone this wiki locally