-
-
Notifications
You must be signed in to change notification settings - Fork 150
The endpoints are described in OpenAPI 3.0 stored in YAML files. The choice for OpenAPI 3.0 is determined by the fact that we use JSONSchema::Validator
which currently does not provide OAS 3.1 support (yet). The library which does support OAS3.1 support (JSON::Schema::Modern
), imports Mojolicious, which is a full web framework that we currently don't depend on.
- Factor commonality into shared description
- Definition of response codes
- Factor out boiler plate from all APIs
- Generate documentation using OpenAPI 3.0 input files
- Definition of the use of filter criteria
- Definition of the interaction of resources with workflows
- [ ]
In REST, there's the concept of HATEOAS: the idea that the client knows how to render the content sent by the server without encoding business logic into the client. For the scope of LedgerSMB, "full HATEOAS" as described in ... is over-engineering for LedgerSMB. The goal that HATEOAS intend to achieve is a goal for the design of our API as well: separating the UI from the underlying business process, using the API not only to convey state of that process, but also to convey available next steps in it.
The API implements the list of available next actions - similar to how it's done with many APIs implementing HATEOAS - by sending a _links
section in our resources, e.g. for a warehouse:
{
"_links": [
{ "rel": "self", "href": "/warehouses/1" },
{ "rel": "delete", "href": "/warehouses/1", "method": "DELETE" },
{ "rel": "modify", "href": "/warehouses/1", "method": "PATCH" },
],
"description": "Warehouse 1",
"id": 1,
}
The above resource indicates to the UI that the available next actions on the resource are "delete" and "modify". The intent is for the client to know how to use these links from the links section and follow them appropriately. That is: a UI client knows how to present these actions to the user.
By providing these _links
, the client doesn't need to know about authorizations extended to the user: the server knows about those and only sends those links
which are available to the user in the context of the current request. The client only renders the links it has been handed. Since the client only knows about types of links and not about the links itself, there is great flexibility to the server to send links that the client doesn't have prior knowledge about: as long as the rel
types are known to the client, it can render the actions as available actions to the user.
The idea here is to keep the number of possible rel
types low in order to keep complexity on the client low. Much more elaborate examples for invoices are included below which show the concept of using a single rel
type for a series of actions that can be performed based on the current resource.
Web service requests currently support exactly one input media type: application/json
. End points intended for file uploads accept a wide range of media types as an exception.
Incoming requests have the following validations applied:
- Maximum request body
- JSON errors
- Schema correctness (e.g. required fields)
- Functional correctness (e.g. existing (cross) references to data entities)
Each of these phases can generate one or more errors. Per phase, the server will determine as many errors as possible and report back all errors found.
Requests on existing resources will be managed for "idempotency" using ETag headers: Every response on an existing or created resource returns an ETag header which must be used by modifying (PUT/PATCH/POST) service calls to uniquely identify the resource version being modified. Creation requests are a different matter: since there is no resource yet, there's no ETag to be retrieved from the server in order to be able to send a modification request. The solution here is to add a "creation UUID" to each creation request. The server checks the creation UUID on each creation request against the UUIDs in the system. When there's a match, the submitted data is compared to the state of the existing resource. When the resource state matches (the definition of a match may be resource specific), the existing resource is returned. When the state does not match, an error 422 (Unprocessable request) is returned.
{
"_links": [
{ "rel": "self", "href": "/invoices/3" },
{ "rel": "delete", "href": "/invoices/3", "method": "DELETE" },
{ "rel": "modify", "href": "/invoices/3", "method": "PUT" },
{ "rel": "transition", "href": "/invoices/3/transitions", "method": "POST", "transition": "post" },
{ "rel": "transition", "href": "/invoices/3/transitions", "method": "POST", "transition": "to-sales-order" },
{ "rel": "transition", "href": "/invoices/3/transitions", "method": "POST", "transition": "to-purchase-order" },
],
{
"id": 3,
"state": "SAVED",
"..."
}
}
{
"_links": [
{ "rel": "self", "href": "/invoices/3" },
{ "rel": "delete", "href": "/invoices/3", "method": "DELETE" },
{ "rel": "modify", "href": "/invoices/3", "method": "PUT" },
{ "rel": "transition", "href": "/invoices/3/transitions", "method": "POST", "transition": "void" },
{ "rel": "transition", "href": "/invoices/3/transitions", "method": "POST", "transition": "e-mail" },
{ "rel": "transition", "href": "/invoices/3/transitions", "method": "POST", "transition": "copy" },
{ "rel": "transition", "href": "/invoices/3/transitions", "method": "POST", "transition": "to-sales-order" },
{ "rel": "transition", "href": "/invoices/3/transitions", "method": "POST", "transition": "to-purchase-order" },
],
{
"id": 3,
"state": "POSTED",
"..."
}
}