Description
[Edited for changes made between #159 and #179]
[Edited to replace hrefVars
with hrefSchema
based on later discussions]
The purpose of this issue is to explore the limitations of the current draft's usage of method
, schema
, and href
(in terms of resolving template variables). There are ideas here that could become a proposal, but I am not directly proposing anything in the example schema. I'm really just trying to find a consensus on the limitations.
The main point here is the need to both specify URL parameters and a message body. The API has events and invitations. The invitation collection can be filtered from either the event perspective or (via email address) the invitee perspective. I did not provide a schema for invitees as it made the example too long without adding anything.
Since the query string is as much a part of the identifier as the path components, this API treats /invitations?event=1234
as the collection of invitations for the given event. The API supports creating invitations for event 1234 by POSTing to that URI, just the same as it would for a URI of /events/elements/1234/invitations
. However, not all of the fields needed to create an invite can be a part of the URI, so a creation also takes a message body providing the remaining parameters.
The current href
specification does not allow it to define user input. But if schema
is used for the body, there is no other mechanism for getting user input into the URI. This example uses hrefSchema
as a way to map the URI template parameters to a schema and/or a value from the instance. This is to help illustrate the two routes of user input, not propose hrefSchema
as necessarily being a solution.
hrefSchema
is a schema which treats the template variables as if they were properties of a JSON object. Each variable property's schema is used to validate "user" input (which may or may not be from an actual human user). It can use $data
with default
to take a default value from the instance when no user input is supplied. To implement the current behavior of only resolving from the instance, you would use a schema like this:
{
"properties": {
"email": {
"enum": {"$data": "0/email"},
"default": {"$data": "0/email"}
}
}
}
For convenience, the above can be specified by just giving the "0/email"
relative JSON pointer instead of a schema (this is what you will see in the example). [EDIT: originally hrefSchema
was hrefVars
and did not quite use regular schema syntax. Now that hrefSchema
is a regular schema, the shortcut might be a bad idea]. The default behavior for template variable {foo}
is "hrefSchema": {"properties": {"foo": "0/foo"}}
(which replicates the current behavior).
I am also not advocating for or against this specific design of collections and individual resources. It was constructed to illustrate my points. You could get around some issues by redesigning the resources, but aside from expecting APIs to not blatantly abuse protocols, I do not think that hyper-schema should be opinionated about resource design.
{
"definitions": {
"identifier": {
"description": "Assigned and managed by the server.",
"type": "integer",
"minimum": 1,
"readOnly": true
},
"event": {
"type": "object",
"properties": {
"id": {"$ref": "#/definitions/identifier"},
"name": {"type": "string"}
}
},
"invitation": {
"type": "object",
"properties": {
"id": {"$ref": "#/definitions/identifier"},
"email": {"$ref": "#/definitions/identifier"},
"event": {"$ref": "#/definitions/identifier"},
"headline": {"type": "string"},
"message": {"type": "string"}
}
},
"eventResource": {
"allOf": [{"$ref": "#/definitions/event"}],
"links": [
{
"rel": "self",
"href": "/events/elements/{id}"
},
{
"rel": "collection",
"href": "/events"
},
{
"rel": "tag:example.com,2016:invitations",
"href": "/invitations{?email,event}",
"hrefSchema": {
"properties": {
"email": {"type": "string", "format": "email"},
"event": "0/id"
}
},
"schema": {
"properties": {
"headline": {
"type": "string",
"default": "0/name"
},
"message": {"type": "string"}
}
}
}
]
},
"invitationResource": {
"allOf": [{"$ref": "#/definitions/invitation"}],
"links": [
{"rel": "self", "href": "/invitations/elements/{id}"},
{"rel": "collection", "href": "/invitations"}
]
},
"eventCollection": {
"type": "object",
"properties": {
"elements": {
"type": "array",
"items": {
"allOf": [{"$ref": "#/definitinos/event"}],
"links": [
{"rel": "item", "href": "/events/elements/{id}"}
]
}
},
"filters": {"name": {"type": "string"}}
},
"links": [
{
"rel": "self",
"href": "/events{?name}",
"hrefSchema": {
"properties": {"name": "0/filters/name"}
}
}
]
},
"invitationCollection": {
"type": "object",
"properties": {
"elements": {
"type": "array",
"items": {
"allOf": [{"$ref": "#/definitions/invitation"}],
"links": [
{ "rel": "item", "href": "/invitations/elements/{id}"}
]
}
},
"filters": {
"type": "object",
"properties": {
"email": {"type": "string", "format": "email"},
"event": {"$ref": "#/definitions/identifier"}
}
}
},
"links": [
{
"rel": "self",
"href": "/invitations{?email,event}",
"hrefSchema": {
"properties": {
"email": "0/filters/email",
"event": "0/filters/event"
}
}
}
]
}
},
"links": [
{
"rel": "tag:example.com,2016:events",
"href": {"$ref": "/events{?name}"},
"hrefSchema": {"properties": {"name": {"type": "string"}}},
"schema": {"$ref": "#/definitions/event"}
},
{
"rel": "tag:example.com,2016:invitations",
"href": {"$ref": "/invitations{?email,event}"},
"hrefSchema": {
"properties": {
"email": {"type": "string", "format": "email"},
"event": {"$ref": "#/definitions/identifier"}
}
}
}
]
}
Given this schema, I should be able to do something like this (separate params and data arguments lifted from the design of Python's "requests" library- again, not insisting this is the solution, just trying to illustrate the challenge):
from magic import restclient
# Assume restclient uses describedBy HTTP
# header links to fetch schemas as needed
entry_point = restclient("https://api.example.com")
# For simplicity, we'll assume the first result is right.
party = restclient.follow("tag:example.com,2016:events",
params={"name": "Awesome Party"})[0]
# This lets the headline be filled in with the event's name automatically
party.submit("tag:example.com,2016:invitations",
params={"email": "[email protected]"},
data={"message": "Come to my party!"})
So in this example, there are several combinations of data sources and destinations:
- "event" is put in the URI, and is only allowed to come from the instance
- "email" is put in the URI, and must come from the user
- "headline" is put in the body, and if not specified, it comes from the instance
- "message" is put in the body, and must come from the user
Currently, this cannot be expressed in hyper-schema. Should it be possible? If we get agreement on that, we can talk about how to do it.
Metadata
Metadata
Assignees
Type
Projects
Status