In a few of my APIs, I have had the need to indicate to the API client (front-end) dynamic constraints for certain attributes and relationships. In working with this, I have produced a specification that works well for us.
I post it here with two goals:
- In the hopes that it is useful as-is to the JSON:API community
- To get feedback: Any obvious weaknesses? Significant improvements? Could this work as a JSON:API v1.1 profile?
Specification for dynamic constraints
This specification is intended to facilitate consistent communication of dynamic field restrictions, i.e. restrictions that may differ during the lifetime of a resource or between different resources of the same type.
Resources MAY have a constraints
attribute. The constraints
attribute MUST be considered a read-only attribute. The constraints
attribute MUST be an object (called a resource constraints collection) whose property names match resources field names. The value of each such property MUST be an object (called field constraints collection). Each field constraints collection property (called constraint) describes a particular constraint for the corresponding resource field.
Different APIs will differ in which constraints are needed. This specification is agnostic regarding the actual constraints and their semantic meaning. This should be defined for each API/resource.
Below is an example showing a resource with three potential constraints; requiredForPublish
, whitelist
and writable
.
{
"data": {
"type": "articles",
"id": "92a34212",
"attributes": {
"category": "tech",
"title": "Try JSON:API!",
"isPublished": false,
"constrains": {
"category": {
"requiredForPublish": true,
"whitelist": [ "tech", "music"]
},
"isPublished": {
"writable": true
},
"author": {
"writable": false
}
},
"relationships": {
"author": {
"data": {
"type": "person",
"id": "aad385f1"
}
}
}
}
}
}
In the example above:
- The
category
attribute is “required for publishing” (the exact semantics are defined by the API, but we can assume that it must be non-null before we can setisPublished
totrue
), and constrained to be eithertech
ormusic
(it may e.g. be an enum with additional documented values, but the set of allowed values may depend on the privileges of the current user). - The
title
attribute is not constrained. - The
isPublished
attribute is currently writable (based on the above, we can assumewritable
would befalse
ifcategory
isnull
). - The
author
relationship is not writable. (For example, the user may not have privileges to change the article’s author.)
To clarify the nomenclature:
- The value of the
constraints
attribute is called the resource constraints collection. - Each
constraints
property (category
,isPublished
,author
) is called a field constraints collection. - Each field constraints collection property (
requiredForPublish
,whitelist
,writable
) is called a constraint.
Constraints MAY be effectively no-op, e.g. "minLength": 0
or "writable": true
. Front-end developers may want to take this into account so the user won’t see a message saying “at least 0 characters” below an input field.
Constraints MAY be excluded from a response if they are effectively no-op. This may be done at any level: for individual constraints, for field constraint collections (if it only has no-op constraints), or for the top-level resource constraints collection (excluding the constraints
attribute if all constraints are no-op).
When resource fields are excluded using sparse fieldsets, the corresponding field constraints collection MAY also be excluded. (In the example above, if specifying fields[articles]=category,constraints
, then constraints.isPublished
and constraints.author
may be excluded.)
Constraints are intended for dynamic restrictions, i.e. restrictions that may differ during the lifetime of a resource or between different resources of the same type. Statically known constraints, such as if a field is permanently read-only or restricted to known values (i.e. enums) or a specific length (e.g. due to database constraints), may be part of the API specification instead of being modeled using constraints. There are however valid use-cases for using constraints for what is effectively static restrictions (or lack of restrictions). For example:
- A
writable
constraint on a field that is currently permanently writable can ensure backwards compatibility if it must later be made read-only (permanently or dynamically). - A
writable
constraint on a field that is currenly permanently read-only can ensure API clients automatically support writing to the field if it is made writable.
In such cases, however, the restrictions should only be supplied using constraints
, not statically in the API specification, since any statically specified constraints indicate that they may be hardcoded into API clients.
Furthermore, returning a writable
(or similar) constraint for fields that are currently permanently read-only must be weighed up against the complexity for API clients to support these fields. If it is not likely that a field will ever be made writable, it may be better to indicate this statically in the API specification, since this may enable front-ends to use more suitable controls (such as displaying text directly vs. using a disabled input.)
Apart from the requirements in this specification, the constraints
attribute is a normal resource attribute as per the JSON:API specification.