PHPnews.io

Solving OpenAPI and JSON Schema Divergence

Written by Phil Sturgeon / Original link on Apr. 13, 2018

My previous article explained the divergence between OpenAPI and JSON Schema (a.k.a the subset/superset/sideset problem), and promised solutions. One of those solutions is a tangible thing, which you can install right now! The other is now ready for tool vendors to start considering.

To briefly recap: OpenAPI v3 declares it supports JSON Schema, but there are more caveats than I can ever remember to that.

data-model-service-model.png

So, if you try to use JSON Schema for your data models, and OpenAPI as the service layer (the glue!) then you bump into errors like this:

$ speccy lint docs/openapi.yml
Specification schema is invalid.

#/paths/~1foo/post/requestBody/content/application~1json/properties/user_uuid
expected Array [ 'string', 'null' ] to be a string
    expected Array [ 'string', 'null' ] to have type string
        expected 'object' to be 'string'

Speccy is a linter we built at WeWork to validate and make recommendations, like rubocop or eslint, but for OpenAPI.

Not only will Speccy consider this invalid, no other OpenAPI/Swagger validator will validate this, and most tools run validation before doing their job.

Postman mirroring via APIMatic Transformer fails.

SDK generation fails.

Everything fails.

Step 1: Converting OpenAPI to JSON Schema

Before we worry about how everything is going to fit together, we need the ability to convert from JSON Schema to OpenAPI-specific schema objects (that conform to as many of those caveats as possible).

This was promptly solved with a new NPM package: json-schema-to-openapi-schema! It was quick to release thanks to an existing package: openapi-schema-to-json-schema.

Creating json-schema-to-openapi was mostly just a case of flipping the tests around, and changing a bunch of the code to just do the opposite of whatever it was doing before.

const toOpenApi = require('json-schema-to-openapi-schema');

const schema = {
  '$schema': 'http://json-schema.org/draft-04/schema#',
  type: ['string', 'null'],
  format: 'date-time',
};

console.log(toOpenApi(schema));

The example prints out:

{
  type: 'string',
  format: 'date-time',
  nullable: true
}

Here's a full list of the conversions it'll make:

Huge props to Henry Andrews (author of the latest JSON Schema drafts) for providing the relevant OpenAPI to convert "dependencies" to. More conversions will need to be made, but I believe 99% of the likely uses are covered.

Henry is also going to be releasing some code to help make this package support multiple drafts of JSON Schema, for now it's only draft 4/5 (they're pretty much the same thing).

Step 2: Workflow

Ok with that done, we need to figure out how and where we convert from JSON Schema to OpenAPI. There are probably a million potential workflows, but here's my recommendation.

Stuff you're doing locally with OpenAPI is usually checking the docs after editing the files, or linting things to see if your changes are ok. Seeing as these are two things Speccy can help with, it seems like a good idea to have Speccy know how to read JSON Schema files.

Default behaviour:

$ speccy lint docs/openapi.yml
Specification schema is invalid.

#/paths/~1invalidations/post/requestBody/content/application~1json/properties/user_uuid
expected Array [ 'string', 'null' ] to be a string
    expected Array [ 'string', 'null' ] to have type string
        expected 'object' to be 'string'

The new --json-schema switch (-j for short).

$ speccy lint docs/openapi.yml -j
Specification is valid, with 0 lint errors

The resolver built into Speccy checks for this switch, and treats $ref like it might be a JSON Schema file. It's fairly harmless to make the assumption for aaaall $ref calls, as it's only going to remove/convert specific keywords that are not valid OpenAPI anyway.

philsturgeon

« API Evolution for REST/HTTP APIs - PHP and the DigitalOcean MySQL cluster »