3

I have a JSON Schema file describing my API. It consists of some definitions as well as some vestigial parts from codegen that I'd like to ignore (the properties and required fields):

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "definitions": {
    "CreateBook": {
      "properties": {
        "title": {"type": "string"},
        "author": {"type": "string"},
        "numPages": {"type": "number"}
      },
      "required": ["title", "author"]
    },
    "CreateShelf": {
      "properties": {
        "books": {"type": "array", "items": {"type": "string"}}
      },
      "required": ["books"]
    }
  },
  "properties": {
    "/api/create-book": {
      "properties": {"type": {"post": {"$ref": "#/definitions/CreateBook"}}},
      "required": ["post"],
      "type": "object"
    },
    "/api/create-shelf": {
      "properties": {"type": {"post": {"$ref": "#/definitions/CreateShelf"}}},
      "required": ["post"],
      "type": "object"
    }
  },
  "required": ["/api/create-book", "/api/create-shelf"],
  "type": "object"
}

I'd like to validate requests according to the definitions. I'd like to completely ignore the properties and required fields, which describe the shape of the API itself, not the individual requests.

Given what I expect to be a CreateBook request and this JSON schema, how should I validate it?

Here's what I tried:

const ajv = new Ajv();
const validate = ajv.compile(jsonSchema);

const body = {
  author: 'Roald Dahl',
  numPages: 234,
  // missing title
};

if (!validate(body, '#/definitions/CreateBook')) {
  console.log(validate.errors);
}

This logs:

[
  {
    keyword: 'required',
    dataPath: '#/definitions/CreateBook',
    schemaPath: '#/required',
    params: { missingProperty: '/api/create-book' },
    message: "should have required property '/api/create-book'"
  }
]

So it's ignoring the dataPath parameter ('#/definitions/CreateBook'). What's the right way to do this? Do I need to create a new schema for every request type?

danvk
  • 15,863
  • 5
  • 72
  • 116

1 Answers1

8

If you use addSchema instead of compile to compile the schema, you can specify a fragment.

const ajv = new Ajv();
ajv.addSchema(jsonSchema);
const validate = ajv.getSchema("#/definitions/CreateBook");

const body = {
  author: 'Roald Dahl',
  numPages: 234,
  // missing title
};

if (!validate(body)) {
  console.log(validate.errors);
}
Jason Desrosiers
  • 22,479
  • 5
  • 47
  • 53
  • It looks like the `$id` is not required, `ajv.getSchema('#/definitions/CreateBook')` works fine. Since `validate` is specific to `CreateBook`, I assume the second param to `validate` on the last line is meaningless. Is it OK to have many validators out at once (one for each endpoint)? I believe ajv keeps track of some state on the `ajv` object itself, so I could imagine this causing trouble. – danvk Sep 15 '20 at 21:13
  • Ahh, I guess you don't need an `$id` unless you need to add more than one schema. Yes, the extra argument to the validate function is not correct. I was modifying your original code and I missed that one. I'll edit the answer to fix those things. – Jason Desrosiers Sep 15 '20 at 21:27
  • "Is it OK to have many validators out at once". Yes, it's fine. If you need to add more than one schema, you'll need to give them `$id`s to distinguish between them. Also, once a validator is compiled, it's just a standalone function that is no longer dependent on ajv state. – Jason Desrosiers Sep 15 '20 at 21:32