Skip to content

Instantly share code, notes, and snippets.

@jdesrosiers
Last active October 14, 2024 19:54
Show Gist options
  • Save jdesrosiers/d8b5aeb13fc6b0afc8f46de19b603bd5 to your computer and use it in GitHub Desktop.
Save jdesrosiers/d8b5aeb13fc6b0afc8f46de19b603bd5 to your computer and use it in GitHub Desktop.
Hyper-schema documentation notes

Hyper-schema

Basic concept

Get a JSON instance

GET https://example.com/book/12345
200 OK
Content-Type: application/json
Link: <https://example.com/schemas/book>; rel="describedby"

{
  "title": "Mobby-Dick",
  "authorId": 100,
  ...
}

Get the schema that describes the instance using the "describedby" Link header.

GET https://example.com/schemas/book
200 OK
Content-Type: application/schema+json

{
  "links": [
    {
      "href": "/author/{authorId}",
      "rel": "author",
      "templateRequired": ["authorId"]
    }
  ]
}

Use the LDO combined with the instance to construct a link to get information about the author.

GET https://example.com/author/100
200 OK
Content-Type: application/json
Link: <https://example.com/schemas/author>; rel="describedby"

{
  "name": "Herman Melville",
  ...
}

Keep following links to discover more content.

Actions

Search

Using a link to construct a URI from user input. (Analogous to <form method="get">)

{
  "links": [
    {
      "href": "/books{?title}{?author}"
      "rel": "search",
      "hrefSchema": {
        "properties": {
          "title": { "type": "string" },
          "author": { "type": "integer" },
          ...
        }
      }
    }
  ]
}
GET https://example.com/books?title=Moby-Dick
200 OK
Content-Type: application/json
Link: <https://example.com/schemas/books>; rel="describedby"

[
  {
    "title": "Mobby-Dick",
    "authorId": 100,
    ...
  }
]

Submission

Using a link to submit user input. (Analogous to <form method="post">)

{
  "links": [
    {
      "href": "/books"
      "rel": "collection",
      "submissionSchema": {
        "type": "object",
        "properties": {
          "title": { "type": "string" },
          "authorId": { "type": "integer" },
          ...
        },
        "required": ["title", "authorId", ...]
      }
    }
  ]
}
POST https://example.com/books
Content-Type: application/json

{
  "title": "Moby-Dick",
  "authorId": 100,
  ...
}
204 No Content

Submission Media Type

You can also make submission will media types other than JSON.

{
  "links": [
    {
      "href": "/books"
      "rel": "collection",
      "submissionMediaType": "application/xml",
      "submissionSchema": {
        "type": "object",
        "properties": {
          "title": { "type": "string" },
          "authorId": { "type": "integer" },
          ...
        },
        "required": ["title", "authorId", ...]
      }
    }
  ]
}
POST https://example.com/books
Content-Type: application/xml

<Book>
  <title>Moby-Dick<title>
  <authorId>100</authorId>
  ...
</Book>
204 No Content

Relations

Registered relation

{
  "links": [
    {
      "href": "https://example.com/authors/100",
      "rel": "author"
    }
  ]
}

Custom relation

{
  "links": [
    {
      "href": "https://example.com/review/6789",
      "rel": "https://example.com/relations/review"
    }
  ]
}

Multiple relations

{
  "links": [
    {
      "href": "https://example.com/reviews",
      "rel": ["collection", "https://example.com/relations/reviews"]
    }
  ]
}

Base URIs

A base URI is used to resolve relative URIs.

Default

By default, the base URI for relative URIs in LDOs is the URI used to retrieve the resource

GET https://example.com/books/1
{
  "links": [
    {
      "href": "/author/1",
      "rel": "author"
    }
  ]
}

Link Target: https://example.com/author/1

Setting the base URI

base can be used to alter the base URI. It is a URI Template and can be relative.

GET https://example.com/myapi/v3/books/1
{
  "base": "/myapi/v3/",
  "links": [
    {
      "href": "author/1",
      "rel": "author"
    }
  ]
}

Link Target: https://example.com/myapi/v3/author/1

Setting a link's "anchor"

A link is a connection between two resources. The source of the link is called the "anchor" and the destination is called the "target. The "anchor" usually doesn't need to be specified because it's understood to be the resource the link appears in. anchor allows you to change the link's "anchor" to something other than the resource it appears in.

When anchor appears in an LDO, it becomes the base URI for resolving href.

(NOTE: I don't have an example because I can't think of any reason someone would want to do this. It's an anti-pattern at best. It might be best to just leave anchor and anchorPointer undocumented.)

URI Templates

href, base, and anchor are URI Templates.

Template variables

Link applicability

{
  "type": "object",
  "properties": {
    "documentation": {
      "links": [
        {
          "href": "/docs",
          "rel": "about"
        }
      ]
    }
  }
}
{
}

about: N/A

Note: Links are annotations, which means they're attached to a location in the JSON instance. If the location of the link doesn't exist in the JSON instance, the link doesn't apply.

{
  "documentation": true
}

about: https://example.com/docs

Required/Optional variables

{
  "links": [
    {
      "href": "/books?page={next}{&perPage}",
      "rel": "next",
      "templateRequired": ["next"]
    },
    {
      "href": "/books?page={previous}{&perPage}",
      "rel": "previous",
      "templateRequired": ["previous"]
    }
  ]
}
{
  "list": [
    { ... book 1 ... },
    { ... book 2 ... },
    { ... book 3 ... }
  ],
  "page": 0,
  "next": 1
}

next: https://example.com/books?page=1

previous: N/A

Note: previous doesn't apply because the required property "previous" is not present. Note: perPage is an optional variable. The next link still applies even though there is no "perPage" property.

{
  "list": [
    { ... book 1 ... },
    { ... book 2 ... }
  ],
  "metaData": {
    "page": 0,
    "next": 1,
    "perPage": 2
  }
}

next: https://example.com/books?page=1&perPage=2

previous: N/A

Note: Optional variable perPage is present and included in the link.

Variable coersion

{
  "links": [
    {
      "href": "/{a}",
      "rel": "https://example.com/relations/a"
    }
  ]
}
{ "a": true }

https://example.com/relations/a: https://example.com/true

{ "a": false }

https://example.com/relations/a: https://example.com/false

{ "a": null }

https://example.com/relations/a: https://example.com/null

{ "a": 42 }

https://example.com/relations/a: https://example.com/42

Pointers

Expand a variable from a different object.

{
  "type": "object",
  "properties": {
    "cartItems": {
      "type": "array",
      "items": {
        "links": [
          {
            "href": "cart-item/{cartId}/{cartItemId}",
            "rel": "https://example.com/relations/cart-item",
            "templateRequired": ["cartId", "cartItemId"],
            "templatePointer": {
              "cartId": "/cartId"
            }
          }
        ]
      }
    }
  }
}
{
  "cartId": 100,
  "cartItems": [
    {
      "cartItemId": 200,
      ...
    }
  ],
  ...
}

https://example.com/relations/cart-item: https://example.com/cart-item/100/200

Example using Relative JSON Pointer instead of JSON Pointer.

{
  "type": "object",
  "properties": {
    "cartItems": {
      "type": "array",
      "items": {
        "links": [
          {
            "href": "cart-item/{cartId}/{cartItemId}",
            "rel": "https://example.com/relations/cart-item",
            "templateRequired": ["cartId", "cartItemId"],
            "templatePointer": {
              "cartId": "2/cartId"
            }
          }
        ]
      }
    }
  }
}

https://example.com/relations/cart-item: https://example.com/cart-item/100/200

TODO

  • PUT and DELETE
  • title
  • description
  • targetMediaType
  • targetSchema
  • targetHints
  • headerSchema
  • Special case relations: collection/item, root, self
@jdesrosiers
Copy link
Author

Change log:

  • Changed search example to use hrefSchema
  • Added submissionMediaType example
  • Moved base URI determination out of URI Templates section into its own section
  • Added some notes on anchor/anchorPointer

@jdesrosiers
Copy link
Author

Change log:

  • Added Template variables section

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment