Dynamic constraints - a proposal for a general specification

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 set isPublished to true), and constrained to be either tech or music (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 assume writable would be false if category is null).
  • 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.