From 5f20d5f90adc6ea58e512f5da72638c7a96d93d9 Mon Sep 17 00:00:00 2001 From: Jason Ian Green Date: Fri, 17 May 2024 18:36:54 +0100 Subject: [PATCH 01/19] compileAsync a schema with discriminator and $ref, fixes #2427 (#2433) * fix #2427 compileAsync a schema with discriminator and $ref Make the discriminator code generation throw MissingRefError when the $ref does not synchronously resolve so that compileAsync can loadSchema and retry. * test: fix errors in test --------- Co-authored-by: Yonathan Randolph --- lib/vocabularies/discriminator/index.ts | 5 +- spec/discriminator.spec.ts | 61 +++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/lib/vocabularies/discriminator/index.ts b/lib/vocabularies/discriminator/index.ts index 98f0f8cfb..19ae6049f 100644 --- a/lib/vocabularies/discriminator/index.ts +++ b/lib/vocabularies/discriminator/index.ts @@ -3,6 +3,7 @@ import type {KeywordCxt} from "../../compile/validate" import {_, getProperty, Name} from "../../compile/codegen" import {DiscrError, DiscrErrorObj} from "../discriminator/types" import {resolveRef, SchemaEnv} from "../../compile" +import MissingRefError from "../../compile/ref_error" import {schemaHasRulesButRef} from "../../compile/util" export type DiscriminatorError = DiscrErrorObj | DiscrErrorObj @@ -66,8 +67,10 @@ const def: CodeKeywordDefinition = { for (let i = 0; i < oneOf.length; i++) { let sch = oneOf[i] if (sch?.$ref && !schemaHasRulesButRef(sch, it.self.RULES)) { - sch = resolveRef.call(it.self, it.schemaEnv.root, it.baseId, sch?.$ref) + const ref = sch.$ref + sch = resolveRef.call(it.self, it.schemaEnv.root, it.baseId, ref) if (sch instanceof SchemaEnv) sch = sch.schema + if (sch === undefined) throw new MissingRefError(it.opts.uriResolver, it.baseId, ref) } const propSch = sch?.properties?.[tagName] if (typeof propSch != "object") { diff --git a/spec/discriminator.spec.ts b/spec/discriminator.spec.ts index 28ff12146..74ba33ef0 100644 --- a/spec/discriminator.spec.ts +++ b/spec/discriminator.spec.ts @@ -159,6 +159,67 @@ describe("discriminator keyword", function () { }) }) + describe("schema with external $refs", () => { + const schemas = { + main: { + type: "object", + discriminator: {propertyName: "foo"}, + required: ["foo"], + oneOf: [ + { + $ref: "schema1", + }, + { + $ref: "schema2", + }, + ], + }, + schema1: { + type: "object", + properties: { + foo: {const: "x"}, + }, + }, + schema2: { + type: "object", + properties: { + foo: {enum: ["y", "z"]}, + }, + }, + } + + const data = {foo: "x"} + const badData = {foo: "w"} + + it("compile should resolve each $ref to a schema that was added with addSchema", () => { + const opts = { + discriminator: true, + } + const ajv = new _Ajv(opts) + ajv.addSchema(schemas.main, "https://host/main") + ajv.addSchema(schemas.schema1, "https://host/schema1") + ajv.addSchema(schemas.schema2, "https://host/schema2") + + const validate = ajv.compile({$ref: "https://host/main"}) + assert.strictEqual(validate(data), true) + assert.strictEqual(validate(badData), false) + }) + it("compileAsync should loadSchema each $ref", async () => { + const opts = { + discriminator: true, + loadSchema(url) { + if (!url.startsWith("https://host/")) return undefined + const name = url.substring("https://host/".length) + return schemas[name] + }, + } + const ajv = new _Ajv(opts) + const validate = await ajv.compileAsync({$ref: "https://host/main"}) + assert.strictEqual(validate(data), true) + assert.strictEqual(validate(badData), false) + }) + }) + describe("validation with deeply referenced schemas", () => { const schema = [ { From 9e266399cac65e487d57ec0f50b9eb4005261622 Mon Sep 17 00:00:00 2001 From: Jason Ian Green Date: Sat, 25 May 2024 13:39:29 +0100 Subject: [PATCH 02/19] bump version to 8.14.0 for publishing (#2440) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 21ac5eed3..cdebd54a9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ajv", - "version": "8.13.0", + "version": "8.14.0", "description": "Another JSON Schema Validator", "main": "dist/ajv.js", "types": "dist/ajv.d.ts", From fa1b5d56711a038af9a5f059cb7d1203e65449f4 Mon Sep 17 00:00:00 2001 From: Angelo Verlain Shema <37999241+vixalien@users.noreply.github.com> Date: Fri, 31 May 2024 11:38:26 +0200 Subject: [PATCH 03/19] Replace `uri-js` with `fast-uri` (#2415) * chore: replace uri-js with fast-uri * fix: test still requiring fast-uri as fallback for resolver --------- Co-authored-by: Jason Ian Green --- docs/options.md | 2 +- lib/compile/index.ts | 2 +- lib/compile/resolve.ts | 2 +- lib/runtime/uri.ts | 2 +- lib/types/index.ts | 2 +- package.json | 6 +++--- spec/resolve.spec.ts | 8 ++++---- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/options.md b/docs/options.md index 6f74b02b3..fdce7f571 100644 --- a/docs/options.md +++ b/docs/options.md @@ -344,7 +344,7 @@ Include human-readable messages in errors. `true` by default. `false` can be pas ### uriResolver -By default `uriResolver` is undefined and relies on the embedded uriResolver [uri-js](https://github.com/garycourt/uri-js). Pass an object that satisfies the interface [UriResolver](https://github.com/ajv-validator/ajv/blob/master/lib/types/index.ts) to be used in replacement. One alternative is [fast-uri](https://github.com/fastify/fast-uri). +By default `uriResolver` is undefined and relies on the embedded uriResolver [fast-uri](https://github.com/fastify/fast-uri). Pass an object that satisfies the interface [UriResolver](https://github.com/ajv-validator/ajv/blob/master/lib/types/index.ts) to be used in replacement. One alternative is [uri-js](https://github.com/garycourt/uri-js). ### code diff --git a/lib/compile/index.ts b/lib/compile/index.ts index 3dac2699b..9ab38d533 100644 --- a/lib/compile/index.ts +++ b/lib/compile/index.ts @@ -14,7 +14,7 @@ import N from "./names" import {LocalRefs, getFullPath, _getFullPath, inlineRef, normalizeId, resolveUrl} from "./resolve" import {schemaHasRulesButRef, unescapeFragment} from "./util" import {validateFunctionCode} from "./validate" -import * as URI from "uri-js" +import * as URI from "fast-uri" import {JSONType} from "./rules" export type SchemaRefs = { diff --git a/lib/compile/resolve.ts b/lib/compile/resolve.ts index be283866c..7bdd36d91 100644 --- a/lib/compile/resolve.ts +++ b/lib/compile/resolve.ts @@ -1,6 +1,6 @@ import type {AnySchema, AnySchemaObject, UriResolver} from "../types" import type Ajv from "../ajv" -import type {URIComponents} from "uri-js" +import type {URIComponents} from "fast-uri" import {eachItem} from "./util" import * as equal from "fast-deep-equal" import * as traverse from "json-schema-traverse" diff --git a/lib/runtime/uri.ts b/lib/runtime/uri.ts index 7dd35f9d1..5450549cd 100644 --- a/lib/runtime/uri.ts +++ b/lib/runtime/uri.ts @@ -1,4 +1,4 @@ -import * as uri from "uri-js" +import * as uri from "fast-uri" type URI = typeof uri & {code: string} ;(uri as URI).code = 'require("ajv/dist/runtime/uri").default' diff --git a/lib/types/index.ts b/lib/types/index.ts index b5ef53eeb..2676a1df3 100644 --- a/lib/types/index.ts +++ b/lib/types/index.ts @@ -1,4 +1,4 @@ -import * as URI from "uri-js" +import * as URI from "fast-uri" import type {CodeGen, Code, Name, ScopeValueSets, ValueScopeName} from "../compile/codegen" import type {SchemaEnv, SchemaCxt, SchemaObjCxt} from "../compile" import type {JSONType} from "../compile/rules" diff --git a/package.json b/package.json index cdebd54a9..a135d10f3 100644 --- a/package.json +++ b/package.json @@ -59,9 +59,9 @@ "runkitExampleFilename": ".runkit_example.js", "dependencies": { "fast-deep-equal": "^3.1.3", + "fast-uri": "^2.3.0", "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.4.1" + "require-from-string": "^2.0.2" }, "devDependencies": { "@ajv-validator/config": "^0.5.0", @@ -83,7 +83,6 @@ "dayjs-plugin-utc": "^0.1.2", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", - "fast-uri": "^2.3.0", "glob": "^10.3.10", "husky": "^9.0.11", "if-node-version": "^1.1.1", @@ -104,6 +103,7 @@ "rollup-plugin-terser": "^7.0.2", "ts-node": "^10.9.2", "tsify": "^5.0.4", + "uri-js": "^4.4.1", "typescript": "5.3.3" }, "collective": { diff --git a/spec/resolve.spec.ts b/spec/resolve.spec.ts index 2fe5b1041..a89cccaf7 100644 --- a/spec/resolve.spec.ts +++ b/spec/resolve.spec.ts @@ -4,17 +4,17 @@ import _Ajv from "./ajv" import type {AnyValidateFunction} from "../dist/types" import type MissingRefError from "../dist/compile/ref_error" import chai from "./chai" -import * as fastUri from "fast-uri" +import * as uriJs from "uri-js" const should = chai.should() -const uriResolvers = [undefined, fastUri] +const uriResolvers = [undefined, uriJs] uriResolvers.forEach((resolver) => { let describeTitle: string if (resolver !== undefined) { - describeTitle = "fast-uri resolver" - } else { describeTitle = "uri-js resolver" + } else { + describeTitle = "fast-uri resolver" } describe(describeTitle, () => { describe("resolve", () => { From accd9f3ee5e0e688244c1accf7bb13745b513177 Mon Sep 17 00:00:00 2001 From: Jason Ian Green Date: Mon, 3 Jun 2024 09:49:15 +0100 Subject: [PATCH 04/19] Bump to 8.15.0 (#2442) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a135d10f3..4da6aed2c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ajv", - "version": "8.14.0", + "version": "8.15.0", "description": "Another JSON Schema Validator", "main": "dist/ajv.js", "types": "dist/ajv.d.ts", From c8b37f448f77448656222a5a5e279432857f7e9f Mon Sep 17 00:00:00 2001 From: Jason Ian Green Date: Tue, 4 Jun 2024 19:25:06 +0100 Subject: [PATCH 05/19] Revert fast-uri change (#2444) * Revert "Replace `uri-js` with `fast-uri` (#2415)" This reverts commit fa1b5d56711a038af9a5f059cb7d1203e65449f4. * bump version to 8.16.0 --- docs/options.md | 2 +- lib/compile/index.ts | 2 +- lib/compile/resolve.ts | 2 +- lib/runtime/uri.ts | 2 +- lib/types/index.ts | 2 +- package.json | 8 ++++---- spec/resolve.spec.ts | 8 ++++---- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/options.md b/docs/options.md index fdce7f571..6f74b02b3 100644 --- a/docs/options.md +++ b/docs/options.md @@ -344,7 +344,7 @@ Include human-readable messages in errors. `true` by default. `false` can be pas ### uriResolver -By default `uriResolver` is undefined and relies on the embedded uriResolver [fast-uri](https://github.com/fastify/fast-uri). Pass an object that satisfies the interface [UriResolver](https://github.com/ajv-validator/ajv/blob/master/lib/types/index.ts) to be used in replacement. One alternative is [uri-js](https://github.com/garycourt/uri-js). +By default `uriResolver` is undefined and relies on the embedded uriResolver [uri-js](https://github.com/garycourt/uri-js). Pass an object that satisfies the interface [UriResolver](https://github.com/ajv-validator/ajv/blob/master/lib/types/index.ts) to be used in replacement. One alternative is [fast-uri](https://github.com/fastify/fast-uri). ### code diff --git a/lib/compile/index.ts b/lib/compile/index.ts index 9ab38d533..3dac2699b 100644 --- a/lib/compile/index.ts +++ b/lib/compile/index.ts @@ -14,7 +14,7 @@ import N from "./names" import {LocalRefs, getFullPath, _getFullPath, inlineRef, normalizeId, resolveUrl} from "./resolve" import {schemaHasRulesButRef, unescapeFragment} from "./util" import {validateFunctionCode} from "./validate" -import * as URI from "fast-uri" +import * as URI from "uri-js" import {JSONType} from "./rules" export type SchemaRefs = { diff --git a/lib/compile/resolve.ts b/lib/compile/resolve.ts index 7bdd36d91..be283866c 100644 --- a/lib/compile/resolve.ts +++ b/lib/compile/resolve.ts @@ -1,6 +1,6 @@ import type {AnySchema, AnySchemaObject, UriResolver} from "../types" import type Ajv from "../ajv" -import type {URIComponents} from "fast-uri" +import type {URIComponents} from "uri-js" import {eachItem} from "./util" import * as equal from "fast-deep-equal" import * as traverse from "json-schema-traverse" diff --git a/lib/runtime/uri.ts b/lib/runtime/uri.ts index 5450549cd..7dd35f9d1 100644 --- a/lib/runtime/uri.ts +++ b/lib/runtime/uri.ts @@ -1,4 +1,4 @@ -import * as uri from "fast-uri" +import * as uri from "uri-js" type URI = typeof uri & {code: string} ;(uri as URI).code = 'require("ajv/dist/runtime/uri").default' diff --git a/lib/types/index.ts b/lib/types/index.ts index 2676a1df3..b5ef53eeb 100644 --- a/lib/types/index.ts +++ b/lib/types/index.ts @@ -1,4 +1,4 @@ -import * as URI from "fast-uri" +import * as URI from "uri-js" import type {CodeGen, Code, Name, ScopeValueSets, ValueScopeName} from "../compile/codegen" import type {SchemaEnv, SchemaCxt, SchemaObjCxt} from "../compile" import type {JSONType} from "../compile/rules" diff --git a/package.json b/package.json index 4da6aed2c..ca56231a8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ajv", - "version": "8.15.0", + "version": "8.16.0", "description": "Another JSON Schema Validator", "main": "dist/ajv.js", "types": "dist/ajv.d.ts", @@ -59,9 +59,9 @@ "runkitExampleFilename": ".runkit_example.js", "dependencies": { "fast-deep-equal": "^3.1.3", - "fast-uri": "^2.3.0", "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" + "require-from-string": "^2.0.2", + "uri-js": "^4.4.1" }, "devDependencies": { "@ajv-validator/config": "^0.5.0", @@ -83,6 +83,7 @@ "dayjs-plugin-utc": "^0.1.2", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", + "fast-uri": "^2.3.0", "glob": "^10.3.10", "husky": "^9.0.11", "if-node-version": "^1.1.1", @@ -103,7 +104,6 @@ "rollup-plugin-terser": "^7.0.2", "ts-node": "^10.9.2", "tsify": "^5.0.4", - "uri-js": "^4.4.1", "typescript": "5.3.3" }, "collective": { diff --git a/spec/resolve.spec.ts b/spec/resolve.spec.ts index a89cccaf7..2fe5b1041 100644 --- a/spec/resolve.spec.ts +++ b/spec/resolve.spec.ts @@ -4,17 +4,17 @@ import _Ajv from "./ajv" import type {AnyValidateFunction} from "../dist/types" import type MissingRefError from "../dist/compile/ref_error" import chai from "./chai" -import * as uriJs from "uri-js" +import * as fastUri from "fast-uri" const should = chai.should() -const uriResolvers = [undefined, uriJs] +const uriResolvers = [undefined, fastUri] uriResolvers.forEach((resolver) => { let describeTitle: string if (resolver !== undefined) { - describeTitle = "uri-js resolver" - } else { describeTitle = "fast-uri resolver" + } else { + describeTitle = "uri-js resolver" } describe(describeTitle, () => { describe("resolve", () => { From 80c014fa537e9e32cd6aa493cea83ffe619e906e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Wed, 12 Jun 2024 23:07:57 +0300 Subject: [PATCH 06/19] Revert "Revert fast-uri change (#2444)" (#2448) * Revert "Revert fast-uri change (#2444)" This reverts commit c8b37f448f77448656222a5a5e279432857f7e9f. * fix inports * up --- docs/options.md | 2 +- lib/compile/index.ts | 4 ++-- lib/compile/resolve.ts | 4 ++-- lib/runtime/uri.ts | 2 +- lib/types/index.ts | 6 +++--- package.json | 8 ++++---- spec/resolve.spec.ts | 8 ++++---- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/options.md b/docs/options.md index 6f74b02b3..fdce7f571 100644 --- a/docs/options.md +++ b/docs/options.md @@ -344,7 +344,7 @@ Include human-readable messages in errors. `true` by default. `false` can be pas ### uriResolver -By default `uriResolver` is undefined and relies on the embedded uriResolver [uri-js](https://github.com/garycourt/uri-js). Pass an object that satisfies the interface [UriResolver](https://github.com/ajv-validator/ajv/blob/master/lib/types/index.ts) to be used in replacement. One alternative is [fast-uri](https://github.com/fastify/fast-uri). +By default `uriResolver` is undefined and relies on the embedded uriResolver [fast-uri](https://github.com/fastify/fast-uri). Pass an object that satisfies the interface [UriResolver](https://github.com/ajv-validator/ajv/blob/master/lib/types/index.ts) to be used in replacement. One alternative is [uri-js](https://github.com/garycourt/uri-js). ### code diff --git a/lib/compile/index.ts b/lib/compile/index.ts index 3dac2699b..bfc393455 100644 --- a/lib/compile/index.ts +++ b/lib/compile/index.ts @@ -14,7 +14,7 @@ import N from "./names" import {LocalRefs, getFullPath, _getFullPath, inlineRef, normalizeId, resolveUrl} from "./resolve" import {schemaHasRulesButRef, unescapeFragment} from "./util" import {validateFunctionCode} from "./validate" -import * as URI from "uri-js" +import {URIComponent} from "fast-uri" import {JSONType} from "./rules" export type SchemaRefs = { @@ -295,7 +295,7 @@ const PREVENT_SCOPE_CHANGE = new Set([ function getJsonPointer( this: Ajv, - parsedRef: URI.URIComponents, + parsedRef: URIComponent, {baseId, schema, root}: SchemaEnv ): SchemaEnv | undefined { if (parsedRef.fragment?.[0] !== "/") return diff --git a/lib/compile/resolve.ts b/lib/compile/resolve.ts index be283866c..b8c4aca39 100644 --- a/lib/compile/resolve.ts +++ b/lib/compile/resolve.ts @@ -1,6 +1,6 @@ import type {AnySchema, AnySchemaObject, UriResolver} from "../types" import type Ajv from "../ajv" -import type {URIComponents} from "uri-js" +import type {URIComponent} from "fast-uri" import {eachItem} from "./util" import * as equal from "fast-deep-equal" import * as traverse from "json-schema-traverse" @@ -73,7 +73,7 @@ export function getFullPath(resolver: UriResolver, id = "", normalize?: boolean) return _getFullPath(resolver, p) } -export function _getFullPath(resolver: UriResolver, p: URIComponents): string { +export function _getFullPath(resolver: UriResolver, p: URIComponent): string { const serialized = resolver.serialize(p) return serialized.split("#")[0] + "#" } diff --git a/lib/runtime/uri.ts b/lib/runtime/uri.ts index 7dd35f9d1..5450549cd 100644 --- a/lib/runtime/uri.ts +++ b/lib/runtime/uri.ts @@ -1,4 +1,4 @@ -import * as uri from "uri-js" +import * as uri from "fast-uri" type URI = typeof uri & {code: string} ;(uri as URI).code = 'require("ajv/dist/runtime/uri").default' diff --git a/lib/types/index.ts b/lib/types/index.ts index b5ef53eeb..39bc51b0b 100644 --- a/lib/types/index.ts +++ b/lib/types/index.ts @@ -1,4 +1,4 @@ -import * as URI from "uri-js" +import {URIComponent} from "fast-uri" import type {CodeGen, Code, Name, ScopeValueSets, ValueScopeName} from "../compile/codegen" import type {SchemaEnv, SchemaCxt, SchemaObjCxt} from "../compile" import type {JSONType} from "../compile/rules" @@ -238,7 +238,7 @@ export interface RegExpLike { } export interface UriResolver { - parse(uri: string): URI.URIComponents + parse(uri: string): URIComponent resolve(base: string, path: string): string - serialize(component: URI.URIComponents): string + serialize(component: URIComponent): string } diff --git a/package.json b/package.json index ca56231a8..5198552a1 100644 --- a/package.json +++ b/package.json @@ -59,9 +59,9 @@ "runkitExampleFilename": ".runkit_example.js", "dependencies": { "fast-deep-equal": "^3.1.3", + "fast-uri": "^2.4.0", "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.4.1" + "require-from-string": "^2.0.2" }, "devDependencies": { "@ajv-validator/config": "^0.5.0", @@ -83,7 +83,6 @@ "dayjs-plugin-utc": "^0.1.2", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", - "fast-uri": "^2.3.0", "glob": "^10.3.10", "husky": "^9.0.11", "if-node-version": "^1.1.1", @@ -104,7 +103,8 @@ "rollup-plugin-terser": "^7.0.2", "ts-node": "^10.9.2", "tsify": "^5.0.4", - "typescript": "5.3.3" + "typescript": "5.3.3", + "uri-js": "^4.4.1" }, "collective": { "type": "opencollective", diff --git a/spec/resolve.spec.ts b/spec/resolve.spec.ts index 2fe5b1041..a89cccaf7 100644 --- a/spec/resolve.spec.ts +++ b/spec/resolve.spec.ts @@ -4,17 +4,17 @@ import _Ajv from "./ajv" import type {AnyValidateFunction} from "../dist/types" import type MissingRefError from "../dist/compile/ref_error" import chai from "./chai" -import * as fastUri from "fast-uri" +import * as uriJs from "uri-js" const should = chai.should() -const uriResolvers = [undefined, fastUri] +const uriResolvers = [undefined, uriJs] uriResolvers.forEach((resolver) => { let describeTitle: string if (resolver !== undefined) { - describeTitle = "fast-uri resolver" - } else { describeTitle = "uri-js resolver" + } else { + describeTitle = "fast-uri resolver" } describe(describeTitle, () => { describe("resolve", () => { From 85dafb06947b4f8932d2d807350bf8ce4ce9e0f7 Mon Sep 17 00:00:00 2001 From: Jason Ian Green Date: Sun, 16 Jun 2024 14:17:45 +0100 Subject: [PATCH 07/19] fix: ignore new eslint error (#2455) --- lib/compile/codegen/code.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/compile/codegen/code.ts b/lib/compile/codegen/code.ts index b17701973..9d4de6149 100644 --- a/lib/compile/codegen/code.ts +++ b/lib/compile/codegen/code.ts @@ -1,3 +1,4 @@ +// eslint-disable-next-line @typescript-eslint/no-extraneous-class export abstract class _CodeOrName { abstract readonly str: string abstract readonly names: UsedNames From 8bccdc4d7c46c1a8e0dacc1c4e1cd317c20b3fe5 Mon Sep 17 00:00:00 2001 From: Jason Ian Green Date: Sun, 16 Jun 2024 14:22:14 +0100 Subject: [PATCH 08/19] docs: clarify behaviour of addVocabulary (#2454) As it's name is so different and not just `addKeywords`, there has been some confusion as to it's behaviour. --- docs/strict-mode.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/strict-mode.md b/docs/strict-mode.md index d067d9d5a..5aed953ef 100644 --- a/docs/strict-mode.md +++ b/docs/strict-mode.md @@ -42,10 +42,10 @@ By default Ajv fails schema compilation when unknown keywords are used. Users ca ajv.addKeyword("allowedKeyword") ``` -or +or use the convenience method `addVocabulary` for multiple keywords ```javascript -ajv.addVocabulary(["allowed1", "allowed2"]) +ajv.addVocabulary(["allowed1", "allowed2"]) // simply calls addKeyword multiple times ``` #### Ignored "additionalItems" keyword From 603f63b291160ae02472184d30d408e8d7114af8 Mon Sep 17 00:00:00 2001 From: Nicholas Blott Date: Wed, 19 Jun 2024 23:45:57 +0200 Subject: [PATCH 09/19] docs: refactor to improve legibility (#2432) The use of "not more" with the reverse ordering of the sentence made this difficult to parse. After reversing the order and removing negation, the surrounding paragraphs were updated to match the style. Co-authored-by: Jason Ian Green --- docs/json-schema.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/json-schema.md b/docs/json-schema.md index c888c636d..0b7659ef5 100644 --- a/docs/json-schema.md +++ b/docs/json-schema.md @@ -478,11 +478,11 @@ To create and equivalent schema in draft-2020-12 use keywords [prefixItems](#pre The value of the keyword should be a boolean or an object. -If `items` keyword is not present or it is an object, `additionalItems` keyword should be ignored regardless of its value. By default Ajv will throw exception in this case - see [Strict mode](./strict-mode.md) +`additionalItems` keyword is ignored if `items` keyword is not present or is an object. By default Ajv will throw exception in this case - see [Strict mode](./strict-mode.md) -If `items` keyword is an array and data array has not more items than the length of `items` keyword value, `additionalItems` keyword is also ignored. +`additionalItems` keyword is ignored if `items` keyword has more elements than data array. -If the length of data array is bigger than the length of "items" keyword value than the result of the validation depends on the value of `additionalItems` keyword: +If the data array has more elements than the `items` keyword value then the result of the validation depends on the value of `additionalItems` keyword: - `false`: data is invalid - `true`: data is valid From 650c7f6d6aa7e8a0608ad4196d674a2f2d2fa685 Mon Sep 17 00:00:00 2001 From: Antonin Delpeuch Date: Wed, 19 Jun 2024 23:58:07 +0200 Subject: [PATCH 10/19] Fix grammatical typo in managing-schemas.md (#2305) "there is many" -> "there are many" Co-authored-by: Jason Ian Green --- docs/guide/managing-schemas.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/managing-schemas.md b/docs/guide/managing-schemas.md index eb007b539..4c860755c 100644 --- a/docs/guide/managing-schemas.md +++ b/docs/guide/managing-schemas.md @@ -186,7 +186,7 @@ In the example above, the key passed to the `addSchema` method was used to retri ### Pre-adding all schemas vs adding on demand -In the example above all schemas were added in advance. It is also possible, to add schemas as they are used - it can be helpful if there is many schemas. In this case, you need to check first whether the schema is already added by calling `getSchema` method - it would return `undefined` if not: +In the example above all schemas were added in advance. It is also possible, to add schemas as they are used - it can be helpful if there are many schemas. In this case, you need to check first whether the schema is already added by calling `getSchema` method - it would return `undefined` if not: ```javascript const schema_user = require("./schema_user.json") From a18641ef4ceb9623ea7c437e6f4f98d44ac293aa Mon Sep 17 00:00:00 2001 From: Alex B <17498057+alexanderjsx@users.noreply.github.com> Date: Sun, 30 Jun 2024 22:59:42 +0100 Subject: [PATCH 11/19] Update modifying-data.md - fix broken strict-mode link (#2459) --- docs/guide/modifying-data.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/modifying-data.md b/docs/guide/modifying-data.md index 2705c0bed..5b1d893bd 100644 --- a/docs/guide/modifying-data.md +++ b/docs/guide/modifying-data.md @@ -210,7 +210,7 @@ With `useDefaults` option `default` keywords throws exception during schema comp The strict mode option can change the behaviour for these unsupported defaults (`strict: false` to ignore them, `"log"` to log a warning). -See [Strict mode](./strict-mode.md). +See [Strict mode](../strict-mode.md). ::: tip Default with discriminator keyword Defaults will be assigned in schemas inside `oneOf` in case [discriminator](../json-schema.md#discriminator) keyword is used. From 595fe58e64e8d5fb8a50fd7a58f9e7f3bcca0bac Mon Sep 17 00:00:00 2001 From: Jason Ian Green Date: Sun, 7 Jul 2024 22:35:20 +0100 Subject: [PATCH 12/19] feat: add test for encoded refs and bump fast-uri (#2449) * test: add encoded ref test * bump fast-uri * remove .only --- package.json | 2 +- spec/resolve.spec.ts | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 5198552a1..88effa2e8 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "runkitExampleFilename": ".runkit_example.js", "dependencies": { "fast-deep-equal": "^3.1.3", - "fast-uri": "^2.4.0", + "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" }, diff --git a/spec/resolve.spec.ts b/spec/resolve.spec.ts index a89cccaf7..032f99ff8 100644 --- a/spec/resolve.spec.ts +++ b/spec/resolve.spec.ts @@ -180,6 +180,41 @@ uriResolvers.forEach((resolver) => { }) }) + describe("URIs with encoded characters (issue #2447)", () => { + it("should resolve the ref", () => { + const schema = { + $ref: "#/definitions/Record%3Cstring%2CPerson%3E", + $schema: "http://json-schema.org/draft-07/schema#", + definitions: { + Person: { + type: "object", + properties: { + firstName: { + type: "string", + description: "The person's first name.", + }, + }, + }, + "Record": { + type: "object", + additionalProperties: { + $ref: "#/definitions/Person", + }, + }, + }, + } + const data = { + joe: { + firstName: "Joe", + }, + } + instances.forEach((ajv) => { + const validate = ajv.compile(schema) + validate(data).should.equal(true) + }) + }) + }) + describe("missing schema error", function () { this.timeout(4000) From a523784388a79ce65e42caf4d2731da36a94b386 Mon Sep 17 00:00:00 2001 From: Jason Ian Green Date: Wed, 10 Jul 2024 08:55:28 +0100 Subject: [PATCH 13/19] fix: changes for @typescript-eslint/array-type rule (#2467) --- lib/types/json-schema.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/types/json-schema.ts b/lib/types/json-schema.ts index 281a38bdb..065c972e5 100644 --- a/lib/types/json-schema.ts +++ b/lib/types/json-schema.ts @@ -108,25 +108,25 @@ type UncheckedJSONSchemaType = ( : UncheckedPropertiesSchema patternProperties?: Record> propertyNames?: Omit, "type"> & {type?: "string"} - dependencies?: {[K in keyof T]?: Readonly<(keyof T)[]> | UncheckedPartialSchema} - dependentRequired?: {[K in keyof T]?: Readonly<(keyof T)[]>} + dependencies?: {[K in keyof T]?: readonly (keyof T)[] | UncheckedPartialSchema} + dependentRequired?: {[K in keyof T]?: readonly (keyof T)[]} dependentSchemas?: {[K in keyof T]?: UncheckedPartialSchema} minProperties?: number maxProperties?: number } & (IsPartial extends true // "required" is not necessary if it's a non-partial type with no required keys // are listed it only asserts that optional cannot be listed. // "required" type does not guarantee that all required properties - ? {required: Readonly<(keyof T)[]>} + ? {required: readonly (keyof T)[]} : [UncheckedRequiredMembers] extends [never] - ? {required?: Readonly[]>} - : {required: Readonly[]>}) + ? {required?: readonly UncheckedRequiredMembers[]} + : {required: readonly UncheckedRequiredMembers[]}) : T extends null ? { type: JSONType<"null", IsPartial> nullable: true } : never) & { - allOf?: Readonly[]> - anyOf?: Readonly[]> - oneOf?: Readonly[]> + allOf?: readonly UncheckedPartialSchema[] + anyOf?: readonly UncheckedPartialSchema[] + oneOf?: readonly UncheckedPartialSchema[] if?: UncheckedPartialSchema then?: UncheckedPartialSchema else?: UncheckedPartialSchema @@ -176,12 +176,12 @@ type Nullable = undefined extends T ? { nullable: true const?: null // any non-null value would fail `const: null`, `null` would fail any other value in const - enum?: Readonly<(T | null)[]> // `null` must be explicitly included in "enum" for `null` to pass + enum?: readonly (T | null)[] // `null` must be explicitly included in "enum" for `null` to pass default?: T | null } : { nullable?: false const?: T - enum?: Readonly + enum?: readonly T[] default?: T } From f7831b41c3a27064c6219f51a1e7371ffb582dfe Mon Sep 17 00:00:00 2001 From: Jason Ian Green Date: Wed, 10 Jul 2024 08:56:10 +0100 Subject: [PATCH 14/19] fixes #2217 - clarify custom keyword naming (#2457) --- docs/api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.md b/docs/api.md index 5dfb0c309..2a0680730 100644 --- a/docs/api.md +++ b/docs/api.md @@ -251,7 +251,7 @@ Add validation keyword to Ajv instance. Keyword should be different from all standard JSON Schema keywords and different from previously defined keywords. There is no way to redefine keywords or to remove keyword definition from the instance. -Keyword must start with a letter, `_` or `$`, and may continue with letters, numbers, `_`, `$`, or `-`. +Keyword must start with an ASCII letter, `_` or `$`, and may continue with ASCII letters, numbers, `_`, `$`, `-`, or `:`. It is recommended to use an application-specific prefix for keywords to avoid current and future name collisions. Example Keywords: From 9050ba1359fb87cd7c143f3c79513ea7624ea443 Mon Sep 17 00:00:00 2001 From: Jason Ian Green Date: Thu, 11 Jul 2024 18:02:58 +0100 Subject: [PATCH 15/19] bump version to 8.17.1 (#2472) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 88effa2e8..17df7b147 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ajv", - "version": "8.16.0", + "version": "8.17.1", "description": "Another JSON Schema Validator", "main": "dist/ajv.js", "types": "dist/ajv.d.ts", From f06766f33ed7291f84c19f22a1286a34475fbdaf Mon Sep 17 00:00:00 2001 From: Jos de Jong Date: Fri, 2 Aug 2024 00:22:50 +0200 Subject: [PATCH 16/19] feat: allow tree-shaking by adding ``"sideEffects": false` to `package.json` (#2480) --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 17df7b147..ba590c17c 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "dist/", ".runkit_example.js" ], + "sideEffects": false, "scripts": { "eslint": "eslint \"lib/**/*.ts\" \"spec/**/*.*s\" --ignore-pattern spec/JSON-Schema-Test-Suite", "prettier:write": "prettier --write \"./**/*.{json,yaml,js,ts}\"", From 69568d08564303e2c32a2de61feb833b41075f96 Mon Sep 17 00:00:00 2001 From: Jason Ian Green Date: Sun, 15 Sep 2024 09:01:40 +0100 Subject: [PATCH 17/19] fix: #2482 Infinity and NaN serialise to null (#2487) * fix: #2482 Infinity and NaN serialise to null * feat: add safeNumbers option * include test for default behavior * change option name to specialNumbers * refactor to be more dry * pr feedback * remove string option --- docs/options.md | 13 ++++++++++ lib/compile/jtd/serialize.ts | 15 ++++++++++-- lib/core.ts | 1 + spec/jtd-schema.spec.ts | 47 ++++++++++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 2 deletions(-) diff --git a/docs/options.md b/docs/options.md index fdce7f571..e8465e815 100644 --- a/docs/options.md +++ b/docs/options.md @@ -204,6 +204,19 @@ Defines how date-time strings are parsed and validated. By default Ajv only allo This option makes JTD validation and parsing more permissive and non-standard. The date strings without time part will be accepted by Ajv, but will be rejected by other JTD validators. ::: +### specialNumbers + +Defines how special case numbers `Infinity`, `-Infinity` and `NaN` are handled. + +Option values: + +- `"fast"` - (default): Do not treat special numbers differently to normal numbers. This is the fastest method but also can produce invalid JSON if the data contains special numbers. +- `"null"` - Special numbers will be serialized to `null` which is the correct behavior according to the JSON spec and is also the same behavior as `JSON.stringify`. + +::: warning The default behavior can produce invalid JSON +Using `specialNumbers: "fast" or undefined` can produce invalid JSON when there are any special case numbers in the data. +::: + ### int32range Can be used to disable range checking for `int32` and `uint32` types. diff --git a/lib/compile/jtd/serialize.ts b/lib/compile/jtd/serialize.ts index 1d228826d..42a47cffc 100644 --- a/lib/compile/jtd/serialize.ts +++ b/lib/compile/jtd/serialize.ts @@ -228,8 +228,19 @@ function serializeString({gen, data}: SerializeCxt): void { gen.add(N.json, _`${useFunc(gen, quote)}(${data})`) } -function serializeNumber({gen, data}: SerializeCxt): void { - gen.add(N.json, _`"" + ${data}`) +function serializeNumber({gen, data, self}: SerializeCxt): void { + const condition = _`${data} === Infinity || ${data} === -Infinity || ${data} !== ${data}` + + if (self.opts.specialNumbers === undefined || self.opts.specialNumbers === "fast") { + gen.add(N.json, _`"" + ${data}`) + } else { + // specialNumbers === "null" + gen.if( + condition, + () => gen.add(N.json, _`null`), + () => gen.add(N.json, _`"" + ${data}`) + ) + } } function serializeRef(cxt: SerializeCxt): void { diff --git a/lib/core.ts b/lib/core.ts index e41ca3e2a..6ceedf541 100644 --- a/lib/core.ts +++ b/lib/core.ts @@ -107,6 +107,7 @@ export interface CurrentOptions { timestamp?: "string" | "date" // JTD only parseDate?: boolean // JTD only allowDate?: boolean // JTD only + specialNumbers?: "fast" | "null" // JTD only $comment?: | true | ((comment: string, schemaPath?: string, rootSchema?: AnySchemaObject) => unknown) diff --git a/spec/jtd-schema.spec.ts b/spec/jtd-schema.spec.ts index f4881b18a..b9cf3ab69 100644 --- a/spec/jtd-schema.spec.ts +++ b/spec/jtd-schema.spec.ts @@ -146,6 +146,53 @@ describe("JSON Type Definition", () => { } }) + describe("serialize special numeric values", () => { + describe("fast", () => { + const ajv = new _AjvJTD({specialNumbers: "fast"}) + + it(`should serialize Infinity to literal`, () => { + const serialize = ajv.compileSerializer({type: "float64"}) + const res = serialize(Infinity) + assert.equal(res, "Infinity") + assert.throws(() => JSON.parse(res)) + }) + it(`should serialize -Infinity to literal`, () => { + const serialize = ajv.compileSerializer({type: "float64"}) + const res = serialize(-Infinity) + assert.equal(res, "-Infinity") + assert.throws(() => JSON.parse(res)) + }) + it(`should serialize NaN to literal`, () => { + const serialize = ajv.compileSerializer({type: "float64"}) + const res = serialize(NaN) + assert.equal(res, "NaN") + assert.throws(() => JSON.parse(res)) + }) + }) + describe("to null", () => { + const ajv = new _AjvJTD({specialNumbers: "null"}) + + it(`should serialize Infinity to null`, () => { + const serialize = ajv.compileSerializer({type: "float64"}) + const res = serialize(Infinity) + assert.equal(res, "null") + assert.equal(JSON.parse(res), null) + }) + it(`should serialize -Infinity to null`, () => { + const serialize = ajv.compileSerializer({type: "float64"}) + const res = serialize(-Infinity) + assert.equal(res, "null") + assert.equal(JSON.parse(res), null) + }) + it(`should serialize NaN to null`, () => { + const serialize = ajv.compileSerializer({type: "float64"}) + const res = serialize(NaN) + assert.equal(res, "null") + assert.equal(JSON.parse(res), null) + }) + }) + }) + describe("parse", () => { let ajv: AjvJTD before(() => (ajv = new _AjvJTD())) From b17ec32cd97542e90ae27231d8a8bce88b9e53b6 Mon Sep 17 00:00:00 2001 From: Renato Monteiro <45536168+monteiro-renato@users.noreply.github.com> Date: Tue, 10 Dec 2024 00:00:15 +0100 Subject: [PATCH 18/19] fix: small grammatical error in managing-schemas.md (#2508) --- docs/guide/managing-schemas.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/managing-schemas.md b/docs/guide/managing-schemas.md index 4c860755c..8923b36e2 100644 --- a/docs/guide/managing-schemas.md +++ b/docs/guide/managing-schemas.md @@ -88,7 +88,7 @@ app.post("/user", async (cxt) => { ::: warning Use single Ajv instance -It recommended to use a single Ajv instance for the whole application, so if you use validation in more than one module, you should: +It is recommended to use a single Ajv instance for the whole application, so if you use validation in more than one module, you should: - require Ajv in a separate module responsible for validation - compile all validators there From 82735a15826a30cc51e97a1bbfb59b3d388e4b98 Mon Sep 17 00:00:00 2001 From: Renato Monteiro <45536168+monteiro-renato@users.noreply.github.com> Date: Mon, 16 Dec 2024 01:05:01 +0100 Subject: [PATCH 19/19] fix: typos in schema-language.md (#2507) Co-authored-by: Jason Ian Green --- docs/guide/schema-language.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/schema-language.md b/docs/guide/schema-language.md index a10fc8f98..f4d4b08e8 100644 --- a/docs/guide/schema-language.md +++ b/docs/guide/schema-language.md @@ -71,7 +71,7 @@ Draft-2019-09 support is provided via a separate export in order to avoid increa With this import Ajv supports the following features: - keywords [`unevaluatedProperties`](../json-schema.md#unevaluatedproperties) and [`unevaluatedItems`](../json-schema.md#unevaluateditems) -- keywords [`dependentRequired`](../json-schema.md#dependentrequired), [`dependentSchemas`](../json-schema.md#dependentschemas), [`maxContains`/`minContain`](../json-schema.md#maxcontains--mincontains) +- keywords [`dependentRequired`](../json-schema.md#dependentrequired), [`dependentSchemas`](../json-schema.md#dependentschemas), [`maxContains`/`minContains`](../json-schema.md#maxcontains-mincontains) - dynamic recursive references with [`recursiveAnchor`/`recursiveReference`] - see [Extending recursive schemas](./combining-schemas.md#extending-recursive-schemas) - draft-2019-09 meta-schema is the default.