restjsonschemadiscoverycapability

For a RESTful service - How does a client find valid values for a resource attribute?


As a part of capabilities discovery for a resource provided by a RESTful API, I am looking for a way for the service to announce accepted values for an attribute. Consider the following example, where an apple resource has an attribute color:

GET /apples/17

This request yields:

{
  "name": "My yummy apple",
  "color": "green"
}

For a client to understand what colorvalues are valid when for instance PUTting a new version of this apple, I can think of many possible ways. However I haven't found any best practices here. The HTTP OPTIONS verb seems not to be made for this fine-grained kind of discovery. Should I just add an array attribute to the /apples collection:

GET /apples

Response:

{
  ...
  "colorValues": ["red", "green"]
}

Are there any better and more commonly used ways?

EDIT: Just realized that one possible way would be to add a resources for schemas for all "real" resources. Something liked GET /schemas/apple that would yield a JSON Schema representation for the apple resource. Modified example from json-schema.org:

{
    "id": "http://foo.bar/schema#",
    "$schema": "http://json-schema.org/draft-04/schema#",
    "description": "schema for an apple resource",
    "type": "object",
    ...
    "colorValues": {
        "enum": [ "red", "green" ]
    }
}

I have not found any examples of this though.


Solution

  • JSON Hyper-Schema is great for this kind of thing.

    GET /apples/17
    
    HTTP/1.1 OK
    Content-Type: application/json; profile="/schema/apple"
    
    {
      "id": 17,
      "name": "My yummy apple",
      "color": "green"
    }
    

    A client can then dereference the hyper-schema at schema/apple to learn what links are available to follow next.

    GET /schema/apple
    
    HTTP/1.1 OK
    Content-Type: application/schema+json
    
    {
      "id": "http://foo.bar/schema/apple",
      "$schema": "http://json-schema.org/draft-04/hyper-schema#",
      "type": "object",
      "properties": {
        "id": { "type": "string", "readOnly": true },
        "name": { "type": "string" },
        "color": { "enum": ["red", "green"] }
      },
      "required": ["id", "name", "color"],
      "links": [
        { "rel": "self", "href": "/apple/{id}" },
        {
          "rel": "http://foo.bar/relation/edit",
          "href": "/apple/{id}",
          "method": "PUT",
          "schema": { "$ref": "#" }
        }
      ]
    }
    

    This is a JSON Schema like you are familiar with, but contains an extra keyword links that describes links you can follow from this resource. The client evaluates the href URI Templates using the values from the original JSON data. So, in this case, the link evaluates to ...

    {
      "rel": "http://foo.bar/relation/edit",
      "href": "/apple/17",
      "method": "PUT",
      "schema": { "$ref": "#" }
    }
    

    This link directs the client that it can make a PUT request to /apple/17 and the request body should validate against the schema at /schema/apple ({ "$ref": "#" } means this schema).

    This gives you both a human readable and a machine executable description of your functionality. The machine executable part is a big deal because changes can often be made to your API without breaking existing clients.

    NOTE: This code is written in JSON Hyper-Schema draft-04. There is a new version draft-05 that came out recently. It makes a few controversial changes. I'm still recommending draft-04 at this point. The specification can be found at https://datatracker.ietf.org/doc/html/draft-luff-json-hyper-schema-00.