JSON Hyper schema API

This page documents the JSON schema API provided by cubicweb-jsonschema under the application/schema+json content type.

Application schema

The application schema consists of hyper schema links with top-level entity types in the application.

>>> resp = client.get_schema('/schema')
>>> print(resp)
Response: 200 OK
Content-Type: application/json
Link: </>; rel="describes"; type="application/json"
{
   "$schema": "http://json-schema.org/draft-06/schema#",
   "title": "test app",
   "type": "null",
   "links" : [
      {
         "rel" : "collection",
         "href" : "/author/",
         "targetSchema" : {
            "$ref" : "/author/schema"
         },
         "submissionSchema" : {
            "$ref" : "/author/schema?role=creation"
         },
         "title" : "Author_plural"
      },
      {
         "rel" : "collection",
         "href" : "/book/",
         "targetSchema" : {
            "$ref" : "/book/schema"
         },
         "submissionSchema" : {
            "$ref" : "/book/schema?role=creation"
         },
         "title" : "Book_plural"
      }
   ]
}

Entity type schema

On a given entity type we get the following schema:

>>> resp = client.get_schema('/author/schema')
>>> print(resp)
Response: 200 OK
Content-Type: application/json
Link: </author/>; rel="describes"; type="application/json"
{
   "$schema": "http://json-schema.org/draft-06/schema#",
   "title" : "Author_plural",
   "items" : {
      "properties" : {
         "type" : {
            "type" : "string"
         },
         "id" : {
            "type" : "string"
         },
         "title" : {
            "type" : "string"
         }
      },
      "type" : "object",
      "links": [
         {
            "rel" : "item",
            "href" : "/author/{id}",
            "anchor": "#"
         }
     ]
   },
   "type" : "array",
   "links" : [
      {
         "rel" : "self",
         "href" : "/author/",
         "targetSchema" : {
            "$ref" : "/author/schema"
         },
         "submissionSchema" : {
            "$ref" : "/author/schema?role=creation"
         },
         "title" : "Author_plural"
      }
   ]
}
>>> author_etype_hyperschema = resp.json

Entity schema

Before examining entity’s schema, we need an entity so let’s create one of type Author, by posting data at the /author/ route:

>>> r = client.post_json('/author/', {'name': 'bob'},
...                      headers={'Accept': 'application/json'})
>>> print(r)  
Response: 201 Created
Content-Type: application/json
Location: https://localhost:80/author/.../
{
  "name": "bob"
}
>>> entity_url = r.location

On our Author entity we get the following schema:

>>> resp = client.get_schema(entity_url + '/schema')
>>> print(resp)  
Response: 200 OK
Content-Type: application/json
Link: </author/.../>; rel="describes"; type="application/json"
{
   "$schema": "http://json-schema.org/draft-06/schema#",
   "title" : "Author",
   "type" : "object",
   "properties" : {
      "name" : {
         "title" : "name",
         "type" : "string"
      }
   },
   "additionalProperties" : false,
   "links" : [
      {
         "rel" : "collection",
         "href" : "/author/",
         "targetSchema" : {
            "$ref" : "/author/schema"
         },
         "title" : "Author_plural"
      },
      {
         "rel" : "self",
         "href" : "/author/.../",
         "targetSchema" : {
            "$ref" : "/author/.../schema?role=view"
         },
         "submissionSchema" : {
            "$ref" : "/author/.../schema?role=edition"
         },
         "title" : "Author #..."
      },
      {
        "href": "/author/.../publications/",
        "rel": "related",
        "title": "publications"
      }
   ]
}
>>> author_hyperschema = resp.json

Hyperschema links

Let’s now follow the self link above which indicates how to interact with the current resource (entity). For instance, would we need to perform edition of this resource we should send a request to self link’s href URL with a payload matching the JSON Schema pointed at by schema property of the link:

>>> self_link = author_hyperschema['links'][1]
>>> resp = client.get_schema(self_link['submissionSchema']['$ref'])
>>> print(resp)
Response: 200 OK
Content-Type: application/json
{
   "$schema": "http://json-schema.org/draft-06/schema#",
   "title" : "Author",
   "type" : "object",
   "properties" : {
      "name" : {
         "type" : "string",
         "title" : "name"
      }
   },
   "required" : [
      "name"
   ],
   "additionalProperties" : false
}

On the other hand, self link’s targetSchema is:

>>> resp = client.get_schema(self_link['targetSchema']['$ref'])
>>> print(resp)
Response: 200 OK
Content-Type: application/json
{
    "$schema": "http://json-schema.org/draft-06/schema#",
    "title": "Author",
    "type": "object",
    "properties": {
      "name": {
        "type": "string",
        "title": "name"
      }
    },
    "additionalProperties": false
}

which is exactly the same schema as found in the Hyper Schema obtained above.

Entity workflow schema

Workflowable entity types expose the schema of their workflow transitions at the /<etype>/<eid>/workflow-transitions/schema endpoint. We’ll work on a UserAccount entity type which has a simple createdactivateddeactivated workflow.

First, let’s create a UserAccount entity by looking at what the creation schema describes:

>>> r = client.get('/useraccount/schema?role=creation',
...                headers={'Accept': 'application/schema+json'})
>>> print(r)  
Response: 200 OK
Content-Type: application/json
Link: </useraccount/>; rel="describes"; type="application/json"
{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "additionalProperties": false,
  "properties": {
    "username": {
      "title": "username",
      "type": "string"
    }
  },
  "required": [
    "username"
  ],
  "title": "UserAccount",
  "type": "object"
}
>>> r = client.post_json('/useraccount/', {'username': 'tommy'},
...                      headers={'Accept': 'application/json'})
>>> print(r)  
Response: 201 Created
Content-Type: application/json
Location: https://localhost:80/useraccount/.../
{
  "username": "tommy",
  "in_state": "created"
}

Notice the "in_state" property that appears in the JSON instance along with respective schema as shown below.

From our entity’s JSON Hyper Schema, we can find a link with a custom relation type tag:cubicweb.org,2017:workflow-transitions (following the Tag URI scheme) as advised by JSON Hyper Schema specification:

>>> r = client.get('/useraccount/tommy/schema',
...                headers={'Accept': 'application/schema+json'})
>>> print(r)  
Response: 200 OK
Content-Type: application/json
{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "additionalProperties": false,
  "links": [
    {
      "href": "/useraccount/",
      "rel": "collection",
      "targetSchema": {
        "$ref": "/useraccount/schema"
      },
      "title": "UserAccount_plural"
    },
    {
      "href": "/useraccount/tommy/",
      "rel": "self",
      "submissionSchema": {
        "$ref": "/useraccount/tommy/schema?role=edition"
      },
      "targetSchema": {
        "$ref": "/useraccount/tommy/schema?role=view"
      },
      "title": "UserAccount #..."
    },
    {
      "href": "/useraccount/tommy/workflow-transitions/",
      "rel": "tag:cubicweb.org,2017:workflow-transitions",
      "title": "workflow history"
    }
  ],
  "properties": {
    "username": {
      "title": "username",
      "type": "string"
    },
    "in_state": {
      "title": "state",
      "type": "string",
      "readOnly": true
    }
  },
  "title": "UserAccount",
  "type": "object"
}

This link endpoint is of course described by a JSON Schema to be found by looking at rel="describedby" Link header:

>>> r = client.head('/useraccount/tommy/workflow-transitions/',
...                 headers={'Accept': 'application/json'})
>>> print(r)
Response: 200 OK
Content-Type: application/json
Link: </useraccount/tommy/>; rel="up"; title="tommy", </useraccount/tommy/workflow-transitions/schema>; rel="describedby"; type="application/schema+json"
>>> r = client.get('/useraccount/tommy/workflow-transitions/schema',
...                headers={'Accept': 'application/schema+json'})
>>> print(r)  
Response: 200 OK
Content-Type: application/json
Link: </useraccount/.../workflow-transitions/>; rel="describes"; type="application/json"
{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "items": {
    "links": [
      {
        "anchor": "#",
        "href": "/useraccount/.../workflow-transitions/{id}",
        "rel": "item"
      }
    ],
    "properties": {
      "id": {
        "type": "string"
      },
      "title": {
        "type": "string"
      },
      "type": {
        "type": "string"
      }
    },
    "type": "object"
  },
  "links": [
    {
      "href": "/useraccount/.../workflow-transitions/",
      "rel": "self",
      "submissionSchema": {
        "$ref": "/useraccount/.../workflow-transitions/schema?role=creation"
      },
      "targetSchema": {
        "$ref": "/useraccount/.../workflow-transitions/schema"
      },
      "title": "TrInfo_plural"
    }
  ],
  "title": "TrInfo_plural",
  "type": "array"
}

This Hyper-Schema is an array of items (which will be TrInfo objects here). The submissionSchema documents how to submit a new workflow transition:

>>> r = client.get('/useraccount/tommy/workflow-transitions/schema?role=creation',
...                headers={'Accept': 'application/schema+json'})
>>> print(r)  
Response: 200 OK
Content-Type: application/json
Link: </useraccount/.../workflow-transitions/>; rel="describes"; type="application/json"
{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "properties": {
    "comment": {
      "type": "string"
    },
    "name": {
      "oneOf": [
        {
          "enum": [
            "activate"
          ],
          "title": "activate"
        }
      ],
      "type": "string"
    }
  },
  "required": [
    "name"
  ],
  "title": "TrInfo",
  "type": "object"
}

We see that we should send a JSON document with a required name property and an optional comment. Values for name are restricted to possible transitions on the entity as can be seen in the oneOf array above.

Now if we change the state of the entity:

>>> r = client.post_json('/useraccount/tommy/workflow-transitions',
...                      {'name': 'activate'},
...                      headers={'Accept': 'application/json'})

and fetch back the previous JSON Schema:

>>> r = client.get('/useraccount/tommy/workflow-transitions/schema?role=creation',
...                headers={'Accept': 'application/schema+json'})
>>> print(r)  
Response: 200 OK
Content-Type: application/json
Link: </useraccount/.../workflow-transitions/>; rel="describes"; type="application/json"
{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "properties": {
    "comment": {
      "type": "string"
    },
    "name": {
      "oneOf": [
        {
          "enum": [
            "deactivate"
          ],
          "title": "deactivate"
        }
      ],
      "type": "string"
    }
  },
  "required": [
    "name"
  ],
  "title": "TrInfo",
  "type": "object"
}