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:
- URI where the official copy of the service definition document can be found
- 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:
http://support.riverbed.com/apis/<name>/<version>
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.1. Links¶
A link defines the set of actions and other resources that are related
to an instance of a resource schema (a concrete resource). For
example, the book
resource defines the get
link which describes how
a client retrieves a copy of the data representation for a particular
book instance.
A link may have the follow properties:
property | description |
---|---|
path | URI template for the address of an instance of this resource |
description | Description of the purpose of this link |
method | Applicable HTTP method used to exercise this link |
request | json-schema for parameters associated with a GET, or the body of a POST |
response | json-schema for the response from a server for this link |
There are several possible combinations of the above properties that have slightly different meanings.
If the “path” is omitted, the “path” associated with the “self” link is used.
The link path
may include one or more variables in the base URL:
- /books/items/{id} - one variable {id}
- /books/items/{bookid}/chapter/{num} - two variable {bookid} and {num}
All variables must be resolved to values in order to exercise the link. Variables are first resolved from the data associated with a resource.
For example, the book
resource is defined as follows (abbreviated):
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 }
author_ids: ...
chapters: ...
links:
self:
description: "Base URI for an instance of a book"
path: "$/books/items/{id}"
get:
description: "Get the current data representation of a book"
method: GET
response: { $ref: '#/resources/book' }
set:
description: "Update a book from a data representation"
method: PUT
request: { $ref: '#/resources/book' }
response: { $ref: '#/resources/book' }
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 }
The links
section above defines four links:
self
- defines the path for accessing a book resource. Note that the path defines a single variable {id} which refers to the property in the book resource definition
resources.book.properties.id
.get
- retrieve the data representation for a book using the GET method.
set
- update a book from a data representation using the PUT method. An updated data representation is returned.
purchase
- initiate a book purchase using the POST method at the URL “$/books/items/{id}/purchase”. Since this link is defined on a
book
resource, the {id} in the URL is filled based upon a specific book data representation.
14.1.1.1.3.3.5.1.1. The $ref
syntax¶
Links frequently use $ref
to refer to resources and types defined
elsewhere in this or other service definitions. The syntax of a fully
qualified reference is simply the id
of the service definition (a
URL) followed by a fragment that is a JSON pointer to some JSON
schema within the service definition at that id
.
There are three supported formats of the reference, which mirrors standard URL page resolution:
Local | #{jsonpointer} |
Same service definition |
Provider | /{name}/{version}#{jsonpointer} |
Same provider, different service |
Full | {id}#{jsonpointer} |
Fully qualified reference |
14.1.1.1.3.3.5.1.2. The self
link¶
path | params | method | request | response |
---|---|---|---|---|
required | optional | n/a | n/a | n/a |
The self
link documents the address of a resource described by this
resource schema. It must be defined for every resource schema and includes
the path
property which defines a URI template.
If the URI template includes variables, the variables should match properties
in the resource’s data representation. For example, in the book self link
below, the {id}
variable matches the book’s id
property.
resources:
info:
links:
# No variables in this resource
self: { path: "$/info" }
book:
type: object:
properties:
id: { type: number }
...
links:
# {id} matches the resource property defined at book.properties.id
self: { path: "$/books/items/{id}" }
book_chapter:
type: object:
properties:
bookid: { type: number }
num: { type: number }
...
links:
# {bookid} matches the resource property book_chapter.properties.bookid
# {num} matches the resource property book_chapter.properties.num
self: { path: "$/books/items/{bookid}/chapter/{num}" }
Self links are only allowed at the root of a resource schema.
The params
property of the link defines additional qualifiers when
addressing a resource. These qualifiers are typically filter parameters
that select a subset of a collection, or restricited set of fields.
For example, the books
resource is shown below (books
is the plural
collection not to be confused with the singular book
resource):
resources:
books:
type: array
items:
type: object
properties:
id: { type: number, readOnly: true }
title: { type: string }
links:
self:
path: "$/books"
params:
# Allow filtering by author id or title
author: { type: number }
title: { type: string }
Parameters are expressed as URI parameters.
Retrieve only books written by author id 1:
GET /books?author=1
Retrieve only books written by author id 1 and have “Bunnies” in the title:
GET /books?author=1&title=Bunnies
Note that the interpretation of the parameters is dependent
on the server implementation. The meaning of the title
parameter
could be an exact match or a substring match. The schema should
document such details that are relevant to client developers.
Note also that /books
is the collection, not /books/items
, so
the parameters apply to /books
. The /books/items
path only
appears when a specific element of the collection’s URI is constructed.
14.1.1.1.3.3.5.1.3. Standard links: get, set, create, delete¶
path | method | request | response |
---|---|---|---|
n/a | required | optional | optional |
The standard resource operations of get
, set
, create
, and
delete
are all defined as links with an HTTP method
and path
equal to the path of the self
link. The path
property is not
provided as these operations are always relative to the resource in
question, thus are available at the self link path.
resources:
info:
links:
get:
method: GET
response: { $ref: '#/resources/info' }
set:
method: PUT
request: { $ref: '#/resources/info' }
response: { $ref: '#/resources/info' }
The request
schema is used to pass data to the server. For a GET
, the
request
must be a flat object whose properties define the valid URL parameters.
For a POST
, the request
may be any valid json-schema
and is provided
in the body of the message.
The response
schema defines the expected response on success. Errors
are handled separately (see the section on Errors).
14.1.1.1.3.3.5.1.4. Verbs¶
path | method | request | response |
---|---|---|---|
required | required | optional | optional |
This type of link is commonly used for verbs like “reboot”, where it is difficult to map normal REST resource (noun) semantics to the URI. The action is performed using the defined “method” at the “path” provided. The “path” must define a unique resource (by URI template) and must be prefixed by the “path” of the “self” link.
The method will normally be a POST:
resources:
book:
links:
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 }
14.1.1.1.3.3.5.1.5. $merge
syntax¶
Services may leverage $merge
as a means for combining two schemas. This should
be regarded as a preprocessing step that results in a valid JSON schema.
The syntax is as follows:
$merge:
source: <object>
with: <object>
The source and with are objects that are combined to form a new object in it’s place. The with object is merged into the source object according to the following rules:
- For each property in with:
- If either source or with is a $ref, dereference the target first
- If a property is in source and the value of with is None, the property is removed from the final object.
- If a property is in source, and both values are in turn objects, recurse into the objects and merge recursively
- Otherwise, set store the value of with in the final object
As an example:
$merge:
source:
x: 1
y: 2
sub:
a: 10
b: 20
with:
x: 0
z: 3
sub:
a: 5
Results in:
x: 0
y: 2
z: 3
sub:
a: 5
b: 20
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:
- Retrieve the data representation for a known “source” resource (say ‘/author/3’)
- Examine the schema for the “target” resource and parse the desired relation definition (say the ‘books’ relation)
- 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 relationvars
mapping.- 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.5. Mapping values to self link parameters¶
The vars
property may be specified to map values from a data
representation of the soruce resource to variables or parameters in the
target resource’s self link. This mapping is what makes it possible to
automatically jump from one resource to a related resource.
Let’s look in detail at the author.relations.books
relation. The
relation’s target resource is books
. The books
self.link is shown
below and was used as an example above in the “Self link” section:
resources:
books:
links:
self:
path: "$/books"
params:
# Allow filtering by author id or title
author: { type: number }
title: { type: string }
Looking at the list of supported parameters, the books
resource
supports filtering based on the author id via the author
parameter.
So, to get the list of books associated with an author
resource, the
id
properties of the author data representation should be used for the
books.links.self.params.author
parameter. This is represented in the
author.relations.books.vars
property:
books:
resource: '#/resources/books'
vars: { author: "0/id" }
The above indicates that author
in the books
resource should be
filled in with the author
data representations id
property.
The “0/id” is a relative JSON pointer, the “0” indicates to start
at the same level as the relation definition, and then traverse using
the “id”. See the appendix on relative JSON pointers for more details
on the syntax.
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.4. Collections and nested links and relations¶
A collection is a common resource pattern that defines two resources, one for accessing an individual element in the collection, the second for accessing the collection as an array. The above “/books/items/{id}” is an example of a collection element and “/books” is the matching collection.
In the simplest case, links
and relations
are defined at the same
level as the base “type” property of the resource:
resources:
info: # non-collection, non-element resource
type: object # of type object
properties:
owner: { type: string }
email: { type: string }
links: # Links at top level
self: { path: "$/info" }
get:
method: GET
response: { $ref: '#/resources/info' }
set:
method: PUT
request: { $ref: '#/resources/info' }
response: { $ref: '#/resources/info' }
relations: # Relations at top level
books: { resource: '#/resources/books' }
authors: { resource: '#/resources/authors' }
Such top-level links apply to an instance of the resource. However,
links and relations may also be defined at other locations within the
data. They are defined at the same level as any type
keyword which begins
the definition of the structure of some subset of the data.
These nested links and relations are necessary to define links and relations based on a specific item in an array.
Consider the structure of the books
resource:
resources:
books:
type: array
items:
type: object
properties:
id: { type: number, readOnly: true }
title: { type: string }
GET /books
[ { id: 1, title: 'My favorite book' },
{ id: 2, title: 'My other favorite book' } ]
The data representation for books
returns an array of data, where each
array element includes the book id and the book title. If the client wants
to examine the authors associated with each book, the full book
resource
must be retrieved for each item in the array, using the id
property of
the array item as the value for {id} in the book self link. Since
the data represents multiple books (2 in the above example), and the
book
resource can only represent one book, the relation must be described
in a way relative to each array element separately.
This relation can be expressed in the service definition
in one of two
ways:
resources:
books:
type: array
items:
type: object # type of each individual array item
properties:
id: { type: number, readOnly: true }
title: { type: string }
relations: # relation on each array item
book:
# Build a book link from the 'id' property
resource: '#/resources/book'
vars:
id: "0/id" # this array item's 'id' property
In this variation, the relation is defined at the same level as the
type: object
, thus the value of the target id
is taken from the
array item id
property.
An alternative is to push the relation definition one level deeper:
resources:
books:
type: array
items:
type: object
properties:
id:
type: number
readOnly: true
relations: # Relation on an object property
book:
# Build a book link from the 'id' property
resource: '#/resources/book'
vars:
id: "0" # this number value
title: { type: string }
Other than moving the relation definition deeper into the data
representation structure to the same level as the type: number
for
the id property, the vars.id
value must change to reflect the fact
that the entire data value at this point in the data structure is used.
In general, it is preferable to put the relation definition as close to the root as possible (the first variation), simply for readability, but links and relations can be defined
There are 2 possible “book” relation links, one for each entry in the response. To resolve the “book” path, first select an entry in the array, then resolve the path. For example, selecting “101” in the array, the “book” path will be “/books/items/101”. The “{$}” indicates that array value should be used. This is described in more detail in the next section.
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 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 |
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 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.2. Include all link variables in the data representation¶
This is particularly important for the self
link, but applies as a
general rule. This allows data representations to be
“self-describing” – meaning that a client can determine the address
for a resource given the data representation and the schema for that
representation.
book.links.self : { path: "$/books/items/{id}" }
{id}
should be in the book data representation
book_chapter.links.self: { path: "$/books/items/{bookid}/chapter/{num}" }
- both
{bookid}
and{num}
should be in the book_chapter data representation
- both
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:
- Using a custom tag (“tags: {collection: true}”)
- The response object has an “items” property which has an array type
- 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:
limit={N}
: maximum number of items in the “items” arrayoffset={N}
: tells the server to skip N itemssortby={fields}
: orders the items in the response according to the specified field/fieldssort={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:
total: integer
: total number of items on the server (regardless of the limit/offset values)count: integer
: size of the “items” collectionoffset: integer
: offset used in the requestnext_offset: integer
andprev_offset: integer
: offset of the next/previous page in a paged collection, which can be used in thenext
/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' |