diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..e59f259 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,20 @@ +Original work copyright (c) 2020 JSON Type Definition Contributors +Modified work copyright (c) 2021 Benjamin Herman + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..44a785a --- /dev/null +++ b/README.md @@ -0,0 +1,187 @@ +# jtd: JSON Validation for JavaScript + +Deno port of the [Node.js package of the same name][repo]. + +[JSON Type Definition][jtd], aka [RFC 8927], is an easy-to-learn, standardized +way to define a schema for JSON data. You can use JSON Typedef to portably +validate data across programming languages, create dummy data, generate code, +and more. + +This `jtd` package is a TypeScript implementation of JSON Type Definition. It +lets you validate input data against JSON Type Definition schemas. This ported +version of `jtd` works in Deno, but the [original][repo] works in Node.js and +web browsers. + +If you're looking to generate code from schemas, check out "Generating +TypeScript from JSON Typedef schemas" in the JSON Typedef docs. + +## Importing + +```js +import * from "https://deno.land/x/jtd@v0.1.0/mod.ts"; +``` + +## Documentation + +Detailed API documentation is available online at: + +https://doc.deno.land/https/deno.land/x/jtd@v0.1.0/mod.ts + +For more high-level documentation about JSON Typedef in general, or JSON Typedef +in combination with JavaScript in particular, see: + +- [The JSON Typedef Website][jtd] +- ["Generating TypeScript from JSON Typedef Schemas"][jtd-ts-codegen] + +## Basic Usage + +Here's an example of how you can use this package to validate JSON data against +a JSON Typedef schema: + +```ts +import { Schema, validate } from "https://deno.land/x/jtd@v0.1.0/mod.ts"; + +const schema = { + properties: { + name: { type: "string" }, + age: { type: "uint32" }, + phones: { + elements: { type: "string" }, + }, + }, +} as Schema; + +// jtd.validate returns an array of validation errors. If there were no problems +// with the input, it returns an empty array. + +// Outputs: [] +console.log(validate(schema, { + name: "John Doe", + age: 43, + phones: ["+44 1234567", "+44 2345678"], +})); + +// This next input has three problems with it: +// +// 1. It's missing "name", which is a required property. +// 2. "age" is a string, but it should be an integer. +// 3. "phones[1]" is a number, but it should be a string. +// +// Each of those errors corresponds to one of the errors returned by validate. + +// Outputs: +// +// [ +// { instancePath: [], schemaPath: [ 'properties', 'name' ] }, +// { +// instancePath: [ 'age' ], +// schemaPath: [ 'properties', 'age', 'type' ] +// }, +// { +// instancePath: [ 'phones', '1' ], +// schemaPath: [ 'properties', 'phones', 'elements', 'type' ] +// } +// ] +console.log(validate(schema, { + age: "43", + phones: ["+44 1234567", 442345678], +})); +``` + +## Advanced Usage: Limiting Errors Returned + +By default, `jtd.validate` returns every error it finds. If you just care about +whether there are any errors at all, or if you can't show more than some number +of errors, then you can get better performance out of `jtd.validate` using the +`maxErrors` option. + +For example, taking the same example from before, but limiting it to 1 error, we +get: + +```ts +// Outputs: +// +// [ { instancePath: [], schemaPath: [ 'properties', 'name' ] } ] +console.log(validate(schema, { + age: "43", + phones: ["+44 1234567", 442345678], +}, { maxErrors: 1 })); +``` + +## Advanced Usage: Handling Untrusted Schemas + +If you want to run `jtd` against a schema that you don't trust, then you should: + +1. Ensure the schema is well-formed, using `jtd.isSchema` and + `jtd.isValidSchema`. `isSchema` does basic "type" checking (and in + TypeScript, it acts as a type guard for the `Schema` type), while + `isValidSchema` validates things like making sure all `ref`s have + corresponding definitions. + +2. Call `jtd.validate` with the `maxDepth` option. JSON Typedef lets you write + recursive schemas -- if you're evaluating against untrusted schemas, you + might go into an infinite loop when evaluating against a malicious input, + such as this one: + + ```json + { + "ref": "loop", + "definitions": { + "loop": { + "ref": "loop" + } + } + } + ``` + + The `maxDepth` option tells `jtd.validate` how many `ref`s to follow + recursively before giving up and throwing `jtd.MaxDepthExceededError`. + +Here's an example of how you can use `jtd` to evaluate data against an untrusted +schema: + +```ts +import { isSchema, isValidSchema, Schema, validate } from "jtd"; + +// validateUntrusted returns true if `data` satisfies `schema`, and false if it +// does not. Throws an error if `schema` is invalid, or if validation goes in an +// infinite loop. +function validateUntrusted(schema: unknown, data: unknown): boolean { + if (!isSchema(schema) || !isValidSchema(schema)) { + throw new Error("invalid schema"); + } + + // You should tune maxDepth to be high enough that most legitimate schemas + // evaluate without errors, but low enough that an attacker cannot cause a + // denial of service attack. + return validate(schema, data, { maxDepth: 32 }).length === 0; +} + +// Returns true +validateUntrusted({ type: "string" }, "foo"); + +// Returns false +validateUntrusted({ type: "string" }, null); + +// Throws "invalid schema" +validateUntrusted({ type: "nonsense" }, null); + +// Throws an instance of jtd.MaxDepthExceededError +validateUntrusted({ + "ref": "loop", + "definitions": { + "loop": { + "ref": "loop", + }, + }, +}, null); +``` + +## LICENSE + +[MIT](LICENSE.txt) + +[repo]: https://github.com/jsontypedef/json-typedef-js +[RFC 8927]: https://tools.ietf.org/html/rfc8927 +[jtd]: https://jsontypedef.com +[jtd-ts-codegen]: https://jsontypedef.com/docs/typescript-codegen/ diff --git a/_rfc3339.test.ts b/_rfc3339.test.ts new file mode 100644 index 0000000..2b443b8 --- /dev/null +++ b/_rfc3339.test.ts @@ -0,0 +1,39 @@ +import { isRFC3339 } from "./_rfc3339.ts"; +import { assertEquals } from "./dev_deps.ts"; + +const testCases: [string, boolean][] = [ + // From the RFC + ["1985-04-12T23:20:50.52Z", true], + ["1990-12-31T23:59:60Z", true], + ["1990-12-31T15:59:60-08:00", true], + ["1937-01-01T12:00:27.87+00:20", true], + + // T and Z can be t or z + ["1985-04-12t23:20:50.52z", true], + + // https://github.com/chronotope/chrono/blob/main/src/format/parse.rs + ["2015-01-20T17:35:20-08:00", true], // normal case + ["1944-06-06T04:04:00Z", true], // D-day + ["2001-09-11T09:45:00-08:00", true], + ["2015-01-20T17:35:20.001-08:00", true], + ["2015-01-20T17:35:20.000031-08:00", true], + ["2015-01-20T17:35:20.000000004-08:00", true], + ["2015-01-20T17:35:20.000000000452-08:00", true], // too small + ["2015-02-30T17:35:20-08:00", false], // bad day of month + ["2015-01-20T25:35:20-08:00", false], // bad hour + ["2015-01-20T17:65:20-08:00", false], // bad minute + ["2015-01-20T17:35:90-08:00", false], // bad second + + // Ensure the regex is anchored + ["x1985-04-12T23:20:50.52Zx", false], + ["1985-04-12T23:20:50.52Zx", false], +]; + +Deno.test({ + name: "isRFC3339", + fn() { + for (const [input, result] of testCases) { + assertEquals(isRFC3339(input), result); + } + }, +}); diff --git a/_rfc3339.ts b/_rfc3339.ts new file mode 100644 index 0000000..f28bcdc --- /dev/null +++ b/_rfc3339.ts @@ -0,0 +1,67 @@ +const pattern = + /^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$/; + +export function isRFC3339(s: string): boolean { + const matches = s.match(pattern); + if (matches === null) { + return false; + } + + const year = parseInt(matches[1], 10); + const month = parseInt(matches[2], 10); + const day = parseInt(matches[3], 10); + const hour = parseInt(matches[4], 10); + const minute = parseInt(matches[5], 10); + const second = parseInt(matches[6], 10); + + if (month > 12) { + return false; + } + + if (day > maxDay(year, month)) { + return false; + } + + if (hour > 23) { + return false; + } + + if (minute > 59) { + return false; + } + + // A value of 60 is permissible as a leap second. + if (second > 60) { + return false; + } + + return true; +} + +function maxDay(year: number, month: number) { + if (month === 2) { + return isLeapYear(year) ? 29 : 28; + } + + return MONTH_LENGTHS[month]; +} + +function isLeapYear(n: number): boolean { + return n % 4 === 0 && (n % 100 !== 0 || n % 400 === 0); +} + +const MONTH_LENGTHS = [ + 0, // months are 1-indexed, this is a dummy element + 31, + 0, // Feb is handled separately + 31, + 30, + 31, + 30, + 31, + 31, + 30, + 31, + 30, + 31, +]; diff --git a/dev_deps.ts b/dev_deps.ts new file mode 100644 index 0000000..8b1e018 --- /dev/null +++ b/dev_deps.ts @@ -0,0 +1 @@ +export * from "https://deno.land/std@0.96.0/testing/asserts.ts"; diff --git a/mod.test.ts b/mod.test.ts new file mode 100644 index 0000000..13d2453 --- /dev/null +++ b/mod.test.ts @@ -0,0 +1,79 @@ +import { + isSchema, + isValidSchema, + MaxDepthExceededError, + validate, +} from "./mod.ts"; +import { assert, assertEquals, assertThrows } from "./dev_deps.ts"; +import { testCases } from "./test_cases.test.ts"; + +Deno.test({ + name: "Schema validity", + fn() { + for (const { description, schema, validSchema } of testCases) { + assertEquals( + isSchema(schema) && isValidSchema(schema), + validSchema, + `${ + validSchema ? "Valid" : "Invalid" + } schema in test case with description "${description}" ${ + validSchema ? "did not validate" : "was incorrectly validated" + }.`, + ); + } + }, +}); + +Deno.test({ + name: "Validation supports limited depth", + fn() { + const schema = { + definitions: { + foo: { + ref: "foo", + }, + }, + ref: "foo", + }; + const instance = null; + assertThrows( + () => validate(schema, instance, { maxDepth: 5, maxErrors: 0 }), + MaxDepthExceededError, + ); + }, +}); + +Deno.test({ + name: "Validation supports limited errors", + fn() { + const schema = { + elements: { + type: "string" as const, + }, + }; + const instance = [null, null, null, null, null]; + assertEquals( + validate(schema, instance, { maxDepth: 0, maxErrors: 3 }).length, + 3, + ); + }, +}); + +Deno.test({ + name: "Validate test cases", + fn() { + const validTestCases = testCases.filter((testCase) => testCase.validSchema); + for (const { description, schema, instance, errors } of validTestCases) { + assert(isSchema(schema)); + assertEquals( + validate(schema, instance), + errors, + `${ + errors.length ? "Invalid" : "Valid" + } instance in test case with description "${description}" ${ + errors.length ? "incorrectly validated" : "did not validate" + }.`, + ); + } + }, +}); diff --git a/mod.ts b/mod.ts new file mode 100644 index 0000000..429091e --- /dev/null +++ b/mod.ts @@ -0,0 +1,2 @@ +export * from "./schema.ts"; +export * from "./validate.ts"; diff --git a/schema.ts b/schema.ts new file mode 100644 index 0000000..b1491a9 --- /dev/null +++ b/schema.ts @@ -0,0 +1,537 @@ +/** + * `Schema` is a TypeScript representation of a correct JSON Typedef schema. + * + * The JSON Typedef specification allows schemas to take on one of eight forms. + * Each of those forms has its own type in this module; `Schema` is simply a union + * of each of those eight types. + */ +export type Schema = + | SchemaFormEmpty + | SchemaFormRef + | SchemaFormType + | SchemaFormEnum + | SchemaFormElements + | SchemaFormProperties + | SchemaFormValues + | SchemaFormDiscriminator; + +/** + * `SchemaFormEmpty` represents schemas of the empty form. + */ +export type SchemaFormEmpty = SharedFormProperties; + +/** + * `SchemaFormRef` represents schemas of the ref form. + */ +export type SchemaFormRef = SharedFormProperties & { + ref: string; +}; + +/** + * `SchemaFormType` represents schemas of the type form. + */ +export type SchemaFormType = SharedFormProperties & { + type: Type; +}; + +/** + * `Type` represents the legal values of the "type" keyword in JSON Typedef. + */ +export type Type = + | "boolean" + | "float32" + | "float64" + | "int8" + | "uint8" + | "int16" + | "uint16" + | "int32" + | "uint32" + | "string" + | "timestamp"; + +/** + * `SchemaFormEnum` represents schemas of the enum form. + */ +export type SchemaFormEnum = SharedFormProperties & { + enum: string[]; +}; + +/** + * `SchemaFormElements` represents schemas of the elements form. + */ +export type SchemaFormElements = SharedFormProperties & { + elements: Schema; +}; + +/** + * `SchemaFormProperties` represents schemas of the properties form. + */ +export type SchemaFormProperties = + & SharedFormProperties + & ( + | { + properties?: { [name: string]: Schema }; + optionalProperties: { [name: string]: Schema }; + additionalProperties?: boolean; + } + | { + properties: { [name: string]: Schema }; + optionalProperties?: { [name: string]: Schema }; + additionalProperties?: boolean; + } + ); + +/** + * `SchemaFormValues` represents schemas of the values form. + */ +export type SchemaFormValues = SharedFormProperties & { + values: Schema; +}; + +/** + * `SchemaFormDiscriminator` represents schemas of the discriminator form. + */ +export type SchemaFormDiscriminator = SharedFormProperties & { + discriminator: string; + mapping: { [name: string]: Schema }; +}; + +/** + * `SharedFormProperties` contains the properties shared among all schema forms. + */ +interface SharedFormProperties { + definitions?: { [definition: string]: Schema }; + metadata?: { [name: string]: unknown }; + nullable?: boolean; +} + +/** + * `isEmptyForm` checks whether some `Schema` is of the empty form. + * + * @param schema The schema to validate + */ +export function isEmptyForm(schema: Schema): schema is SchemaFormEmpty { + // deno-lint-ignore no-unused-vars + const { definitions, nullable, metadata, ...rest } = schema; + return Object.keys(rest).length === 0; +} + +/** + * `isRefForm` checks whether some `Schema` is of the ref form. + * + * @param schema The schema to validate + */ +export function isRefForm(schema: Schema): schema is SchemaFormRef { + return "ref" in schema; +} + +/** + * `isTypeForm` checks whether some `Schema` is of the type form. + * + * @param schema The schema to validate + */ +export function isTypeForm(schema: Schema): schema is SchemaFormType { + return "type" in schema; +} + +/** + * `isEnumForm` checks whether some `Schema` is of the enum form. + * + * @param schema The schema to validate + */ +export function isEnumForm(schema: Schema): schema is SchemaFormEnum { + return "enum" in schema; +} + +/** + * `isElementsForm` checks whether some `Schema` is of the elements form. + * + * @param schema The schema to validate + */ +export function isElementsForm(schema: Schema): schema is SchemaFormElements { + return "elements" in schema; +} + +/** + * `isPropertiesForm` checks whether some `Schema` is of the properties form. + * + * @param schema The schema to validate + */ +export function isPropertiesForm( + schema: Schema, +): schema is SchemaFormProperties { + return "properties" in schema || "optionalProperties" in schema; +} + +/** + * `isPropertiesForm` checks whether some `Schema` is of the values form. + * + * @param schema The schema to validate + */ +export function isValuesForm(schema: Schema): schema is SchemaFormValues { + return "values" in schema; +} + +/** + * `isDiscriminatorForm` checks whether some `Schema` is of the values form. + * + * @param schema The schema to validate + */ +export function isDiscriminatorForm( + schema: Schema, +): schema is SchemaFormDiscriminator { + return "discriminator" in schema; +} + +/** + * `isValidSchema` checks whether some `Schema` is correct, according to the syntax + * rules of JSON Typedef. + * + * In particular, `isValidSchema` verifies that: + * + * 1. The schema does not have any non-root definitions, + * 2. All references point to actually-existing definitions, + * 3. All enums are non-empty, and do not contain duplicates, + * 4. The `properties` and `optionalProperties` of a schema never share + * properties, + * 5. All schemas in `mapping` are of the properties form, + * 6. Schemas in `mapping` never re-specify the `discriminator` property + * + * If an object returned from `JSON.parse` passes both [isSchema](#isSchema) and + * [isValidSchema](#isValidSchema), then it is a correct JSON Typedef schema. + * + * @param schema The schema to validate + * @param root The schema to consider as the "root" schema. If undefined, + * `schema` will be used as the root. This is usually what you want to do. + */ +export function isValidSchema(schema: Schema, root?: Schema): boolean { + if (root === undefined) { + root = schema; + } + + if (schema.definitions !== undefined) { + if (root !== schema) { + return false; + } + + for (const subSchema of Object.values(schema.definitions)) { + if (!isValidSchema(subSchema, root)) { + return false; + } + } + } + + if (isRefForm(schema)) { + if (!(schema.ref in (root.definitions || {}))) { + return false; + } + } + + if (isEnumForm(schema)) { + if (schema.enum.length === 0) { + return false; + } + + if (schema.enum.length !== new Set(schema.enum).size) { + return false; + } + } + + if (isElementsForm(schema)) { + return isValidSchema(schema.elements, root); + } + + if (isPropertiesForm(schema)) { + for (const subSchema of Object.values(schema.properties || {})) { + if (!isValidSchema(subSchema, root)) { + return false; + } + } + + for (const subSchema of Object.values(schema.optionalProperties || {})) { + if (!isValidSchema(subSchema, root)) { + return false; + } + } + + for (const key of Object.keys(schema.properties || {})) { + if (key in (schema.optionalProperties || {})) { + return false; + } + } + } + + if (isValuesForm(schema)) { + return isValidSchema(schema.values, root); + } + + if (isDiscriminatorForm(schema)) { + for (const subSchema of Object.values(schema.mapping)) { + if (!isValidSchema(subSchema, root) || !isPropertiesForm(subSchema)) { + return false; + } + + if (subSchema.nullable) { + return false; + } + + if (schema.discriminator in (subSchema.properties || {})) { + return false; + } + + if (schema.discriminator in (subSchema.optionalProperties || {})) { + return false; + } + } + } + + return true; +} + +// Index of valid form "signatures" -- i.e., combinations of the presence of the +// keywords (in order): +// +// ref type enum elements properties optionalProperties additionalProperties +// values discriminator mapping +// +// The keywords "definitions", "nullable", and "metadata" are not included here, +// because they would restrict nothing. +const VALID_FORMS = [ + // Empty form + [false, false, false, false, false, false, false, false, false, false], + // Ref form + [true, false, false, false, false, false, false, false, false, false], + // Type form + [false, true, false, false, false, false, false, false, false, false], + // Enum form + [false, false, true, false, false, false, false, false, false, false], + // Elements form + [false, false, false, true, false, false, false, false, false, false], + // Properties form -- properties or optional properties or both, and never + // additional properties on its own + [false, false, false, false, true, false, false, false, false, false], + [false, false, false, false, false, true, false, false, false, false], + [false, false, false, false, true, true, false, false, false, false], + [false, false, false, false, true, false, true, false, false, false], + [false, false, false, false, false, true, true, false, false, false], + [false, false, false, false, true, true, true, false, false, false], + // Values form + [false, false, false, false, false, false, false, true, false, false], + // Discriminator form + [false, false, false, false, false, false, false, false, true, true], +]; + +// List of valid values that the "type" keyboard may take on. +const VALID_TYPES = [ + "boolean", + "float32", + "float64", + "int8", + "uint8", + "int16", + "uint16", + "int32", + "uint32", + "string", + "timestamp", +]; + +/** + * `isSchema` checks whether some piece of JSON data has the shape of a JSON + * Typedef Schema. + * + * This function only looks at the "shape" of data: it just makes sure all + * property names and types are valid, and that the data takes on one of the + * eight JSON Typedef forms. + * + * If an object returned from `JSON.parse` passes both [isSchema](#isSchema) and + * [isValidSchema](#isValidSchema), then it is a correct JSON Typedef schema. + * + * @param data The data to check + */ +export function isSchema(data: unknown): data is Schema { + if (typeof data !== "object" || Array.isArray(data) || data === null) { + return false; + } + + // TypeScript does not let us coerce `{}` into `{ [index: string]: unknown }`. + // At the time of writing, it's unclear why this is the case, nor under what + // circumstances such an coercion would be wrong. + // + // So we work around the compiler here. + // deno-lint-ignore no-explicit-any + const obj: { [index: string]: unknown } = data as any; + + const { + definitions = undefined, + nullable = undefined, + metadata = undefined, + ref = undefined, + type = undefined, + enum: enum_ = undefined, + elements = undefined, + properties = undefined, + optionalProperties = undefined, + additionalProperties = undefined, + values = undefined, + discriminator = undefined, + mapping = undefined, + ...rest + } = obj; + + const formSignature = [ + ref !== undefined, + type !== undefined, + enum_ !== undefined, + elements !== undefined, + properties !== undefined, + optionalProperties !== undefined, + additionalProperties !== undefined, + values !== undefined, + discriminator !== undefined, + mapping !== undefined, + ]; + + let formOk = false; + for (const validForm of VALID_FORMS) { + formOk = formOk || + validForm.every((value, index) => value === formSignature[index]); + } + + if (!formOk) { + return false; + } + + if (definitions !== undefined) { + if ( + typeof definitions !== "object" || + Array.isArray(definitions) || + definitions === null + ) { + return false; + } + + for (const value of Object.values(definitions)) { + if (!isSchema(value)) { + return false; + } + } + } + + if (nullable !== undefined) { + if (typeof nullable !== "boolean") { + return false; + } + } + + if (metadata !== undefined) { + if ( + typeof metadata !== "object" || + Array.isArray(metadata) || + metadata === null + ) { + return false; + } + } + + if (ref !== undefined) { + if (typeof ref !== "string") { + return false; + } + } + + if (type !== undefined) { + if (typeof type !== "string" || !VALID_TYPES.includes(type)) { + return false; + } + } + + if (enum_ !== undefined) { + if (!Array.isArray(enum_)) { + return false; + } + + if (!enum_.every((elem) => typeof elem === "string")) { + return false; + } + } + + if (elements !== undefined) { + if (!isSchema(elements)) { + return false; + } + } + + if (properties !== undefined) { + if ( + typeof properties !== "object" || + Array.isArray(properties) || + properties === null + ) { + return false; + } + + for (const value of Object.values(properties)) { + if (!isSchema(value)) { + return false; + } + } + } + + if (optionalProperties !== undefined) { + if ( + typeof optionalProperties !== "object" || + Array.isArray(optionalProperties) || + optionalProperties === null + ) { + return false; + } + + for (const value of Object.values(optionalProperties)) { + if (!isSchema(value)) { + return false; + } + } + } + + if (additionalProperties !== undefined) { + if (typeof additionalProperties !== "boolean") { + return false; + } + } + + if (values !== undefined) { + if (!isSchema(values)) { + return false; + } + } + + if (discriminator !== undefined) { + if (typeof discriminator !== "string") { + return false; + } + } + + if (mapping !== undefined) { + if ( + typeof mapping !== "object" || + Array.isArray(mapping) || + mapping === null + ) { + return false; + } + + for (const value of Object.values(mapping)) { + if (!isSchema(value)) { + return false; + } + } + } + + if (Object.keys(rest).length !== 0) { + return false; + } + + return true; +} diff --git a/test_cases.test.ts b/test_cases.test.ts new file mode 100644 index 0000000..ec82f24 --- /dev/null +++ b/test_cases.test.ts @@ -0,0 +1,5860 @@ +interface TestCase { + description: string; + schema: unknown; + validSchema: boolean; + instance: unknown; + errors: TestCaseError[]; +} + +interface TestCaseError { + instancePath: string[]; + schemaPath: string[]; +} + +export const testCases: TestCase[] = [ + { + description: "null schema", + schema: null, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "boolean schema", + schema: true, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "integer schema", + schema: 1, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "float schema", + schema: 3.14, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "string schema", + schema: "foo", + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "array schema", + schema: [], + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "illegal keyword", + schema: { + foo: 123, + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "nullable not boolean", + schema: { + nullable: 123, + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "definitions not object", + schema: { + definitions: 123, + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "definition not object", + schema: { + definitions: { + foo: 123, + }, + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "non-root definitions", + schema: { + definitions: { + foo: { + definitions: { + x: {}, + }, + }, + }, + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "ref not string", + schema: { + ref: 123, + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "ref but no definitions", + schema: { + ref: "foo", + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "ref to non-existent definition", + schema: { + definitions: {}, + ref: "foo", + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "sub-schema ref to non-existent definition", + schema: { + definitions: {}, + elements: { + ref: "foo", + }, + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "type not string", + schema: { + type: 123, + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "type not valid string value", + schema: { + type: "foo", + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "enum not array", + schema: { + enum: 123, + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "enum empty array", + schema: { + enum: [], + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "enum not array of strings", + schema: { + enum: ["foo", 123, "baz"], + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "enum contains duplicates", + schema: { + enum: ["foo", "bar", "foo"], + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "elements not object", + schema: { + elements: 123, + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "elements not correct schema", + schema: { + elements: { + definitions: { + x: {}, + }, + }, + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "properties not object", + schema: { + properties: 123, + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "properties value not correct schema", + schema: { + properties: { + foo: { + definitions: { + x: {}, + }, + }, + }, + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "optionalProperties not object", + schema: { + optionalProperties: 123, + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "optionalProperties value not correct schema", + schema: { + optionalProperties: { + foo: { + definitions: { + x: {}, + }, + }, + }, + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "additionalProperties not boolean", + schema: { + properties: {}, + additionalProperties: 123, + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "properties shares keys with optionalProperties", + schema: { + properties: { + foo: {}, + bar: {}, + }, + optionalProperties: { + foo: {}, + baz: {}, + }, + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "values not object", + schema: { + values: 123, + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "values not correct schema", + schema: { + values: { + definitions: { + x: {}, + }, + }, + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "discriminator not string", + schema: { + discriminator: 123, + mapping: {}, + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "mapping not object", + schema: { + discriminator: "foo", + mapping: 123, + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "mapping value not correct schema", + schema: { + discriminator: "foo", + mapping: { + x: { + properties: {}, + definitions: { + x: {}, + }, + }, + }, + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "mapping value not of properties form", + schema: { + discriminator: "foo", + mapping: { + x: {}, + }, + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "mapping value has nullable set to true", + schema: { + discriminator: "foo", + mapping: { + x: { + nullable: true, + properties: { + bar: {}, + }, + }, + }, + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "discriminator shares keys with mapping properties", + schema: { + discriminator: "foo", + mapping: { + x: { + properties: { + foo: {}, + }, + }, + }, + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "discriminator shares keys with mapping optionalProperties", + schema: { + discriminator: "foo", + mapping: { + x: { + optionalProperties: { + foo: {}, + }, + }, + }, + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "invalid form - ref and type", + schema: { + definitions: { + foo: {}, + }, + ref: "foo", + type: "uint32", + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "invalid form - type and enum", + schema: { + type: "uint32", + enum: ["foo"], + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "invalid form - enum and elements", + schema: { + enum: ["foo"], + elements: {}, + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "invalid form - elements and properties", + schema: { + elements: {}, + properties: {}, + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "invalid form - elements and optionalProperties", + schema: { + elements: {}, + optionalProperties: {}, + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "invalid form - elements and additionalProperties", + schema: { + elements: {}, + additionalProperties: true, + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "invalid form - additionalProperties alone", + schema: { + additionalProperties: true, + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "invalid form - properties and values", + schema: { + properties: {}, + values: {}, + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "invalid form - values and discriminator", + schema: { + values: {}, + discriminator: "foo", + mapping: {}, + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "invalid form - discriminator alone", + schema: { + discriminator: "foo", + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "invalid form - mapping alone", + schema: { + mapping: {}, + }, + validSchema: false, + instance: undefined, + errors: [], + }, + { + description: "empty schema - null", + schema: {}, + validSchema: true, + instance: null, + errors: [], + }, + { + description: "empty schema - boolean", + schema: {}, + validSchema: true, + instance: true, + errors: [], + }, + { + description: "empty schema - integer", + schema: {}, + validSchema: true, + instance: 1, + errors: [], + }, + { + description: "empty schema - float", + schema: {}, + validSchema: true, + instance: 3.14, + errors: [], + }, + { + description: "empty schema - string", + schema: {}, + validSchema: true, + instance: "foo", + errors: [], + }, + { + description: "empty schema - array", + schema: {}, + validSchema: true, + instance: [], + errors: [], + }, + { + description: "empty schema - object", + schema: {}, + validSchema: true, + instance: {}, + errors: [], + }, + { + description: "empty nullable schema - null", + schema: { + nullable: true, + }, + validSchema: true, + instance: null, + errors: [], + }, + { + description: "empty nullable schema - object", + schema: { + nullable: true, + }, + validSchema: true, + instance: {}, + errors: [], + }, + { + description: "empty schema with metadata - null", + schema: { + metadata: {}, + }, + validSchema: true, + instance: null, + errors: [], + }, + { + description: "ref schema - ref to empty definition", + schema: { + definitions: { + foo: {}, + }, + ref: "foo", + }, + validSchema: true, + instance: true, + errors: [], + }, + { + description: "ref schema - nested ref", + schema: { + definitions: { + foo: { + ref: "bar", + }, + bar: {}, + }, + ref: "foo", + }, + validSchema: true, + instance: true, + errors: [], + }, + { + description: "ref schema - ref to type definition, ok", + schema: { + definitions: { + foo: { + type: "boolean", + }, + }, + ref: "foo", + }, + validSchema: true, + instance: true, + errors: [], + }, + { + description: "ref schema - ref to type definition, fail", + schema: { + definitions: { + foo: { + type: "boolean", + }, + }, + ref: "foo", + }, + validSchema: true, + instance: {}, + errors: [ + { + instancePath: [], + schemaPath: [ + "definitions", + "foo", + "type", + ], + }, + ], + }, + { + description: "nullable ref schema - ref to type definition, ok", + schema: { + definitions: { + foo: { + type: "boolean", + }, + }, + ref: "foo", + nullable: true, + }, + validSchema: true, + instance: true, + errors: [], + }, + { + description: + "nullable ref schema - ref to type definition, ok because null", + schema: { + definitions: { + foo: { + type: "boolean", + }, + }, + ref: "foo", + nullable: true, + }, + validSchema: true, + instance: null, + errors: [], + }, + { + description: "nullable ref schema - nullable: false ignored", + schema: { + definitions: { + foo: { + type: "boolean", + nullable: false, + }, + }, + ref: "foo", + nullable: true, + }, + validSchema: true, + instance: null, + errors: [], + }, + { + description: "ref schema - recursive schema, ok", + schema: { + definitions: { + root: { + elements: { + ref: "root", + }, + }, + }, + ref: "root", + }, + validSchema: true, + instance: [], + errors: [], + }, + { + description: "ref schema - recursive schema, bad", + schema: { + definitions: { + root: { + elements: { + ref: "root", + }, + }, + }, + ref: "root", + }, + validSchema: true, + instance: [ + [], + [ + [], + ], + [ + [ + [], + [ + "a", + ], + ], + ], + ], + errors: [ + { + instancePath: [ + "2", + "0", + "1", + "0", + ], + schemaPath: [ + "definitions", + "root", + "elements", + ], + }, + ], + }, + { + description: "boolean type schema - null", + schema: { + type: "boolean", + }, + validSchema: true, + instance: null, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "boolean type schema - boolean", + schema: { + type: "boolean", + }, + validSchema: true, + instance: true, + errors: [], + }, + { + description: "boolean type schema - integer", + schema: { + type: "boolean", + }, + validSchema: true, + instance: 1, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "boolean type schema - float", + schema: { + type: "boolean", + }, + validSchema: true, + instance: 3.14, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "boolean type schema - string", + schema: { + type: "boolean", + }, + validSchema: true, + instance: "foo", + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "boolean type schema - array", + schema: { + type: "boolean", + }, + validSchema: true, + instance: [], + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "boolean type schema - object", + schema: { + type: "boolean", + }, + validSchema: true, + instance: {}, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable boolean type schema - null", + schema: { + type: "boolean", + nullable: true, + }, + validSchema: true, + instance: null, + errors: [], + }, + { + description: "nullable boolean type schema - boolean", + schema: { + type: "boolean", + nullable: true, + }, + validSchema: true, + instance: true, + errors: [], + }, + { + description: "nullable boolean type schema - integer", + schema: { + type: "boolean", + nullable: true, + }, + validSchema: true, + instance: 1, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable boolean type schema - float", + schema: { + type: "boolean", + nullable: true, + }, + validSchema: true, + instance: 3.14, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable boolean type schema - string", + schema: { + type: "boolean", + nullable: true, + }, + validSchema: true, + instance: "foo", + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable boolean type schema - array", + schema: { + type: "boolean", + nullable: true, + }, + validSchema: true, + instance: [], + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable boolean type schema - object", + schema: { + type: "boolean", + nullable: true, + }, + validSchema: true, + instance: {}, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "float32 type schema - null", + schema: { + type: "float32", + }, + validSchema: true, + instance: null, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "float32 type schema - boolean", + schema: { + type: "float32", + }, + validSchema: true, + instance: true, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "float32 type schema - integer", + schema: { + type: "float32", + }, + validSchema: true, + instance: 1, + errors: [], + }, + { + description: "float32 type schema - float", + schema: { + type: "float32", + }, + validSchema: true, + instance: 3.14, + errors: [], + }, + { + description: "float32 type schema - string", + schema: { + type: "float32", + }, + validSchema: true, + instance: "foo", + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "float32 type schema - array", + schema: { + type: "float32", + }, + validSchema: true, + instance: [], + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "float32 type schema - object", + schema: { + type: "float32", + }, + validSchema: true, + instance: {}, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable float32 type schema - null", + schema: { + type: "float32", + nullable: true, + }, + validSchema: true, + instance: null, + errors: [], + }, + { + description: "nullable float32 type schema - boolean", + schema: { + type: "float32", + nullable: true, + }, + validSchema: true, + instance: true, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable float32 type schema - integer", + schema: { + type: "float32", + nullable: true, + }, + validSchema: true, + instance: 1, + errors: [], + }, + { + description: "nullable float32 type schema - float", + schema: { + type: "float32", + nullable: true, + }, + validSchema: true, + instance: 3.14, + errors: [], + }, + { + description: "nullable float32 type schema - string", + schema: { + type: "float32", + nullable: true, + }, + validSchema: true, + instance: "foo", + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable float32 type schema - array", + schema: { + type: "float32", + nullable: true, + }, + validSchema: true, + instance: [], + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable float32 type schema - object", + schema: { + type: "float32", + nullable: true, + }, + validSchema: true, + instance: {}, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "float64 type schema - null", + schema: { + type: "float64", + }, + validSchema: true, + instance: null, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "float64 type schema - boolean", + schema: { + type: "float64", + }, + validSchema: true, + instance: true, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "float64 type schema - integer", + schema: { + type: "float64", + }, + validSchema: true, + instance: 1, + errors: [], + }, + { + description: "float64 type schema - float", + schema: { + type: "float64", + }, + validSchema: true, + instance: 3.14, + errors: [], + }, + { + description: "float64 type schema - string", + schema: { + type: "float64", + }, + validSchema: true, + instance: "foo", + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "float64 type schema - array", + schema: { + type: "float64", + }, + validSchema: true, + instance: [], + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "float64 type schema - object", + schema: { + type: "float64", + }, + validSchema: true, + instance: {}, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable float64 type schema - null", + schema: { + type: "float64", + nullable: true, + }, + validSchema: true, + instance: null, + errors: [], + }, + { + description: "nullable float64 type schema - boolean", + schema: { + type: "float64", + nullable: true, + }, + validSchema: true, + instance: true, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable float64 type schema - integer", + schema: { + type: "float64", + nullable: true, + }, + validSchema: true, + instance: 1, + errors: [], + }, + { + description: "nullable float64 type schema - float", + schema: { + type: "float64", + nullable: true, + }, + validSchema: true, + instance: 3.14, + errors: [], + }, + { + description: "nullable float64 type schema - string", + schema: { + type: "float64", + nullable: true, + }, + validSchema: true, + instance: "foo", + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable float64 type schema - array", + schema: { + type: "float64", + nullable: true, + }, + validSchema: true, + instance: [], + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable float64 type schema - object", + schema: { + type: "float64", + nullable: true, + }, + validSchema: true, + instance: {}, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "int8 type schema - null", + schema: { + type: "int8", + }, + validSchema: true, + instance: null, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "int8 type schema - boolean", + schema: { + type: "int8", + }, + validSchema: true, + instance: true, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "int8 type schema - integer", + schema: { + type: "int8", + }, + validSchema: true, + instance: 1, + errors: [], + }, + { + description: "int8 type schema - float", + schema: { + type: "int8", + }, + validSchema: true, + instance: 3.14, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "int8 type schema - string", + schema: { + type: "int8", + }, + validSchema: true, + instance: "foo", + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "int8 type schema - array", + schema: { + type: "int8", + }, + validSchema: true, + instance: [], + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "int8 type schema - object", + schema: { + type: "int8", + }, + validSchema: true, + instance: {}, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable int8 type schema - null", + schema: { + type: "int8", + nullable: true, + }, + validSchema: true, + instance: null, + errors: [], + }, + { + description: "nullable int8 type schema - boolean", + schema: { + type: "int8", + nullable: true, + }, + validSchema: true, + instance: true, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable int8 type schema - integer", + schema: { + type: "int8", + nullable: true, + }, + validSchema: true, + instance: 1, + errors: [], + }, + { + description: "nullable int8 type schema - float", + schema: { + type: "int8", + nullable: true, + }, + validSchema: true, + instance: 3.14, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable int8 type schema - string", + schema: { + type: "int8", + nullable: true, + }, + validSchema: true, + instance: "foo", + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable int8 type schema - array", + schema: { + type: "int8", + nullable: true, + }, + validSchema: true, + instance: [], + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable int8 type schema - object", + schema: { + type: "int8", + nullable: true, + }, + validSchema: true, + instance: {}, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "int8 type schema - min value", + schema: { + type: "int8", + }, + validSchema: true, + instance: -128, + errors: [], + }, + { + description: "int8 type schema - max value", + schema: { + type: "int8", + }, + validSchema: true, + instance: 127, + errors: [], + }, + { + description: "int8 type schema - less than min", + schema: { + type: "int8", + }, + validSchema: true, + instance: -129, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "int8 type schema - more than max", + schema: { + type: "int8", + }, + validSchema: true, + instance: 128, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "uint8 type schema - null", + schema: { + type: "uint8", + }, + validSchema: true, + instance: null, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "uint8 type schema - boolean", + schema: { + type: "uint8", + }, + validSchema: true, + instance: true, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "uint8 type schema - integer", + schema: { + type: "uint8", + }, + validSchema: true, + instance: 1, + errors: [], + }, + { + description: "uint8 type schema - float", + schema: { + type: "uint8", + }, + validSchema: true, + instance: 3.14, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "uint8 type schema - string", + schema: { + type: "uint8", + }, + validSchema: true, + instance: "foo", + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "uint8 type schema - array", + schema: { + type: "uint8", + }, + validSchema: true, + instance: [], + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "uint8 type schema - object", + schema: { + type: "uint8", + }, + validSchema: true, + instance: {}, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable uint8 type schema - null", + schema: { + type: "uint8", + nullable: true, + }, + validSchema: true, + instance: null, + errors: [], + }, + { + description: "nullable uint8 type schema - boolean", + schema: { + type: "uint8", + nullable: true, + }, + validSchema: true, + instance: true, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable uint8 type schema - integer", + schema: { + type: "uint8", + nullable: true, + }, + validSchema: true, + instance: 1, + errors: [], + }, + { + description: "nullable uint8 type schema - float", + schema: { + type: "uint8", + nullable: true, + }, + validSchema: true, + instance: 3.14, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable uint8 type schema - string", + schema: { + type: "uint8", + nullable: true, + }, + validSchema: true, + instance: "foo", + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable uint8 type schema - array", + schema: { + type: "uint8", + nullable: true, + }, + validSchema: true, + instance: [], + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable uint8 type schema - object", + schema: { + type: "uint8", + nullable: true, + }, + validSchema: true, + instance: {}, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "uint8 type schema - min value", + schema: { + type: "uint8", + }, + validSchema: true, + instance: 0, + errors: [], + }, + { + description: "uint8 type schema - max value", + schema: { + type: "uint8", + }, + validSchema: true, + instance: 255, + errors: [], + }, + { + description: "uint8 type schema - less than min", + schema: { + type: "uint8", + }, + validSchema: true, + instance: -1, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "uint8 type schema - more than max", + schema: { + type: "uint8", + }, + validSchema: true, + instance: 256, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "int16 type schema - null", + schema: { + type: "int16", + }, + validSchema: true, + instance: null, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "int16 type schema - boolean", + schema: { + type: "int16", + }, + validSchema: true, + instance: true, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "int16 type schema - integer", + schema: { + type: "int16", + }, + validSchema: true, + instance: 1, + errors: [], + }, + { + description: "int16 type schema - float", + schema: { + type: "int16", + }, + validSchema: true, + instance: 3.14, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "int16 type schema - string", + schema: { + type: "int16", + }, + validSchema: true, + instance: "foo", + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "int16 type schema - array", + schema: { + type: "int16", + }, + validSchema: true, + instance: [], + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "int16 type schema - object", + schema: { + type: "int16", + }, + validSchema: true, + instance: {}, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable int16 type schema - null", + schema: { + type: "int16", + nullable: true, + }, + validSchema: true, + instance: null, + errors: [], + }, + { + description: "nullable int16 type schema - boolean", + schema: { + type: "int16", + nullable: true, + }, + validSchema: true, + instance: true, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable int16 type schema - integer", + schema: { + type: "int16", + nullable: true, + }, + validSchema: true, + instance: 1, + errors: [], + }, + { + description: "nullable int16 type schema - float", + schema: { + type: "int16", + nullable: true, + }, + validSchema: true, + instance: 3.14, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable int16 type schema - string", + schema: { + type: "int16", + nullable: true, + }, + validSchema: true, + instance: "foo", + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable int16 type schema - array", + schema: { + type: "int16", + nullable: true, + }, + validSchema: true, + instance: [], + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable int16 type schema - object", + schema: { + type: "int16", + nullable: true, + }, + validSchema: true, + instance: {}, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "int16 type schema - min value", + schema: { + type: "int16", + }, + validSchema: true, + instance: -32768, + errors: [], + }, + { + description: "int16 type schema - max value", + schema: { + type: "int16", + }, + validSchema: true, + instance: 32767, + errors: [], + }, + { + description: "int16 type schema - less than min", + schema: { + type: "int16", + }, + validSchema: true, + instance: -32769, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "int16 type schema - more than max", + schema: { + type: "int16", + }, + validSchema: true, + instance: 32768, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "uint16 type schema - null", + schema: { + type: "uint16", + }, + validSchema: true, + instance: null, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "uint16 type schema - boolean", + schema: { + type: "uint16", + }, + validSchema: true, + instance: true, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "uint16 type schema - integer", + schema: { + type: "uint16", + }, + validSchema: true, + instance: 1, + errors: [], + }, + { + description: "uint16 type schema - float", + schema: { + type: "uint16", + }, + validSchema: true, + instance: 3.14, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "uint16 type schema - string", + schema: { + type: "uint16", + }, + validSchema: true, + instance: "foo", + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "uint16 type schema - array", + schema: { + type: "uint16", + }, + validSchema: true, + instance: [], + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "uint16 type schema - object", + schema: { + type: "uint16", + }, + validSchema: true, + instance: {}, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable uint16 type schema - null", + schema: { + type: "uint16", + nullable: true, + }, + validSchema: true, + instance: null, + errors: [], + }, + { + description: "nullable uint16 type schema - boolean", + schema: { + type: "uint16", + nullable: true, + }, + validSchema: true, + instance: true, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable uint16 type schema - integer", + schema: { + type: "uint16", + nullable: true, + }, + validSchema: true, + instance: 1, + errors: [], + }, + { + description: "nullable uint16 type schema - float", + schema: { + type: "uint16", + nullable: true, + }, + validSchema: true, + instance: 3.14, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable uint16 type schema - string", + schema: { + type: "uint16", + nullable: true, + }, + validSchema: true, + instance: "foo", + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable uint16 type schema - array", + schema: { + type: "uint16", + nullable: true, + }, + validSchema: true, + instance: [], + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable uint16 type schema - object", + schema: { + type: "uint16", + nullable: true, + }, + validSchema: true, + instance: {}, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "uint16 type schema - min value", + schema: { + type: "uint16", + }, + validSchema: true, + instance: 0, + errors: [], + }, + { + description: "uint16 type schema - max value", + schema: { + type: "uint16", + }, + validSchema: true, + instance: 65535, + errors: [], + }, + { + description: "uint16 type schema - less than min", + schema: { + type: "uint16", + }, + validSchema: true, + instance: -1, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "uint16 type schema - more than max", + schema: { + type: "uint16", + }, + validSchema: true, + instance: 65536, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "int32 type schema - null", + schema: { + type: "int32", + }, + validSchema: true, + instance: null, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "int32 type schema - boolean", + schema: { + type: "int32", + }, + validSchema: true, + instance: true, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "int32 type schema - integer", + schema: { + type: "int32", + }, + validSchema: true, + instance: 1, + errors: [], + }, + { + description: "int32 type schema - float", + schema: { + type: "int32", + }, + validSchema: true, + instance: 3.14, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "int32 type schema - string", + schema: { + type: "int32", + }, + validSchema: true, + instance: "foo", + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "int32 type schema - array", + schema: { + type: "int32", + }, + validSchema: true, + instance: [], + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "int32 type schema - object", + schema: { + type: "int32", + }, + validSchema: true, + instance: {}, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable int32 type schema - null", + schema: { + type: "int32", + nullable: true, + }, + validSchema: true, + instance: null, + errors: [], + }, + { + description: "nullable int32 type schema - boolean", + schema: { + type: "int32", + nullable: true, + }, + validSchema: true, + instance: true, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable int32 type schema - integer", + schema: { + type: "int32", + nullable: true, + }, + validSchema: true, + instance: 1, + errors: [], + }, + { + description: "nullable int32 type schema - float", + schema: { + type: "int32", + nullable: true, + }, + validSchema: true, + instance: 3.14, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable int32 type schema - string", + schema: { + type: "int32", + nullable: true, + }, + validSchema: true, + instance: "foo", + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable int32 type schema - array", + schema: { + type: "int32", + nullable: true, + }, + validSchema: true, + instance: [], + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable int32 type schema - object", + schema: { + type: "int32", + nullable: true, + }, + validSchema: true, + instance: {}, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "int32 type schema - min value", + schema: { + type: "int32", + }, + validSchema: true, + instance: -2147483648, + errors: [], + }, + { + description: "int32 type schema - max value", + schema: { + type: "int32", + }, + validSchema: true, + instance: 2147483647, + errors: [], + }, + { + description: "int32 type schema - less than min", + schema: { + type: "int32", + }, + validSchema: true, + instance: -2147483649, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "int32 type schema - more than max", + schema: { + type: "int32", + }, + validSchema: true, + instance: 2147483648, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "uint32 type schema - null", + schema: { + type: "uint32", + }, + validSchema: true, + instance: null, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "uint32 type schema - boolean", + schema: { + type: "uint32", + }, + validSchema: true, + instance: true, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "uint32 type schema - integer", + schema: { + type: "uint32", + }, + validSchema: true, + instance: 1, + errors: [], + }, + { + description: "uint32 type schema - float", + schema: { + type: "uint32", + }, + validSchema: true, + instance: 3.14, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "uint32 type schema - string", + schema: { + type: "uint32", + }, + validSchema: true, + instance: "foo", + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "uint32 type schema - array", + schema: { + type: "uint32", + }, + validSchema: true, + instance: [], + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "uint32 type schema - object", + schema: { + type: "uint32", + }, + validSchema: true, + instance: {}, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable uint32 type schema - null", + schema: { + type: "uint32", + nullable: true, + }, + validSchema: true, + instance: null, + errors: [], + }, + { + description: "nullable uint32 type schema - boolean", + schema: { + type: "uint32", + nullable: true, + }, + validSchema: true, + instance: true, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable uint32 type schema - integer", + schema: { + type: "uint32", + nullable: true, + }, + validSchema: true, + instance: 1, + errors: [], + }, + { + description: "nullable uint32 type schema - float", + schema: { + type: "uint32", + nullable: true, + }, + validSchema: true, + instance: 3.14, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable uint32 type schema - string", + schema: { + type: "uint32", + nullable: true, + }, + validSchema: true, + instance: "foo", + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable uint32 type schema - array", + schema: { + type: "uint32", + nullable: true, + }, + validSchema: true, + instance: [], + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable uint32 type schema - object", + schema: { + type: "uint32", + nullable: true, + }, + validSchema: true, + instance: {}, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "uint32 type schema - min value", + schema: { + type: "uint32", + }, + validSchema: true, + instance: 0, + errors: [], + }, + { + description: "uint32 type schema - max value", + schema: { + type: "uint32", + }, + validSchema: true, + instance: 4294967295, + errors: [], + }, + { + description: "uint32 type schema - less than min", + schema: { + type: "uint32", + }, + validSchema: true, + instance: -1, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "uint32 type schema - more than max", + schema: { + type: "uint32", + }, + validSchema: true, + instance: 4294967296, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "string type schema - null", + schema: { + type: "string", + }, + validSchema: true, + instance: null, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "string type schema - boolean", + schema: { + type: "string", + }, + validSchema: true, + instance: true, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "string type schema - integer", + schema: { + type: "string", + }, + validSchema: true, + instance: 1, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "string type schema - float", + schema: { + type: "string", + }, + validSchema: true, + instance: 3.14, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "string type schema - string", + schema: { + type: "string", + }, + validSchema: true, + instance: "foo", + errors: [], + }, + { + description: "string type schema - array", + schema: { + type: "string", + }, + validSchema: true, + instance: [], + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "string type schema - object", + schema: { + type: "string", + }, + validSchema: true, + instance: {}, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable string type schema - null", + schema: { + type: "string", + nullable: true, + }, + validSchema: true, + instance: null, + errors: [], + }, + { + description: "nullable string type schema - boolean", + schema: { + type: "string", + nullable: true, + }, + validSchema: true, + instance: true, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable string type schema - integer", + schema: { + type: "string", + nullable: true, + }, + validSchema: true, + instance: 1, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable string type schema - float", + schema: { + type: "string", + nullable: true, + }, + validSchema: true, + instance: 3.14, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable string type schema - string", + schema: { + type: "string", + nullable: true, + }, + validSchema: true, + instance: "foo", + errors: [], + }, + { + description: "nullable string type schema - array", + schema: { + type: "string", + nullable: true, + }, + validSchema: true, + instance: [], + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable string type schema - object", + schema: { + type: "string", + nullable: true, + }, + validSchema: true, + instance: {}, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "timestamp type schema - null", + schema: { + type: "timestamp", + }, + validSchema: true, + instance: null, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "timestamp type schema - boolean", + schema: { + type: "timestamp", + }, + validSchema: true, + instance: true, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "timestamp type schema - integer", + schema: { + type: "timestamp", + }, + validSchema: true, + instance: 1, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "timestamp type schema - float", + schema: { + type: "timestamp", + }, + validSchema: true, + instance: 3.14, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "timestamp type schema - string", + schema: { + type: "timestamp", + }, + validSchema: true, + instance: "foo", + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "timestamp type schema - array", + schema: { + type: "timestamp", + }, + validSchema: true, + instance: [], + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "timestamp type schema - object", + schema: { + type: "timestamp", + }, + validSchema: true, + instance: {}, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable timestamp type schema - null", + schema: { + type: "timestamp", + nullable: true, + }, + validSchema: true, + instance: null, + errors: [], + }, + { + description: "nullable timestamp type schema - boolean", + schema: { + type: "timestamp", + nullable: true, + }, + validSchema: true, + instance: true, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable timestamp type schema - integer", + schema: { + type: "timestamp", + nullable: true, + }, + validSchema: true, + instance: 1, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable timestamp type schema - float", + schema: { + type: "timestamp", + nullable: true, + }, + validSchema: true, + instance: 3.14, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable timestamp type schema - string", + schema: { + type: "timestamp", + nullable: true, + }, + validSchema: true, + instance: "foo", + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable timestamp type schema - array", + schema: { + type: "timestamp", + nullable: true, + }, + validSchema: true, + instance: [], + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "nullable timestamp type schema - object", + schema: { + type: "timestamp", + nullable: true, + }, + validSchema: true, + instance: {}, + errors: [ + { + instancePath: [], + schemaPath: [ + "type", + ], + }, + ], + }, + { + description: "timestamp type schema - 1985-04-12T23:20:50.52Z", + schema: { + type: "timestamp", + }, + validSchema: true, + instance: "1985-04-12T23:20:50.52Z", + errors: [], + }, + { + description: "timestamp type schema - 1996-12-19T16:39:57-08:00", + schema: { + type: "timestamp", + }, + validSchema: true, + instance: "1996-12-19T16:39:57-08:00", + errors: [], + }, + { + description: "timestamp type schema - 1990-12-31T23:59:60Z", + schema: { + type: "timestamp", + }, + validSchema: true, + instance: "1990-12-31T23:59:60Z", + errors: [], + }, + { + description: "timestamp type schema - 1990-12-31T15:59:60-08:00", + schema: { + type: "timestamp", + }, + validSchema: true, + instance: "1990-12-31T15:59:60-08:00", + errors: [], + }, + { + description: "timestamp type schema - 1937-01-01T12:00:27.87+00:20", + schema: { + type: "timestamp", + }, + validSchema: true, + instance: "1937-01-01T12:00:27.87+00:20", + errors: [], + }, + { + description: "enum schema - null", + schema: { + enum: [ + "foo", + "bar", + "baz", + ], + }, + validSchema: true, + instance: null, + errors: [ + { + instancePath: [], + schemaPath: [ + "enum", + ], + }, + ], + }, + { + description: "enum schema - boolean", + schema: { + enum: [ + "foo", + "bar", + "baz", + ], + }, + validSchema: true, + instance: true, + errors: [ + { + instancePath: [], + schemaPath: [ + "enum", + ], + }, + ], + }, + { + description: "enum schema - integer", + schema: { + enum: [ + "foo", + "bar", + "baz", + ], + }, + validSchema: true, + instance: 1, + errors: [ + { + instancePath: [], + schemaPath: [ + "enum", + ], + }, + ], + }, + { + description: "enum schema - float", + schema: { + enum: [ + "foo", + "bar", + "baz", + ], + }, + validSchema: true, + instance: 3.14, + errors: [ + { + instancePath: [], + schemaPath: [ + "enum", + ], + }, + ], + }, + { + description: "enum schema - string", + schema: { + enum: [ + "foo", + "bar", + "baz", + ], + }, + validSchema: true, + instance: "foo", + errors: [], + }, + { + description: "enum schema - array", + schema: { + enum: [ + "foo", + "bar", + "baz", + ], + }, + validSchema: true, + instance: [], + errors: [ + { + instancePath: [], + schemaPath: [ + "enum", + ], + }, + ], + }, + { + description: "enum schema - object", + schema: { + enum: [ + "foo", + "bar", + "baz", + ], + }, + validSchema: true, + instance: {}, + errors: [ + { + instancePath: [], + schemaPath: [ + "enum", + ], + }, + ], + }, + { + description: "nullable enum schema - null", + schema: { + enum: [ + "foo", + "bar", + "baz", + ], + nullable: true, + }, + validSchema: true, + instance: null, + errors: [], + }, + { + description: "nullable enum schema - boolean", + schema: { + enum: [ + "foo", + "bar", + "baz", + ], + nullable: true, + }, + validSchema: true, + instance: true, + errors: [ + { + instancePath: [], + schemaPath: [ + "enum", + ], + }, + ], + }, + { + description: "nullable enum schema - integer", + schema: { + enum: [ + "foo", + "bar", + "baz", + ], + nullable: true, + }, + validSchema: true, + instance: 1, + errors: [ + { + instancePath: [], + schemaPath: [ + "enum", + ], + }, + ], + }, + { + description: "nullable enum schema - float", + schema: { + enum: [ + "foo", + "bar", + "baz", + ], + nullable: true, + }, + validSchema: true, + instance: 3.14, + errors: [ + { + instancePath: [], + schemaPath: [ + "enum", + ], + }, + ], + }, + { + description: "nullable enum schema - string", + schema: { + enum: [ + "foo", + "bar", + "baz", + ], + nullable: true, + }, + validSchema: true, + instance: "foo", + errors: [], + }, + { + description: "nullable enum schema - array", + schema: { + enum: [ + "foo", + "bar", + "baz", + ], + nullable: true, + }, + validSchema: true, + instance: [], + errors: [ + { + instancePath: [], + schemaPath: [ + "enum", + ], + }, + ], + }, + { + description: "nullable enum schema - object", + schema: { + enum: [ + "foo", + "bar", + "baz", + ], + nullable: true, + }, + validSchema: true, + instance: {}, + errors: [ + { + instancePath: [], + schemaPath: [ + "enum", + ], + }, + ], + }, + { + description: "enum schema - value not in enum", + schema: { + enum: [ + "foo", + "bar", + "baz", + ], + nullable: true, + }, + validSchema: true, + instance: "quux", + errors: [ + { + instancePath: [], + schemaPath: [ + "enum", + ], + }, + ], + }, + { + description: "enum schema - ok", + schema: { + enum: [ + "foo", + "bar", + "baz", + ], + nullable: true, + }, + validSchema: true, + instance: "bar", + errors: [], + }, + { + description: "elements schema - null", + schema: { + elements: { + type: "string", + }, + }, + validSchema: true, + instance: null, + errors: [ + { + instancePath: [], + schemaPath: [ + "elements", + ], + }, + ], + }, + { + description: "elements schema - boolean", + schema: { + elements: { + type: "string", + }, + }, + validSchema: true, + instance: true, + errors: [ + { + instancePath: [], + schemaPath: [ + "elements", + ], + }, + ], + }, + { + description: "elements schema - float", + schema: { + elements: { + type: "string", + }, + }, + validSchema: true, + instance: 3.14, + errors: [ + { + instancePath: [], + schemaPath: [ + "elements", + ], + }, + ], + }, + { + description: "elements schema - integer", + schema: { + elements: { + type: "string", + }, + }, + validSchema: true, + instance: 1, + errors: [ + { + instancePath: [], + schemaPath: [ + "elements", + ], + }, + ], + }, + { + description: "elements schema - string", + schema: { + elements: { + type: "string", + }, + }, + validSchema: true, + instance: "foo", + errors: [ + { + instancePath: [], + schemaPath: [ + "elements", + ], + }, + ], + }, + { + description: "elements schema - object", + schema: { + elements: { + type: "string", + }, + }, + validSchema: true, + instance: {}, + errors: [ + { + instancePath: [], + schemaPath: [ + "elements", + ], + }, + ], + }, + { + description: "nullable elements schema - null", + schema: { + elements: { + type: "string", + }, + nullable: true, + }, + validSchema: true, + instance: null, + errors: [], + }, + { + description: "nullable elements schema - boolean", + schema: { + elements: { + type: "string", + }, + nullable: true, + }, + validSchema: true, + instance: true, + errors: [ + { + instancePath: [], + schemaPath: [ + "elements", + ], + }, + ], + }, + { + description: "nullable elements schema - float", + schema: { + elements: { + type: "string", + }, + nullable: true, + }, + validSchema: true, + instance: 3.14, + errors: [ + { + instancePath: [], + schemaPath: [ + "elements", + ], + }, + ], + }, + { + description: "nullable elements schema - integer", + schema: { + elements: { + type: "string", + }, + nullable: true, + }, + validSchema: true, + instance: 1, + errors: [ + { + instancePath: [], + schemaPath: [ + "elements", + ], + }, + ], + }, + { + description: "nullable elements schema - string", + schema: { + elements: { + type: "string", + }, + nullable: true, + }, + validSchema: true, + instance: "foo", + errors: [ + { + instancePath: [], + schemaPath: [ + "elements", + ], + }, + ], + }, + { + description: "nullable elements schema - object", + schema: { + elements: { + type: "string", + }, + nullable: true, + }, + validSchema: true, + instance: {}, + errors: [ + { + instancePath: [], + schemaPath: [ + "elements", + ], + }, + ], + }, + { + description: "elements schema - empty array", + schema: { + elements: { + type: "string", + }, + }, + validSchema: true, + instance: [], + errors: [], + }, + { + description: "elements schema - all values ok", + schema: { + elements: { + type: "string", + }, + }, + validSchema: true, + instance: [ + "foo", + "bar", + "baz", + ], + errors: [], + }, + { + description: "elements schema - some values bad", + schema: { + elements: { + type: "string", + }, + }, + validSchema: true, + instance: [ + "foo", + null, + null, + ], + errors: [ + { + instancePath: [ + "1", + ], + schemaPath: [ + "elements", + "type", + ], + }, + { + instancePath: [ + "2", + ], + schemaPath: [ + "elements", + "type", + ], + }, + ], + }, + { + description: "elements schema - all values bad", + schema: { + elements: { + type: "string", + }, + }, + validSchema: true, + instance: [ + null, + null, + null, + ], + errors: [ + { + instancePath: [ + "0", + ], + schemaPath: [ + "elements", + "type", + ], + }, + { + instancePath: [ + "1", + ], + schemaPath: [ + "elements", + "type", + ], + }, + { + instancePath: [ + "2", + ], + schemaPath: [ + "elements", + "type", + ], + }, + ], + }, + { + description: "elements schema - nested elements, ok", + schema: { + elements: { + elements: { + type: "string", + }, + }, + }, + validSchema: true, + instance: [ + [], + [ + "foo", + ], + [ + "foo", + "bar", + "baz", + ], + ], + errors: [], + }, + { + description: "elements schema - nested elements, bad", + schema: { + elements: { + elements: { + type: "string", + }, + }, + }, + validSchema: true, + instance: [ + [ + null, + ], + [ + "foo", + ], + [ + "foo", + null, + "baz", + ], + null, + ], + errors: [ + { + instancePath: [ + "0", + "0", + ], + schemaPath: [ + "elements", + "elements", + "type", + ], + }, + { + instancePath: [ + "2", + "1", + ], + schemaPath: [ + "elements", + "elements", + "type", + ], + }, + { + instancePath: [ + "3", + ], + schemaPath: [ + "elements", + "elements", + ], + }, + ], + }, + { + description: "properties schema - null", + schema: { + properties: { + foo: { + type: "string", + }, + }, + }, + validSchema: true, + instance: null, + errors: [ + { + instancePath: [], + schemaPath: [ + "properties", + ], + }, + ], + }, + { + description: "properties schema - boolean", + schema: { + properties: { + foo: { + type: "string", + }, + }, + }, + validSchema: true, + instance: true, + errors: [ + { + instancePath: [], + schemaPath: [ + "properties", + ], + }, + ], + }, + { + description: "properties schema - float", + schema: { + properties: { + foo: { + type: "string", + }, + }, + }, + validSchema: true, + instance: 3.14, + errors: [ + { + instancePath: [], + schemaPath: [ + "properties", + ], + }, + ], + }, + { + description: "properties schema - integer", + schema: { + properties: { + foo: { + type: "string", + }, + }, + }, + validSchema: true, + instance: 1, + errors: [ + { + instancePath: [], + schemaPath: [ + "properties", + ], + }, + ], + }, + { + description: "properties schema - string", + schema: { + properties: { + foo: { + type: "string", + }, + }, + }, + validSchema: true, + instance: "foo", + errors: [ + { + instancePath: [], + schemaPath: [ + "properties", + ], + }, + ], + }, + { + description: "properties schema - array", + schema: { + properties: { + foo: { + type: "string", + }, + }, + }, + validSchema: true, + instance: [], + errors: [ + { + instancePath: [], + schemaPath: [ + "properties", + ], + }, + ], + }, + { + description: "nullable properties schema - null", + schema: { + properties: { + foo: { + type: "string", + }, + }, + nullable: true, + }, + validSchema: true, + instance: null, + errors: [], + }, + { + description: "nullable properties schema - boolean", + schema: { + properties: { + foo: { + type: "string", + }, + }, + nullable: true, + }, + validSchema: true, + instance: true, + errors: [ + { + instancePath: [], + schemaPath: [ + "properties", + ], + }, + ], + }, + { + description: "nullable properties schema - float", + schema: { + properties: { + foo: { + type: "string", + }, + }, + nullable: true, + }, + validSchema: true, + instance: 3.14, + errors: [ + { + instancePath: [], + schemaPath: [ + "properties", + ], + }, + ], + }, + { + description: "nullable properties schema - integer", + schema: { + properties: { + foo: { + type: "string", + }, + }, + nullable: true, + }, + validSchema: true, + instance: 1, + errors: [ + { + instancePath: [], + schemaPath: [ + "properties", + ], + }, + ], + }, + { + description: "nullable properties schema - string", + schema: { + properties: { + foo: { + type: "string", + }, + }, + nullable: true, + }, + validSchema: true, + instance: "foo", + errors: [ + { + instancePath: [], + schemaPath: [ + "properties", + ], + }, + ], + }, + { + description: "nullable properties schema - array", + schema: { + properties: { + foo: { + type: "string", + }, + }, + nullable: true, + }, + validSchema: true, + instance: [], + errors: [ + { + instancePath: [], + schemaPath: [ + "properties", + ], + }, + ], + }, + { + description: "properties and optionalProperties schema - null", + schema: { + properties: { + foo: { + type: "string", + }, + }, + optionalProperties: { + bar: { + type: "string", + }, + }, + }, + validSchema: true, + instance: null, + errors: [ + { + instancePath: [], + schemaPath: [ + "properties", + ], + }, + ], + }, + { + description: "properties and optionalProperties schema - boolean", + schema: { + properties: { + foo: { + type: "string", + }, + }, + optionalProperties: { + bar: { + type: "string", + }, + }, + }, + validSchema: true, + instance: true, + errors: [ + { + instancePath: [], + schemaPath: [ + "properties", + ], + }, + ], + }, + { + description: "properties and optionalProperties schema - float", + schema: { + properties: { + foo: { + type: "string", + }, + }, + optionalProperties: { + bar: { + type: "string", + }, + }, + }, + validSchema: true, + instance: 3.14, + errors: [ + { + instancePath: [], + schemaPath: [ + "properties", + ], + }, + ], + }, + { + description: "properties and optionalProperties schema - integer", + schema: { + properties: { + foo: { + type: "string", + }, + }, + optionalProperties: { + bar: { + type: "string", + }, + }, + }, + validSchema: true, + instance: 1, + errors: [ + { + instancePath: [], + schemaPath: [ + "properties", + ], + }, + ], + }, + { + description: "properties and optionalProperties schema - string", + schema: { + properties: { + foo: { + type: "string", + }, + }, + optionalProperties: { + bar: { + type: "string", + }, + }, + }, + validSchema: true, + instance: "foo", + errors: [ + { + instancePath: [], + schemaPath: [ + "properties", + ], + }, + ], + }, + { + description: "properties and optionalProperties schema - array", + schema: { + properties: { + foo: { + type: "string", + }, + }, + optionalProperties: { + bar: { + type: "string", + }, + }, + }, + validSchema: true, + instance: [], + errors: [ + { + instancePath: [], + schemaPath: [ + "properties", + ], + }, + ], + }, + { + description: "optionalProperties schema - null", + schema: { + optionalProperties: { + foo: { + type: "string", + }, + }, + }, + validSchema: true, + instance: null, + errors: [ + { + instancePath: [], + schemaPath: [ + "optionalProperties", + ], + }, + ], + }, + { + description: "optionalProperties schema - boolean", + schema: { + optionalProperties: { + foo: { + type: "string", + }, + }, + }, + validSchema: true, + instance: true, + errors: [ + { + instancePath: [], + schemaPath: [ + "optionalProperties", + ], + }, + ], + }, + { + description: "optionalProperties schema - float", + schema: { + optionalProperties: { + foo: { + type: "string", + }, + }, + }, + validSchema: true, + instance: 3.14, + errors: [ + { + instancePath: [], + schemaPath: [ + "optionalProperties", + ], + }, + ], + }, + { + description: "optionalProperties schema - integer", + schema: { + optionalProperties: { + foo: { + type: "string", + }, + }, + }, + validSchema: true, + instance: 1, + errors: [ + { + instancePath: [], + schemaPath: [ + "optionalProperties", + ], + }, + ], + }, + { + description: "optionalProperties schema - string", + schema: { + optionalProperties: { + foo: { + type: "string", + }, + }, + }, + validSchema: true, + instance: "foo", + errors: [ + { + instancePath: [], + schemaPath: [ + "optionalProperties", + ], + }, + ], + }, + { + description: "optionalProperties schema - array", + schema: { + optionalProperties: { + foo: { + type: "string", + }, + }, + }, + validSchema: true, + instance: [], + errors: [ + { + instancePath: [], + schemaPath: [ + "optionalProperties", + ], + }, + ], + }, + { + description: "strict properties - ok", + schema: { + properties: { + foo: { + type: "string", + }, + }, + }, + validSchema: true, + instance: { + foo: "foo", + }, + errors: [], + }, + { + description: "strict properties - bad wrong type", + schema: { + properties: { + foo: { + type: "string", + }, + }, + }, + validSchema: true, + instance: { + foo: 123, + }, + errors: [ + { + instancePath: [ + "foo", + ], + schemaPath: [ + "properties", + "foo", + "type", + ], + }, + ], + }, + { + description: "strict properties - bad missing property", + schema: { + properties: { + foo: { + type: "string", + }, + }, + }, + validSchema: true, + instance: {}, + errors: [ + { + instancePath: [], + schemaPath: [ + "properties", + "foo", + ], + }, + ], + }, + { + description: "strict properties - bad additional property", + schema: { + properties: { + foo: { + type: "string", + }, + }, + }, + validSchema: true, + instance: { + foo: "foo", + bar: "bar", + }, + errors: [ + { + instancePath: [ + "bar", + ], + schemaPath: [], + }, + ], + }, + { + description: + "strict properties - bad additional property with explicit additionalProperties: false", + schema: { + properties: { + foo: { + type: "string", + }, + }, + additionalProperties: false, + }, + validSchema: true, + instance: { + foo: "foo", + bar: "bar", + }, + errors: [ + { + instancePath: [ + "bar", + ], + schemaPath: [], + }, + ], + }, + { + description: "non-strict properties - ok", + schema: { + properties: { + foo: { + type: "string", + }, + }, + additionalProperties: true, + }, + validSchema: true, + instance: { + foo: "foo", + }, + errors: [], + }, + { + description: "non-strict properties - bad wrong type", + schema: { + properties: { + foo: { + type: "string", + }, + }, + additionalProperties: true, + }, + validSchema: true, + instance: { + foo: 123, + }, + errors: [ + { + instancePath: [ + "foo", + ], + schemaPath: [ + "properties", + "foo", + "type", + ], + }, + ], + }, + { + description: "non-strict properties - bad missing property", + schema: { + properties: { + foo: { + type: "string", + }, + }, + additionalProperties: true, + }, + validSchema: true, + instance: {}, + errors: [ + { + instancePath: [], + schemaPath: [ + "properties", + "foo", + ], + }, + ], + }, + { + description: "non-strict properties - ok additional property", + schema: { + properties: { + foo: { + type: "string", + }, + }, + additionalProperties: true, + }, + validSchema: true, + instance: { + foo: "foo", + bar: "bar", + }, + errors: [], + }, + { + description: "strict optionalProperties - ok", + schema: { + optionalProperties: { + foo: { + type: "string", + }, + }, + }, + validSchema: true, + instance: { + foo: "foo", + }, + errors: [], + }, + { + description: "strict optionalProperties - bad wrong type", + schema: { + optionalProperties: { + foo: { + type: "string", + }, + }, + }, + validSchema: true, + instance: { + foo: 123, + }, + errors: [ + { + instancePath: [ + "foo", + ], + schemaPath: [ + "optionalProperties", + "foo", + "type", + ], + }, + ], + }, + { + description: "strict optionalProperties - ok missing property", + schema: { + optionalProperties: { + foo: { + type: "string", + }, + }, + }, + validSchema: true, + instance: {}, + errors: [], + }, + { + description: "strict optionalProperties - bad additional property", + schema: { + optionalProperties: { + foo: { + type: "string", + }, + }, + }, + validSchema: true, + instance: { + foo: "foo", + bar: "bar", + }, + errors: [ + { + instancePath: [ + "bar", + ], + schemaPath: [], + }, + ], + }, + { + description: + "strict optionalProperties - bad additional property with explicit additionalProperties: false", + schema: { + optionalProperties: { + foo: { + type: "string", + }, + }, + additionalProperties: false, + }, + validSchema: true, + instance: { + foo: "foo", + bar: "bar", + }, + errors: [ + { + instancePath: [ + "bar", + ], + schemaPath: [], + }, + ], + }, + { + description: "non-strict optionalProperties - ok", + schema: { + optionalProperties: { + foo: { + type: "string", + }, + }, + additionalProperties: true, + }, + validSchema: true, + instance: { + foo: "foo", + }, + errors: [], + }, + { + description: "non-strict optionalProperties - bad wrong type", + schema: { + optionalProperties: { + foo: { + type: "string", + }, + }, + additionalProperties: true, + }, + validSchema: true, + instance: { + foo: 123, + }, + errors: [ + { + instancePath: [ + "foo", + ], + schemaPath: [ + "optionalProperties", + "foo", + "type", + ], + }, + ], + }, + { + description: "non-strict optionalProperties - ok missing property", + schema: { + optionalProperties: { + foo: { + type: "string", + }, + }, + additionalProperties: true, + }, + validSchema: true, + instance: {}, + errors: [], + }, + { + description: "non-strict optionalProperties - ok additional property", + schema: { + optionalProperties: { + foo: { + type: "string", + }, + }, + additionalProperties: true, + }, + validSchema: true, + instance: { + foo: "foo", + bar: "bar", + }, + errors: [], + }, + { + description: "strict mixed properties and optionalProperties - ok", + schema: { + properties: { + foo: { + type: "string", + }, + }, + optionalProperties: { + bar: { + type: "string", + }, + }, + }, + validSchema: true, + instance: { + foo: "foo", + bar: "bar", + }, + errors: [], + }, + { + description: "strict mixed properties and optionalProperties - bad", + schema: { + properties: { + foo: { + type: "string", + }, + }, + optionalProperties: { + bar: { + type: "string", + }, + }, + }, + validSchema: true, + instance: { + foo: 123, + bar: 123, + }, + errors: [ + { + instancePath: [ + "foo", + ], + schemaPath: [ + "properties", + "foo", + "type", + ], + }, + { + instancePath: [ + "bar", + ], + schemaPath: [ + "optionalProperties", + "bar", + "type", + ], + }, + ], + }, + { + description: + "strict mixed properties and optionalProperties - bad additional property", + schema: { + properties: { + foo: { + type: "string", + }, + }, + optionalProperties: { + bar: { + type: "string", + }, + }, + }, + validSchema: true, + instance: { + foo: "foo", + bar: "bar", + baz: "baz", + }, + errors: [ + { + instancePath: [ + "baz", + ], + schemaPath: [], + }, + ], + }, + { + description: "values schema - null", + schema: { + values: { + type: "string", + }, + }, + validSchema: true, + instance: null, + errors: [ + { + instancePath: [], + schemaPath: [ + "values", + ], + }, + ], + }, + { + description: "values schema - boolean", + schema: { + values: { + type: "string", + }, + }, + validSchema: true, + instance: true, + errors: [ + { + instancePath: [], + schemaPath: [ + "values", + ], + }, + ], + }, + { + description: "values schema - float", + schema: { + values: { + type: "string", + }, + }, + validSchema: true, + instance: 3.14, + errors: [ + { + instancePath: [], + schemaPath: [ + "values", + ], + }, + ], + }, + { + description: "values schema - integer", + schema: { + values: { + type: "string", + }, + }, + validSchema: true, + instance: 1, + errors: [ + { + instancePath: [], + schemaPath: [ + "values", + ], + }, + ], + }, + { + description: "values schema - string", + schema: { + values: { + type: "string", + }, + }, + validSchema: true, + instance: "foo", + errors: [ + { + instancePath: [], + schemaPath: [ + "values", + ], + }, + ], + }, + { + description: "values schema - array", + schema: { + values: { + type: "string", + }, + }, + validSchema: true, + instance: [], + errors: [ + { + instancePath: [], + schemaPath: [ + "values", + ], + }, + ], + }, + { + description: "nullable values schema - null", + schema: { + values: { + type: "string", + }, + nullable: true, + }, + validSchema: true, + instance: null, + errors: [], + }, + { + description: "nullable values schema - boolean", + schema: { + values: { + type: "string", + }, + nullable: true, + }, + validSchema: true, + instance: true, + errors: [ + { + instancePath: [], + schemaPath: [ + "values", + ], + }, + ], + }, + { + description: "nullable values schema - float", + schema: { + values: { + type: "string", + }, + nullable: true, + }, + validSchema: true, + instance: 3.14, + errors: [ + { + instancePath: [], + schemaPath: [ + "values", + ], + }, + ], + }, + { + description: "nullable values schema - integer", + schema: { + values: { + type: "string", + }, + nullable: true, + }, + validSchema: true, + instance: 1, + errors: [ + { + instancePath: [], + schemaPath: [ + "values", + ], + }, + ], + }, + { + description: "nullable values schema - string", + schema: { + values: { + type: "string", + }, + nullable: true, + }, + validSchema: true, + instance: "foo", + errors: [ + { + instancePath: [], + schemaPath: [ + "values", + ], + }, + ], + }, + { + description: "nullable values schema - array", + schema: { + values: { + type: "string", + }, + nullable: true, + }, + validSchema: true, + instance: [], + errors: [ + { + instancePath: [], + schemaPath: [ + "values", + ], + }, + ], + }, + { + description: "values schema - empty object", + schema: { + values: { + type: "string", + }, + }, + validSchema: true, + instance: {}, + errors: [], + }, + { + description: "values schema - all values ok", + schema: { + values: { + type: "string", + }, + }, + validSchema: true, + instance: { + foo: "foo", + bar: "bar", + baz: "baz", + }, + errors: [], + }, + { + description: "values schema - some values bad", + schema: { + values: { + type: "string", + }, + }, + validSchema: true, + instance: { + foo: "foo", + bar: 123, + baz: 123, + }, + errors: [ + { + instancePath: [ + "bar", + ], + schemaPath: [ + "values", + "type", + ], + }, + { + instancePath: [ + "baz", + ], + schemaPath: [ + "values", + "type", + ], + }, + ], + }, + { + description: "values schema - all values bad", + schema: { + values: { + type: "string", + }, + }, + validSchema: true, + instance: { + foo: 123, + bar: 123, + baz: 123, + }, + errors: [ + { + instancePath: [ + "foo", + ], + schemaPath: [ + "values", + "type", + ], + }, + { + instancePath: [ + "bar", + ], + schemaPath: [ + "values", + "type", + ], + }, + { + instancePath: [ + "baz", + ], + schemaPath: [ + "values", + "type", + ], + }, + ], + }, + { + description: "values schema - nested values, ok", + schema: { + values: { + values: { + type: "string", + }, + }, + }, + validSchema: true, + instance: { + a0: { + b0: "c", + }, + a1: {}, + a2: { + b0: "c", + }, + }, + errors: [], + }, + { + description: "values schema - nested values, bad", + schema: { + values: { + values: { + type: "string", + }, + }, + }, + validSchema: true, + instance: { + a0: { + b0: null, + }, + a1: { + b0: "c", + }, + a2: { + b0: "c", + b1: null, + }, + a3: null, + }, + errors: [ + { + instancePath: [ + "a0", + "b0", + ], + schemaPath: [ + "values", + "values", + "type", + ], + }, + { + instancePath: [ + "a2", + "b1", + ], + schemaPath: [ + "values", + "values", + "type", + ], + }, + { + instancePath: [ + "a3", + ], + schemaPath: [ + "values", + "values", + ], + }, + ], + }, + { + description: "discriminator schema - null", + schema: { + discriminator: "foo", + mapping: {}, + }, + validSchema: true, + instance: null, + errors: [ + { + instancePath: [], + schemaPath: [ + "discriminator", + ], + }, + ], + }, + { + description: "discriminator schema - boolean", + schema: { + discriminator: "foo", + mapping: {}, + }, + validSchema: true, + instance: true, + errors: [ + { + instancePath: [], + schemaPath: [ + "discriminator", + ], + }, + ], + }, + { + description: "discriminator schema - float", + schema: { + discriminator: "foo", + mapping: {}, + }, + validSchema: true, + instance: 3.14, + errors: [ + { + instancePath: [], + schemaPath: [ + "discriminator", + ], + }, + ], + }, + { + description: "discriminator schema - integer", + schema: { + discriminator: "foo", + mapping: {}, + }, + validSchema: true, + instance: 1, + errors: [ + { + instancePath: [], + schemaPath: [ + "discriminator", + ], + }, + ], + }, + { + description: "discriminator schema - string", + schema: { + discriminator: "foo", + mapping: {}, + }, + validSchema: true, + instance: "foo", + errors: [ + { + instancePath: [], + schemaPath: [ + "discriminator", + ], + }, + ], + }, + { + description: "discriminator schema - array", + schema: { + discriminator: "foo", + mapping: {}, + }, + validSchema: true, + instance: [], + errors: [ + { + instancePath: [], + schemaPath: [ + "discriminator", + ], + }, + ], + }, + { + description: "nullable discriminator schema - null", + schema: { + discriminator: "foo", + mapping: {}, + nullable: true, + }, + validSchema: true, + instance: null, + errors: [], + }, + { + description: "nullable discriminator schema - boolean", + schema: { + discriminator: "foo", + mapping: {}, + nullable: true, + }, + validSchema: true, + instance: true, + errors: [ + { + instancePath: [], + schemaPath: [ + "discriminator", + ], + }, + ], + }, + { + description: "nullable discriminator schema - float", + schema: { + discriminator: "foo", + mapping: {}, + nullable: true, + }, + validSchema: true, + instance: 3.14, + errors: [ + { + instancePath: [], + schemaPath: [ + "discriminator", + ], + }, + ], + }, + { + description: "nullable discriminator schema - integer", + schema: { + discriminator: "foo", + mapping: {}, + nullable: true, + }, + validSchema: true, + instance: 1, + errors: [ + { + instancePath: [], + schemaPath: [ + "discriminator", + ], + }, + ], + }, + { + description: "nullable discriminator schema - string", + schema: { + discriminator: "foo", + mapping: {}, + nullable: true, + }, + validSchema: true, + instance: "foo", + errors: [ + { + instancePath: [], + schemaPath: [ + "discriminator", + ], + }, + ], + }, + { + description: "nullable discriminator schema - array", + schema: { + discriminator: "foo", + mapping: {}, + nullable: true, + }, + validSchema: true, + instance: [], + errors: [ + { + instancePath: [], + schemaPath: [ + "discriminator", + ], + }, + ], + }, + { + description: "discriminator schema - discriminator missing", + schema: { + discriminator: "foo", + mapping: { + x: { + properties: { + a: { + type: "string", + }, + }, + }, + y: { + properties: { + a: { + type: "float64", + }, + }, + }, + }, + }, + validSchema: true, + instance: {}, + errors: [ + { + instancePath: [], + schemaPath: [ + "discriminator", + ], + }, + ], + }, + { + description: "discriminator schema - discriminator not string", + schema: { + discriminator: "foo", + mapping: { + x: { + properties: { + a: { + type: "string", + }, + }, + }, + y: { + properties: { + a: { + type: "float64", + }, + }, + }, + }, + }, + validSchema: true, + instance: { + foo: null, + }, + errors: [ + { + instancePath: [ + "foo", + ], + schemaPath: [ + "discriminator", + ], + }, + ], + }, + { + description: "discriminator schema - discriminator not in mapping", + schema: { + discriminator: "foo", + mapping: { + x: { + properties: { + a: { + type: "string", + }, + }, + }, + y: { + properties: { + a: { + type: "float64", + }, + }, + }, + }, + }, + validSchema: true, + instance: { + foo: "z", + }, + errors: [ + { + instancePath: [ + "foo", + ], + schemaPath: [ + "mapping", + ], + }, + ], + }, + { + description: "discriminator schema - instance fails mapping schema", + schema: { + discriminator: "foo", + mapping: { + x: { + properties: { + a: { + type: "string", + }, + }, + }, + y: { + properties: { + a: { + type: "float64", + }, + }, + }, + }, + }, + validSchema: true, + instance: { + foo: "y", + a: "a", + }, + errors: [ + { + instancePath: [ + "a", + ], + schemaPath: [ + "mapping", + "y", + "properties", + "a", + "type", + ], + }, + ], + }, + { + description: "discriminator schema - ok", + schema: { + discriminator: "foo", + mapping: { + x: { + properties: { + a: { + type: "string", + }, + }, + }, + y: { + properties: { + a: { + type: "float64", + }, + }, + }, + }, + }, + validSchema: true, + instance: { + foo: "x", + a: "a", + }, + errors: [], + }, +]; diff --git a/validate.ts b/validate.ts new file mode 100644 index 0000000..2dcc0a2 --- /dev/null +++ b/validate.ts @@ -0,0 +1,418 @@ +import { isRFC3339 } from "./_rfc3339.ts"; +import { + isDiscriminatorForm, + isElementsForm, + isEnumForm, + isPropertiesForm, + isRefForm, + isTypeForm, + isValuesForm, + Schema, +} from "./schema.ts"; + +/** + * ValidationConfig represents options you can pass to [validate](#validate). + */ +export interface ValidationConfig { + /** + * maxDepth is the maximum number of `ref`s to recursively follow before + * [validate](#validate) throws [MaxDepthExceededError](#MaxDepthExceededError). + * + * If maxDepth is zero, then no maximum depth will be enforced. + * [validate](#validate) will recursively follow `ref`s indefinitely, potentially + * causing a stack overflow. + * + * By default, maxDepth is zero. + */ + maxDepth: number; + + /** + * maxErrors is the maximum number of errors to return from [validate](#validate). + * + * If maxErrors is positive, [validate](#validate) may return fewer errors than + * maxErrors, but it will never return more than maxErrors. + * + * If maxErrors is zero, [validate](#validate) will return all validation errors. + * + * By default, maxErrors is zero. + */ + maxErrors: number; +} + +/** + * MaxDepthExceededError is the error returned if + * [ValidationConfig](#ValidationConfig)maxDepth} is exceeded during [validate](#validate). + */ +export class MaxDepthExceededError extends Error {} + +class MaxErrorsReachedError extends Error {} + +/** + * ValidationError represents a JSON Typedef validation error. + * + * In terms of the formal JSON Typedef specification, ValidationError + * corresponds to a JSON Typedef error "indicator". ValidationError is *not* a + * subclass of Error. It is just a plain old TypeScript interface. + * + * Both elements of ValidationError are meant to be used as the path segments of + * an [RFC6901 JSON Pointer](https://tools.ietf.org/html/rfc6901). This package + * does not provide an implementation of JSON Pointers for you, and you may + * choose to not use JSON Pointers at all. + */ +export interface ValidationError { + /** + * instancePath is the path to a part of the instance, or "input", that was + * rejected. + */ + instancePath: string[]; + + /** + * schemaPath is the path to the part of the schema that rejected the input. + */ + schemaPath: string[]; +} + +/** + * validate performs JSON Typedef validation of an instance (or "input") against + * a JSON Typedef schema, returning a standardized set of errors. + * + * This function may throw [MaxDepthExceededError](#MaxDepthExceededError) if you have configured + * a [ValidationConfig](#ValidationConfig)maxDepth}. If you do not configure such a maxDepth, + * then this function may cause a stack overflow. That's because of + * circularly-defined schemas like this one: + * + * ```json + * { + * "ref": "loop", + * "definitions": { + * "loop": { "ref": "loop" } + * } + * } + * ``` + * + * If your schema is not circularly defined like this, then there is no risk for + * validate to overflow the stack. + * + * If you are only interested in a certain number of error messages, consider + * using [ValidationConfig](#ValidationConfig)maxErrors} to get better performance. For + * instance, if all you care about is whether the input is OK or if it has + * errors, you may want to set maxErrors to 1. + * + * @param schema The schema to validate data against + * @param instance The "input" to validate + * @param config Validation options. Optional. + */ +export function validate( + schema: Schema, + instance: unknown, + config?: ValidationConfig, +): ValidationError[] { + const state = { + errors: [], + instanceTokens: [], + schemaTokens: [[]], + root: schema, + config: config || { maxDepth: 0, maxErrors: 0 }, + }; + + try { + validateWithState(state, schema, instance); + } catch (err) { + if (err instanceof MaxErrorsReachedError) { + // MaxErrorsReachedError is just a dummy error to abort further + // validation. The contents of state.errors are what we need to return. + } else { + // This is a genuine error. Let's re-throw it. + throw err; + } + } + + return state.errors; +} + +interface ValidationState { + errors: ValidationError[]; + instanceTokens: string[]; + schemaTokens: string[][]; + root: Schema; + config: ValidationConfig; +} + +function validateWithState( + state: ValidationState, + schema: Schema, + instance: unknown, + parentTag?: string, +) { + if (schema.nullable && instance === null) { + return; + } + + if (isRefForm(schema)) { + if (state.schemaTokens.length === state.config.maxDepth) { + throw new MaxDepthExceededError(); + } + + // The ref form is the only case where we push a new array onto + // schemaTokens; we maintain a separate stack for each reference. + state.schemaTokens.push(["definitions", schema.ref]); + validateWithState(state, state.root.definitions![schema.ref], instance); + state.schemaTokens.pop(); + } else if (isTypeForm(schema)) { + pushSchemaToken(state, "type"); + + switch (schema.type) { + case "boolean": + if (typeof instance !== "boolean") { + pushError(state); + } + break; + case "float32": + case "float64": + if (typeof instance !== "number") { + pushError(state); + } + break; + case "int8": + validateInt(state, instance, -128, 127); + break; + case "uint8": + validateInt(state, instance, 0, 255); + break; + case "int16": + validateInt(state, instance, -32768, 32767); + break; + case "uint16": + validateInt(state, instance, 0, 65535); + break; + case "int32": + validateInt(state, instance, -2147483648, 2147483647); + break; + case "uint32": + validateInt(state, instance, 0, 4294967295); + break; + case "string": + if (typeof instance !== "string") { + pushError(state); + } + break; + case "timestamp": + if (typeof instance !== "string") { + pushError(state); + } else { + if (!isRFC3339(instance)) { + pushError(state); + } + } + break; + } + + popSchemaToken(state); + } else if (isEnumForm(schema)) { + pushSchemaToken(state, "enum"); + + if (typeof instance !== "string" || !schema.enum.includes(instance)) { + pushError(state); + } + + popSchemaToken(state); + } else if (isElementsForm(schema)) { + pushSchemaToken(state, "elements"); + + if (Array.isArray(instance)) { + for (const [index, subInstance] of instance.entries()) { + pushInstanceToken(state, index.toString()); + validateWithState(state, schema.elements, subInstance); + popInstanceToken(state); + } + } else { + pushError(state); + } + + popSchemaToken(state); + } else if (isPropertiesForm(schema)) { + // JSON has six basic types of data (null, boolean, number, string, + // array, object). Of their standard JS countparts, three have a + // `typeof` of "object": null, array, and object. + // + // This check attempts to check if something is "really" an object. + if ( + typeof instance === "object" && + instance !== null && + !Array.isArray(instance) + ) { + if (schema.properties !== undefined) { + pushSchemaToken(state, "properties"); + for (const [name, subSchema] of Object.entries(schema.properties)) { + pushSchemaToken(state, name); + // deno-lint-ignore no-prototype-builtins + if (instance.hasOwnProperty(name)) { + pushInstanceToken(state, name); + // deno-lint-ignore no-explicit-any + validateWithState(state, subSchema, (instance as any)[name]); + popInstanceToken(state); + } else { + pushError(state); + } + popSchemaToken(state); + } + popSchemaToken(state); + } + + if (schema.optionalProperties !== undefined) { + pushSchemaToken(state, "optionalProperties"); + for ( + const [name, subSchema] of Object.entries( + schema.optionalProperties, + ) + ) { + pushSchemaToken(state, name); + // deno-lint-ignore no-prototype-builtins + if (instance.hasOwnProperty(name)) { + pushInstanceToken(state, name); + // deno-lint-ignore no-explicit-any + validateWithState(state, subSchema, (instance as any)[name]); + popInstanceToken(state); + } + popSchemaToken(state); + } + popSchemaToken(state); + } + + if (schema.additionalProperties !== true) { + for (const name of Object.keys(instance)) { + const inRequired = schema.properties && name in schema.properties; + const inOptional = schema.optionalProperties && + name in schema.optionalProperties; + + if (!inRequired && !inOptional && name !== parentTag) { + pushInstanceToken(state, name); + pushError(state); + popInstanceToken(state); + } + } + } + } else { + if (schema.properties !== undefined) { + pushSchemaToken(state, "properties"); + } else { + pushSchemaToken(state, "optionalProperties"); + } + + pushError(state); + popSchemaToken(state); + } + } else if (isValuesForm(schema)) { + pushSchemaToken(state, "values"); + + // See comment in properties form on why this is the test we use for + // checking for objects. + if ( + typeof instance === "object" && + instance !== null && + !Array.isArray(instance) + ) { + for (const [name, subInstance] of Object.entries(instance)) { + pushInstanceToken(state, name); + validateWithState(state, schema.values, subInstance); + popInstanceToken(state); + } + } else { + pushError(state); + } + + popSchemaToken(state); + } else if (isDiscriminatorForm(schema)) { + // See comment in properties form on why this is the test we use for + // checking for objects. + if ( + typeof instance === "object" && + instance !== null && + !Array.isArray(instance) + ) { + // deno-lint-ignore no-prototype-builtins + if (instance.hasOwnProperty(schema.discriminator)) { + // deno-lint-ignore no-explicit-any + const tag = (instance as any)[schema.discriminator]; + + if (typeof tag === "string") { + if (tag in schema.mapping) { + pushSchemaToken(state, "mapping"); + pushSchemaToken(state, tag); + validateWithState( + state, + schema.mapping[tag], + instance, + schema.discriminator, + ); + popSchemaToken(state); + popSchemaToken(state); + } else { + pushSchemaToken(state, "mapping"); + pushInstanceToken(state, schema.discriminator); + pushError(state); + popInstanceToken(state); + popSchemaToken(state); + } + } else { + pushSchemaToken(state, "discriminator"); + pushInstanceToken(state, schema.discriminator); + pushError(state); + popInstanceToken(state); + popSchemaToken(state); + } + } else { + pushSchemaToken(state, "discriminator"); + pushError(state); + popSchemaToken(state); + } + } else { + pushSchemaToken(state, "discriminator"); + pushError(state); + popSchemaToken(state); + } + } +} + +function validateInt( + state: ValidationState, + instance: unknown, + min: number, + max: number, +) { + if ( + typeof instance !== "number" || + !Number.isInteger(instance) || + instance < min || + instance > max + ) { + pushError(state); + } +} + +function pushInstanceToken(state: ValidationState, token: string) { + state.instanceTokens.push(token); +} + +function popInstanceToken(state: ValidationState) { + state.instanceTokens.pop(); +} + +function pushSchemaToken(state: ValidationState, token: string) { + state.schemaTokens[state.schemaTokens.length - 1].push(token); +} + +function popSchemaToken(state: ValidationState) { + state.schemaTokens[state.schemaTokens.length - 1].pop(); +} + +function pushError(state: ValidationState) { + state.errors.push({ + instancePath: [...state.instanceTokens], + schemaPath: [...state.schemaTokens[state.schemaTokens.length - 1]], + }); + + if (state.errors.length === state.config.maxErrors) { + throw new MaxErrorsReachedError(); + } +}