14.1.1.1. Service Definition Specification

14.1.1.1.1. Overview

This document describes a schema for defining a REST API. This schema, also known as a service definition, is designed with the following goals in mind:

  • Web-browsable and searchable documentation across all products and versions
  • Product / service specific REST APIs
  • Uniform structure for all resource calls
  • Support for automated document generation
  • Allow definition of hyperlinks between services
  • Enable validation and simplified REST usage via language specific libraries

14.1.1.1.2. Background

REST APIs provide a popular and convenient menas of access to configuration and monitoring of servers and services, but they are only as good as they are usable by clients. First and foremost, good documentation is required and gives an important first impression that the APIs are well engineered and supported. Beyond good documentation, good supporting client libraries further enhance the programmer’s experience in interacting with an API.

14.1.1.1.2.1. Documentation

REST API documentation, like documenting SOAP or SNMP, is often a painful and laborious process. A fully featured API for a product may involve hundreds of calls that need to be properly documented, with more calls added with each release. For each call, the list of acceptable parameters must be documented, as well as the format of request and response objects.

In addition, documenting the individual calls is often insufficient, as accomplishing a task may requiring the caller to issue a series of calls. For example, running a report on Profiler involves connecting to the device, creating a report object with the appropriate criteria, polling for status until the report is done, then retrieving the results. Documenting such tasks will often be the first place users will look to get an understanding of how to interact with a device via REST APIs.

In products using v2.0 or later of our API definition schema, the documentation strings are embedded directly in the schema, or referenced from the schema in external files. The schema also includes type information and value constraints, all of which can be leveraged to produce documentation or validate data. Due to the standardized location of documentation within the schema, documentation is generated automatically rather than manually transferred into an external format.

14.1.1.1.2.2. Supporting Client Libraries

Working directly with REST APIs is a great improvement over technologies like SNMP and SOAP. It is possible to make REST calls directly with command line tools such as curl and wget, or in many cases simply with a web browser.

However, writing extensive programs by manually coding support for REST can quickly get out of hand. In particular, hand-crafting the URLs of REST resources is similar to embedding magic numbers in code. It’s doable on a small scale, but it is error prone and difficult to maintain as the application changes in size and the resource definitions change.

Many services that offer a REST API also provide client-side libraries that mirror the REST API. These libraries are often provided in multiple languages, allowing client developers choice. Use of the libraries enables developers to work with interfaces and objects that are more native to the language they are developing in, abstracting away the REST-ness of the API being programmed against.

However, development and maintenance of the client libraries themselves can become a burden on the server-side developers. An out-of-date or incomplete library is of no use to client developers.

As such, it is critical to enable the development of client-side libraries that need minimal or perhaps no changes at all as APIs change or new APIs become available. This is made possible by the schema supporting Hypertext As the Engine of Application State (HATEOAS) through link and relationship definitions.

14.1.1.1.3. Specification

This specification defines a JSON object that fully describes the REST resources associated with a single version of a service. This JSON object is referred to as the service definition.

The service definition is used to generate REST API documentation as well as enable the development of client-side libraries that can parse the schema and eliminate the hand-coding of resource URLs and links.

This schema will cover the following topics:

  • Full resource specification
  • Specification for a distinct API version
  • Tasks that involve a series REST calls
  • Examples, etc.

14.1.1.1.3.1. Documentation Conventions

This document uses RFC 2119-style terminology, with the following specifics:

MUST:
Indicates that implementations MUST return an error if the condition is not met.
SHOULD:
Means that implementations MUST allow deviations from the condition, but MAY issue a warning. Tools supporting best practices such as a linter MUST report non-conformance as a warning if non-conformance can be detected (some conditions relay on outside knowledge of the system such as whether combinations of fields create unique keys, and therefore cannot be detected programmatically).

In practice, MUST means that reschema requires it, and SHOULD means that reschema does not require it but relint (the linter for reschema) will complain about it if it possibly can.

14.1.1.1.3.2. JSON Schema

The service definition heavily relies on JSON Schema primer for describing data types. Similar to C struct, at a minimum a json-schema lays out all the elements and types of a JSON object.

This representation is used to describe both simple strings and numbers as well as complex data types in JSON. The schema may also include documentation for each attribute, as well as constraints on data values.

For example, the following simple json-schema defines a JSON object representing an address:

{
    "type": "object",
    "id": "address",
    "properties" : {
        "street" : { "type": "string", "description": "Street Address" },
        "city" : { "type": "string", "description": "City" },
        "state" : { "type": "string", "description": "State", "pattern": "[A-Z][A-Z]" },
        "zip" : { "type": "string", "description": "Zip Code (5-digit)", "pattern": "[0-9][0-9][0-9][0-9][0-9]" }
    }
}

A JSON object conforming to this schema:

{
    "street" : "123 High Street",
    "city" : "Springfield",
    "state": "IL",
    "zip": "12345"
}

The examples in this document are presented in YAML format rather than JSON, as YAML is easier to read and is close enough to JSON that there is no ambiguity in meaning. The YAML equivalent of the above examples is shown below:

Address schema in YAML:

type: object
properties:
   street: { type: string, description: "Street Address" }
   city: { type: string, description: "City" }
   state: { type: string, description: "State", pattern: "[A-Z][A-Z]" }
   zip: { type: string, description: "Zip Code (5-digit)", pattern: "[0-9][0-9][0-9][0-9][0-9]" }

Address object in YAML:

street: "123 High Street"
city: "Springfield"
state: "IL"
zip: "12345"

See JSON Schema primer for a brief introduction and pointers to the IETF drafts.

14.1.1.1.3.3. REST API Service Definition Object (service definition)

This section describes the service definition object in detail.

14.1.1.1.3.3.1. Service Definition Object

The service definition object has the following structure:

$schema: <string>,
id: <string>,
provider: <provider>
name: <string>,
version: <string>,
title: <string>,
description: <string>,
defaultAuthorization: <string>,
documentationLink: <string>,
types: { ... },
resources: { ... },
errors: [ ... ],
tasks: [ ... ],

The properties are defined as follows:

property type description notes
$schema string URI of the JSON Schema describing the format of this document This document describes http://support.riverbed.com/api/service_def/2.3
id string URI where the official copy of this document can be found Serves as the unique identifier for the service definition
provider string Identifier of the organization that authored this document Riverbed service definitions use ‘riverbed’, other organizations must use a globally unique identifier
name string Name of the service provided by this REST API Unique name within the namespace of the provider
version string Version string of the REST API described  
title string One line description  
description string More verbose description of the API  
defaultAuthorization string Default authorization for all resources and links if not specified. Valid values: required, optional, none
documentationLink string URL for online documentation for this API  
types object common data types used by resource requests and/or responses  
resources object resources schemas defined for the various REST resources supported  
errors array documentation of possible error codes when resource requests fail  
tasks array documentation of more complex tasks involving multiple resource calls  

The heart of the service definition object is in the resources section.

14.1.1.1.3.3.2. Service definition ‘id’

The ‘id’ property serves two purposes:

  1. URI where the official copy of the service definition document can be found
  2. Globally unique identifier for the service definition

As a unique identifier, the ‘id’ is used when defining links and relations to other services via the ‘$ref’ or ‘resource’ properties.

The Riverbed format for the ‘id’ is show below:

14.1.1.1.3.3.3. Service Path

The service path is the fully qualified path (URI) that is the base for all REST resources of the same hosted service.

As a simple example, the following is the service path for a ‘bookstore’ service hosted by a server 10.1.2.3:

https://10.1.2.3/api/bookstore/1.0

The service path is implementation specific and thus fully separate from the service definition. For this reason, the fully qualified service path is not specified as part of the definition and is instead replaced with the ‘$’ character when defining the templates for building URIs for the various resources that make up the service.

As will be described in more detail in later sections, each resource definition includes a ‘self’ link that defines the URI template for this resource. A ‘books’ resource in the ‘bookstore’ service might have the following self link:

resources:
   books:
      links:
         self: { path: '$/books' }

The ‘$’ in the path must be replaced at runtime based with the fully qualified service path.

Note that within a single network, a single service definition may be implemented multiple times as different services, each with a unique service path. The path itself has a {server} component and a {relpath}:

{server}{relpath}

https://10.1.2.3/api/bookstore/1.0

server := 'https://10.1.2.3'
relpath := '/api/bookstore/1.0'

For example:

multiple instances on the same server
The {relpath} component must be different for each instantiation of the service.
one instance per server, but multiple servers
Since the service definition is implemented on different servers, the {server} component of the service path will be unique. The {relpath} component therefore may (but need not) be the same across all servers.
multiple instances on multiple servers
A generalization of the above two cases.

The breakdown of the {relpath} component is {server} (ie. implementation) specific, meaning that two different servers may actually assign different paths for implementing the same service definition.

Note that the full generalization of this means that some servers may implement a single instance and others may implement multiple instances. In this generalization, a client that is attempting to access multiple services in the network, the client must be able to determine the service path for any service that it might connect to.

See the section on service path resolution below for further details.

14.1.1.1.3.3.4. Types

The types property is used to define common data types that are used by request or response objects. The types value is a JSON object where each property name defines a unique type.

types:
   <type-name> : <json-schema>
   <type-name> : <json-schema>

For example, the following defines two types, “address” and “phone”:

types:
   address:
      type: object

      properties:
         street:
            type: string
            description: "Street Address"
         city:
            type: string
            description: "City"
         state:
            type: string
            description: "State"
            pattern: "[A-Z][A-Z]"
         zip:
            type: string
            description: "Zip Code (5-digit)"
            pattern: "[0-9][0-9][0-9][0-9][0-9]"

   phone:
      type: string
      pattern: "[0-9]{3}-[0-9]{3}-[0-9]{4}"

A type defines only the structure of a data object, it does not have any associated behaviors. Defining types allows the same fundamental data types to be referenced by other types and resources, in this service definition as well as in others.

14.1.1.1.3.3.5. Resources

The resources section defines a schema for each REST resource exposed by the service. Note that a resource schema defined in the schema may map to multiple concrete resources supported by the server. This relationship is analogous to classes and objects. For example, the book resource schema defines the properties associated with a book resource, including the data type as well as the behaviors associated with a book. The server will have multiple actual instances, or books, all described by this resource schema.

Whereas a type defines only the structure of a data object, a resource binds a data object to a resource as supported by a server via the self link and allows the definition of related actions that can be performed via additional links, as well as relations to other resources.

A resource definition is an extension of a json-schema, adding links and relations. The following properties are used to describe a resource:

property description
type json-schema type: object, string, array, number
description text description of this resource
links one or more named links applicable to this resource
links.self required link providing the URI template for an instance of this resource
relations one or more named relations to other resources applicable to this resource

When in the context of a resource, additional nested schemas are defined when the type is object or an array. These nested schemas do not have a self link, however they may have other links and relations defined. This is described in more detail in later sections.

A simple resource schema for book is shown below:

resources:
   book:
      description: "Defines a book resource"

      type: object
      additionalProperties: False
      required: [ id, title ]
      properties:
         id:
            type: number
            readOnly: True
         title: { type: string }
         publisher_id: { type: number }
         author_ids:
            # Array of author ids, associated with this book
            type: array
            items: { type: number }

         chapters:
            type: array
            items:
               type: object
               additionalProperties: False
               properties:
                  num: { type: number }
                  heading: { type: string }

      links:
         self:
            # Base URI for an instance of a book
            path: "$/books/items/{id}"

         get:
            # Get the current data representation of a book
            # path defaults to "self" at /books/items{id}
            method: GET
            response: { $ref: '#/resources/book' }

         set:
            # Replace the data representation for a book
            # path defaults to "self" at /books/items/{id}
            method: PUT
            request: { $ref: '#/resources/book' }
            response: { $ref: '#/resources/book' }

         delete:
            # Link to delete a book instance
            # path defaults to "self"
            method: DELETE

         purchase:
            path: "$/books/items/{id}/purchase"
            method: POST
            request:
               type: object
               properties:
                  num_copies: { type: number }
                  shipping_address: { $ref: '#/types/address' }

            response:
               type: object
               properties:
                  delivery_date: { type: string }
                  final_cost: { type: number }

      relations:
         publisher:
            resource: '#/resources/publisher'
            vars: { id: '0/publisher_id' }
14.1.1.1.3.3.5.2. Relations

A relation defines another resource that is related to the given resource, usually based on the data representation of the given resource. A relation is essentially a pointer to another resource that may be followed to get to the other resource.

The primary use of defining relations is to explicitly define relations based on a given resource data representation, rather than implying relationships via documentation. For example, in the book resource, the publisher relation explicitly describes that the publisher_id property of a book resource can be used to reach the associated publisher resource:

resources:
   book:
      description: "Defines a book resource"

      type: object
      additionalProperties: False
      required: [ id, title ]
      properties:
         id: { type: number }
         title: { type: string }
         publisher_id: { type: number }
         ...

      relations:
         publisher:
            resource: '#/resources/publisher'
            vars: { id: '0/publisher_id' }

   publisher:
      type: object
      properties:
         id: { type: number }
         name: { type: string }

      links:
         self: { path: "$/publishers/{id}" }

As another example, the author resource defines the books relation that fills in the author parameter that is supported by the books self link:

resources:
   author:
      type: object
      properties:
         id: { type: number }
         name: { type: string }

      relations:
         # the entire list of authors
         instances:
            resource: '#/resources/authors'

         # the collection of books written by this author
         books:
            resource: '#/resources/books'
            vars: { author: "0/id" }

Each relation must define the resource property. This identifies the related resource by name. The instances relation links to the authors resource (note again the singular author vs the plural authors, two different resources).

The phrase “following a resource relation” is used to describe the process:

  1. Retrieve the data representation for a known “source” resource (say ‘/author/3’)
  2. Examine the schema for the “target” resource and parse the desired relation definition (say the ‘books’ relation)
  3. Examine the self link of the target resource and fill in the necessary parameters based on the data representation of the source resource using the relation vars mapping.
  4. Retrieve the data representation for the target resource using the fully resolved self link.
14.1.1.1.3.3.5.2.1. Syntax

A relation is defined as follows:

resources:
   <resource>:
      relations:
         <relation_name>:
            resource: <target_resource>
            vars:
               <target_var>: <rel_json_pointer>
               <target_var>: <rel_json_pointer>
               ...
relation_name Unique name within this set of relations
target_resource Reference to the target resource, see below
target_var Variable in the target resource’s self link
rel_json_pointer Relative JSON pointer to use to fill in the target_var based on a data representation of the source resource
14.1.1.1.3.3.5.2.2. resource references

For relations, the resource property is similar to link references via $ref. The primary difference is that the target of a resource property must point to another resource, it cannot be a type.

When following a resource relation, there are two types of resolution that must occur:

schema resolution
identifying the general service definition schema that describes the target resource
URI resolution
identifying the full URI of the particular target resource in the context of a data representation of the source resource

For example, returning to the author/relations/books example, the schema and path are resolved as follows:

source schema http://support.riverbed.com/apis/bookstore/1.0#/resources/author
source URI https://10.1.2.3/api/bookstore/1.0/author/12
source data rep { id: 12, name: "John Smith" }
target schema http://support.riverbed.com/apis/bookstore/1.0#/resources/books
target URI https://10.1.2.3/api/bookstore/1.0/books?author=12

The target URI above is based on the data representation of the source data representation and was built from the self link of the ‘books’ resource.

14.1.1.1.3.3.5.2.3. $ and URI resolution across services

In the simplest case, the ‘$’ in the self link of a target resource is replaced with the service URI of the source resource. However, it is possible to support links and relations from one service to another where some services are potentially on different servers.

TBD

14.1.1.1.3.3.5.2.4. full and instances relations

The full relation is a special relation that refers to the full resource associated with a partial representation of that resource. This is used most frequently with collections.

Likewise the instances relation is a special relation that refers to the full collection of resources of which the given resource is just one.

For example, consider the books and book resources:

 resources:
    books:
       type: array
       items:
          type: object
          properties:
             id: { type: number, readOnly: true }
             title: { type: string }

          relations:
             full:                       # "full" relation on collection
                resource: '#/resources/book'
                vars: { id: "0/id" }
       ...

    book:
       type: object
       ...
       relations:
          instances:                     # "instances" relation on element
             resource: '#/resources/books'

Notice that the full relation is a nested relation defined on the array item and leverages the item id property to link to the full book resource.

The full relation may be used in other contexts as well. For example, the link from a book to it’s publisher via the publisher_id could be represented as follows:

 resources:
    book:
       type: object
       properties:
          id: { type: number }
          publisher_id:
             type: number
             relations:
                full:                    # "full" relation on a property
                   resource: '#/resources/publisher'
                   vars: { id: "0" }
14.1.1.1.3.3.5.2.6. Example relation: publisher of a book

The following example shows how to define a relation from a book to the publisher resource for that book:

resources:
   book:
      description: A book object
      type: object
      properties:
         id: { type: number }
         title: { type: string }
         publisher_id: { type: number }
         ...

      relations:
         publisher:
            resource: '#/resources/publisher'
            vars:
               id: '0/publisher_id'

publisher:
   ...
   links:
      self: { path: "$/publishers/{id}" }

To start, the above defines a relation from a singular book resource to the singular publisher associated with that book based on the publisher_id property of a book.

relations.publisher.resource indicates that the related resource is called publisher, and the vars indicates that the ={id} variable in the self link path of the target resource should be filled in using the publisher_id of a data representation for a book.

14.1.1.1.3.3.5.3. Singletons versus instances

A resource schema may define either a single resource instance or multiple instances depending on the self relation. If the self path template does not contain any variables, than the resource is a singleton. If self includes one or more variables, than the resource is multi-instance, with each instance addressable by filling in all of the variable in the template.

For example:

  • “/info” describes a singleton
  • “/books” describes a singleton, even though the response data representation is a collection (of books), the resource itself is a singleton
  • “/books/items/{id}” describes a multi-instance resource indexed by the variable {id}
  • “/books/items/{id}/chapter/{num}” describes a multi-instance resource indexed by the tuple {id} and {num}.

Note that “/books/items/1” is a separate resource from “/books/items/1/chapter/2”, despite the fact that the former is a prefix of the latter. Each of these two resources is described by different resources (book and book_chapter, see the appendix).

14.1.1.1.3.3.5.5. Paths

The “path” property associated with a link definition defines the URI template for the link. The template may be a static path such as “$/info”, or it may be a path with variables such as “$/books/items/{id}” or “$/books/items/{bookid}/chapter/{num}”.

A simple static path with no variables:

resources:
   info:
      links:
         self: "$/info"

In order to evaluate a link, all variables in the link must be resolved to values. The values are taken first either from a data representation of the associated resource, or provided by the client when resolving the link.

When resolving variables from a data representation, the relative location of the link definition within the resource schema definition is taken into account. This is discussed further below.

Warning

The following text has never been implemented, and is probably not needed now that we have JSON relative pointers. Contact the RTC before using.

DEPRECATED?: “{$}” indicates that the value of the instance data (relative to the link definition). See the examples below.

The “path” property may take two forms, direct or indirect. In the direct form, the “path” property is a string defining a URI template conforming to RFC 6750. Simply put, this is a URI with zero or more variables in curly braces that must be replaced. Only variables at the same level as the link definition can be resolved in the direct form.

Shown below is a path with a single variable taken from instance data. The “{id}” variable is resolve by inspecting the data representation associated with a given instance of a “book” resource:

 resources:
    book:
       type: object
       properties:
          id: { type: number }               # Type information for 'id'

       links:
          self:
             path: "$/books/items/{id}"      # Use of 'id' in the path

In the indirect form, the path is an object with two properties as follows:

path:
   template: <string>
   vars:
      <var>: <relative JSON Pointer>
      <var>: <relative JSON Pointer>
      ...

In this second form, the URI template is defined in the “template” property. However, the variables are resolved first by looking the in “vars” object, then a resource data representation, then finally to other sources such as the user. (This form is necessary since the allowed characters for variables in a URI template is severely restricted and does not allow for the “/”, thus this layer of indirection is needed.)

The “vars” property defines variables using relative JSON pointers. This allows referencing instance data at other locations within a complex JSON data structure.

TODO: Example for the indirect form.

14.1.1.1.3.3.6. Errors

The mechanism for defining errors and the format of the responses closely follows the Problem Details IETF draft (draft-ietf-appsawg-http-problem).

14.1.1.1.3.3.6.1. Service Defined Errors

A service definition may specify a list of errors that the service may generate. The errors property occurs at the same level as resourcess.

Each error definition has the following properties:

property description
type

An absolute URI that identifies the error type. When dereferenced, it SHOULD provide human-readable documentation for the error type. Note that the type URI is not explicitly defined in the service defintion, it is implied based on the full service id.

This MUST be used as the primarly identifier for the error type. Clients SHOULD NOT automatically dereference this URL.

title A short, human readable summary of the problem type.
description A detailed description of the error type suitable for documenation. This text should describe in detail what causes the error to occur and identifies steps the client can take to fix the problem.
properites

A dictionary of additional properties that may be included in the error response. The value associated with each name is a schema that defines the structure of the property value in the response body.

There is one special detail-values property that SHOULD be used to JSON schema that describes the structure of the values property that may be included in the error response

An example of an error definition:

id: 'http://support.riverbed.com/apis/bookstore/1.0'

...

errors:
   invalid_username:
      title: "The specified username is invalid"
      description: >
         An attempt was made to access a resctricted resource
         using a username that is not valid.  This may be
         because there is no such account or because the
         account is disabled.  Check with an adminstrator
         for this device.
      properties:
         detail-values:
            type: object
            properties:
               username: { type: string }

The full type for the above error would be http://support.riverbed.com/apis/bookstore/1.0/service.html#/errors/invalid_username. Note that the type links to the HTML rendering of the service definition, with an anchor taking the user directly to the error defined within the service.

14.1.1.1.3.3.6.2. Error Responses

If an error occurs while processing a request from a client, a server will respond with an appropriate HTTP Status Code and the respond body will provide additional details.

The structure of the error is as follows:

property description
id URI of the JSON schema for this error response.
type Absolute URI that identifies the error type. This is the same as the type in the error definition.
title A short human readable summary of the problem type. This text MUST NOT change except for purposes of localization.
detail

A human readable explanation specific to this occurrence of the problem. The string SHOULD contain dynamic information providing context for this occurrence.

This SHOULD focus on helping the end user correct the problem, rather than providing debugging information.

Clients SHOULD NOT attempt to parse this string. Instead, extensions (see additionalProperties) should be provided.

detail-values

A dictionary of name/values pairs that identify the unique information for this specific occurrence of the problem.

In general, these values could be plugged into a format string to generate the detail above.

The schema for the values SHOULD be defined in the error definition under “properties”

additional properties Other custom properties that may be returned in the error response. The schema for these additional properties SHOULD be defined in the error definition under “properties”.

An example error response:

type: "http://support.riverbed.com/apis/bookstore/1.0/service.html#/errors/invalid_username"
title: "The specified username is invalid"
detail: "'jdoe' is not a valid username"
detail-values:
   username: "jdoe"
14.1.1.1.3.3.6.3. Additional Properties

As described above, the error response may contain additional properties that provide additional information beyond what is specified here. For example, a resource that is used to process web forms may include an addition “form_fields” property that allows the server to enumerate customer error messages for multiple form fields.

The error definition:

errors:
   invalid_form:
      title: "Form data is invalid."
      description: >
         A form was posted that does not satisfy one or more
         constraints.  This may be values out of range, or may
         be an invalid combination of field values.
      properties:
         detail-values:
            type: object
            properties:
               fields:
                  type: array
                  items: { type: string }

         form-fields:
            type: object
            description: >
               Dictionary of form fields that failed validation.
               The value for each field is a mesesage describing
               why that field failed validation.
            properties:
                message: { type: string}

An example error response:

type: "http://support.riverbed.com/apis/bookstore/1.0/service.html#/errors/invalid_form"
title: "Form data is invalid."
detail: "The following fields failed validation: SSN, email"
detail-values:
   fields: [ "SSN", "email" ]
form-fields:
   SSN: "Social security number must be provied"
   email: "The email address provided is not valid"

14.1.1.1.3.4. Versioning

A service definition object represents the complete REST API reference for a single version of a single service. The top level “version” property describes the version. Each time the REST API version number is changed, a new documentation object must be generated.

Two versions of the service definition for the same service (describing two versions of the REST API) may be compared programmatically to produce a change set, including:

  • added or removed resources / methods
  • changes to existing methods
  • changes to descriptions and links

14.1.1.1.3.5. Schema Best Practices

14.1.1.1.3.5.1. Always return an object

Always return an object. If the desired data type is number, string, or array, wrap this in an object. This enables the inclusion of meta data.

In the case of an array, the property of the object that is the array should be named “items”, as in:

resources:
    things:
        type: object
            properties:
                items: {$ref: '#/resources/thing'}

TODO:

  • Arrays and paging XXX

TODO - examples

14.1.1.1.3.5.3. Use plural for URI of collections

Stylistic consideration, use the plural form for collections:

  • books schema has a path of “/books”
  • book schema has a path of “/books/items/{id}”
  • authors schema has a path of “/authors”
  • author schema has a path of “/authors/{id}”

14.1.1.1.3.5.4. Collections

Most RESTful interfaces provide one or more resources representing collections of items. These collections can be recognized by one or more of the following traits:

  1. Using a custom tag (“tags: {collection: true}”)
  2. The response object has an “items” property which has an array type
  3. The response array items define an “instances” relation

This guideline provides recommendations for collection resources, to provide common functionality for manipulating elements and retrieving collection information.

14.1.1.1.3.5.4.1. Reading & Paging

To load the collection resource the client MUST send a GET request to the resource URI:

=> GET /api/bookstore/1.0/books

<= HTTP 200 OK

{
    "items": [
        { ... },
        { ... },
        ...
        { ... }
    ]
}

A collection resource MAY support additional parameters such as:

  1. limit={N} : maximum number of items in the “items” array
  2. offset={N} : tells the server to skip N items
  3. sortby={fields} : orders the items in the response according to the specified field/fields
  4. sort={directions} : sorting direction - ‘asc’ or ‘desc’ (if sorting on multiple fields, number of specifiers must match the number of fields)

The response schema MAY be extended with a “meta” property which is an object containing additional properties to aid the clients. For example:

  1. total: integer : total number of items on the server (regardless of the limit/offset values)
  2. count: integer : size of the “items” collection
  3. offset: integer : offset used in the request
  4. next_offset: integer and prev_offset: integer : offset of the next/previous page in a paged collection, which can be used in the next/prev links in the resource definition

Note

There is still considerable debate over the exact structure of the “meta” object.

If the resource does return the information specified for the “meta” property, its SHOULD use the “meta” property and the property names documented above. A resource MAY choose to support only some meta fields, or to not support a “meta” section at all.

Example:

resources:
   books:
      type: object
      additionalProperties: False
      required: [items]
      properties:
         items:
            type: array
            items:
               $ref: '#/resources/book'
         meta:
            type: object
            readOnly: True
            additionalProperties: False
            properties:
               offset: { type: integer }
               limit: { type: integer }
               total: { type: integer }
               count: { type: integer }
               next_offset: { type: integer }
               prev_offset: { type: integer }
      relations:
         next_page:
            resource: '#/resources/books'
            vars:
               offset: '0/meta/next_offset'
               limit:  '0/meta/limit'
         prev_page:
            resource: '#/resources/books'
            vars:
               offset: '0/meta/prev_offset'
               limit:  '0/meta/limit'
      links:
         self:
            path: "$/books"
            params:
               offset: { type: number }
               limit: { type: number }
=> GET /api/bookstore/1.0/books?limit=5&offset=10&sortby=title&sort=asc
<= HTTP 200 OK

{
    "items": [
        { ... },
        { ... },
        { ... },
        { ... },
        { ... }
    ],
    "meta": {
        "total": 1974,
        "count": 5,
        "offset": 10,
        "next_offset": 15,
        "prev_offset": 5
    }
}
14.1.1.1.3.5.4.2. Creating

To create an element in a collection the client SHOULD POST the creation data to the collection URI.

=> POST /api/bookstore/1.0/books
{
    "title": "YUI Cookbook",
    "isbn": "1449304192"
}

<= HTTP 201 Created
Location: /api/bookstore/1.0/books/items/1975

The server MUST reply with a 201 code and a Location header referring to the URI of the created resource. The server MAY also return a representation of the created resource.

Note

Currently Location headers are not supported by either Lumberjack or sleepwalker, and reschema has no way to enforce anything about them. In effect, it is not really behaving as a MUST condition at this time. Need bugs filed on this.

14.1.1.1.3.5.4.3. Deleting

To delete a collection element simply send a DELETE request on the resource URI:

=> DELETE /api/bookstore/1.0/books/items/1975
<= HTTP 204 No Content

=> GET /api/bookstore/1.0/books/items/1975
<= HTTP 404 Not Found
14.1.1.1.3.5.4.4. Updating

To update a collection element, the client SHOULD send a PUT request on an element URI with a body containing full resource representation. The server MAY reply with an updated resource representation (which may contain updated read-only fields). If no resource representation is desired due to concerns such as performance issues with large resources, the resource MUST return a 204.

=> PUT /api/bookstore/1.0/books/items/1975
{
    "title": "YUI3 Cookbook",
    "isbn": "1449304192"
}

<= HTTP 200 OK
{
    "title": "YUI3 Cookbook",
    "isbn": "1449304192"
}

14.1.1.1.4. Example: Bookstore

Consider a simple REST API that provides access to a bookstore’s inventory.

The service definition that describes this API: bookstore.yaml

This example demonstrates most of the capabilities of both the service definition schema as well as JSON Schema itself:

  • Defines schemas, resources, methods, errors, and tasks
  • Leverages schemas by “$ref”
  • Examples and tasks by reference to external files
  • Field constraints: limits, patterns, enumerations

14.1.1.1.5. Appendix: JSON Pointers

RFC 7901 defines JSON Pointer notation which allows indexing into an arbitrary JSON object via a string. The basic syntax looks very much like traversing a directory structure, using “/” as a delimiter.

Consider the following JSON data:

{
   "id": 1,
   "name": {
      "first": "John",
      "last": Doe
   },
   "age": 42,
   "children" : [
      { "first": "Susan",
        "age": 4 },
      { "first": "Bob",
        "age": 10 }
   ]
}

The following table shows a few JSON Pointers and the resulting data:

JSON Pointer Result
“” { 'id': 1 .... } (The entire data structure)
‘/id’ 1
‘/name’ { "first": "John", "last": "Doe" }
‘/name/first’ "John"
‘/children/0/first’ "Susan"
‘/children/1/age’ 10

14.1.1.1.6. Appendix Relative JSON Pointers

Relative JSON pointers IETF draft allows starting at some arbitrary point in JSON data and then indexing to some other point in that same data.

The basic syntax of a relative JSON Pointer is “<scope>/<json pointer>”, where scope defines the number of levels to proceed up the data structure from the current location, then follow the <json pointer> to retrieve the instance value.

Using the directory structure notation, this is like traversing up multiple directory levels before iterating back down.

Using the same data object as above, the following table illustrates the result of a relative JSON pointer based up a starting location:

Starting point Relative JSON Pointer Result
‘/name/first’ ‘1’ { 'first': 'John', 'last': 'Doe' }
‘/name/first’ ‘1/last’ 'Doe'
‘/name/first’ ‘2/name/last’ 'Doe'
‘/children/0’ ‘0/first’ 'Susan'
‘/children/0’ ‘1/1/first’ 'Bob'

14.1.1.1.7. References