Client-side json:api resource validation with ajv
Part of signing up for Meet Kinksters involves ensuring users provide sufficient
(and valid) information about themselves and their dating preferences. This data
is of course validated on the server, but I needed a way to provide UI
feedback regarding incomplete Drupal entities (surfaced as json:api
resources)
in real time.
The problem
I discussed this topic on the Drupal Slack #contenta
(API-first) channel, and
my open-source colleagues suggested a number of possible solutions. Most suggested
posting the entire entity and watching a returned meta
object for details on
validation errors. By default, Drupal’s json:api
implementation validates
required fields and all field data and returns a 422 Unprocessable Entity
error
on failure. I could change this behavior, as the Commerce API module does,
but this seemed a little too bespoke for my needs. In addition, I wanted to
maintain the ability to PATCH
specific entity fields as the user completes their
profile, saving bandwidth and making operations more atomic.
json-schema
to the rescue
I was already using Drupal’s jsonapi_schema
module to retrieve enumerations of field options, and so I wondered if these
JSON Schema definitions could also validate client-side
copies of entities under construction. It turns out, they can!
I did need to make a few bugfixes to the Drupal module while I was at it, but
eventually I was able to feed the relevant schemas into ajv
,
an awesome JavaScript JSON schema validator. First the code, and then some gotchas.
import Ajv2019 from "ajv/dist/2019"
import hyperSchema from "../lib/hyper-schema.json";
import hyperSchemaMeta from "../lib/hyper-schema-meta.json";
import linksSchema from "../lib/links-schema.json";
import jsonApiSchema from "../lib/jsonapi-schema.json";
import addFormats from "ajv-formats"
const ajv = new Ajv2019({
strict: false,
// Validating formats can be a security risk in production, and slow.
validateFormats: __DEV__,
// One error might be enough to block progress.
allErrors: __DEV__,
});
__DEV__ && addFormats(ajv);
// Required b/c json:api uses it.
const draft7MetaSchema = require("ajv/dist/refs/json-schema-draft-07.json")
ajv.addMetaSchema(draft7MetaSchema)
ajv.addMetaSchema(hyperSchemaMeta, undefined, false);
ajv.addSchema(linksSchema, undefined, true);
ajv.addSchema(hyperSchema, undefined, true);
ajv.addSchema(jsonApiSchema, 'https://jsonapi.org/schema', true);
// ... Schemas are retrieved from Drupal.
// Inside your function component:
useEffect(() => {
const userValid = userSchemaValidator(context.user);
// Do something with the result.
setSchemasValidate(userValid);
__DEV__ && console.log(
['User Validation Errors', userSchemaValidator.errors, context.user, profileSchemaValidator.schema]
);
}, [context.user]);
What to do with the validation results are up to you, of course.
Schema validation is hard
It took a whole day’s work to get to a working state. The client side of the equation was the simplest part; ajv is well-documented and supported. The difficulty came in fixing a number of issues in my dependencies:
json:api
schema generation in Drupal is still very much under development. Both thejsonapi_schema
andjsonapi_extras
modules needed significant work to generate truly valid schema. I have opened an issue against theschemata
project summarizing the current issues. If you’re working in this space and can contribute, we’d welcome your input. The main sticking points within Drupal’s implementation had to do with handling nullability for non-required fields.- The JSON schema for
json:api
itself was incorrect/out of date. There is a PR open to update it, however I ran into some issues with validation when paired with Drupal’sjsonapi_hypermedia
module. Thankfully, since I am vendoring the proposed schema file within my project, I could update it without much trouble. - It’s not at all intuitive to validate JSON Hyper-Schema with ajv; I had to vendor a number of manually-downloaded schema and meta-schema to include in the validator compilation (as you can see above.) I don’t necessarily think ajv needs to include this schema in its distribution, but it did catch me by surprise.
Was it worth the trouble?
I spent a whole day wrestling the various components in play, mostly within
Drupal. I did learn a lot, for instance the use-case for
JSON Hyper-Schema
in describing json:api
documents. My original goal was to roll up the validation
status of various entities under construction on the client side, and I still
don’t see any real way to do this within my chosen stack. Hopefully the issues
I’ve uncovered regarding Drupal’s schema generation will help others get up
and running faster. I certainly couldn’t be building such a complex application
without the excellent prior efforts of other open-source maintainers.