diff --git a/README.md b/README.md index 85383024..c55953d8 100644 --- a/README.md +++ b/README.md @@ -556,6 +556,9 @@ These are available from the `@hyperjump/json-schema/experimental` export. Define a vocabulary that maps keyword name to keyword URIs defined using `addKeyword`. +* **hasVocabulary**: (dialectId: string) => boolean; + + Determine whether the vocabulary is supported. * **getKeywordId**: (keywordName: string, dialectId: string) => string Get the identifier for a keyword by its name. @@ -582,11 +585,20 @@ These are available from the `@hyperjump/json-schema/experimental` export. Remove a dialect. You shouldn't need to use this function. It's called for you when you call `unregisterSchema`. +* **getDialectIds** + + This function retrieves the identifiers of all loaded JSON Schema dialects. +* **getDialect**: (dialectId: string) => Record; + + This function retrieves all the keywords appropriate for a particular + dialect. +* **hasDialect**: (dialectId: string) => boolean; + + Determine whether the dialect is supported. * **Validation**: Keyword A Keyword object that represents a "validate" operation. You would use this for compiling and evaluating sub-schemas when defining a custom keyword. - * **getSchema**: (uri: string, browser?: Browser) => Promise\ Get a schema by it's URI taking the local schema registry into account. diff --git a/draft-04/schema.js b/draft-04/schema.js index 130d72f4..0a640dc1 100644 --- a/draft-04/schema.js +++ b/draft-04/schema.js @@ -125,13 +125,19 @@ export default { "uniqueItems": true }, "type": { - "anyOf": [ - { "$ref": "#/definitions/simpleTypes" }, + "type": ["string", "array"], + "allOf": [ + { + "if": { "type": "string" }, + "then": { "$ref": "#/definitions/simpleTypes" } + }, { - "type": "array", - "items": { "$ref": "#/definitions/simpleTypes" }, - "minItems": 1, - "uniqueItems": true + "if": { "type": "array" }, + "then": { + "items": { "$ref": "#/definitions/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } } ] }, diff --git a/draft-06/schema.js b/draft-06/schema.js index 3ebf9910..f0e37c34 100644 --- a/draft-06/schema.js +++ b/draft-06/schema.js @@ -134,13 +134,19 @@ export default { "uniqueItems": true }, "type": { - "anyOf": [ - { "$ref": "#/definitions/simpleTypes" }, + "type": ["string", "array"], + "allOf": [ + { + "if": { "type": "string" }, + "then": { "$ref": "#/definitions/simpleTypes" } + }, { - "type": "array", - "items": { "$ref": "#/definitions/simpleTypes" }, - "minItems": 1, - "uniqueItems": true + "if": { "type": "array" }, + "then": { + "items": { "$ref": "#/definitions/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } } ] }, diff --git a/draft-07/schema.js b/draft-07/schema.js index 83aa3de6..9ae33774 100644 --- a/draft-07/schema.js +++ b/draft-07/schema.js @@ -147,13 +147,19 @@ export default { "uniqueItems": true }, "type": { - "anyOf": [ - { "$ref": "#/definitions/simpleTypes" }, + "type": ["string", "array"], + "allOf": [ + { + "if": { "type": "string" }, + "then": { "$ref": "#/definitions/simpleTypes" } + }, { - "type": "array", - "items": { "$ref": "#/definitions/simpleTypes" }, - "minItems": 1, - "uniqueItems": true + "if": { "type": "array" }, + "then": { + "items": { "$ref": "#/definitions/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } } ] }, diff --git a/draft-2019-09/meta/applicator.js b/draft-2019-09/meta/applicator.js index 3ff97a46..5e6430bd 100644 --- a/draft-2019-09/meta/applicator.js +++ b/draft-2019-09/meta/applicator.js @@ -1,9 +1,6 @@ export default { "$id": "https://json-schema.org/draft/2019-09/meta/applicator", "$schema": "https://json-schema.org/draft/2019-09/schema", - "$vocabulary": { - "https://json-schema.org/draft/2019-09/vocab/applicator": true - }, "$recursiveAnchor": true, "title": "Applicator vocabulary meta-schema", diff --git a/draft-2019-09/meta/content.js b/draft-2019-09/meta/content.js index c62760f3..e8a66767 100644 --- a/draft-2019-09/meta/content.js +++ b/draft-2019-09/meta/content.js @@ -1,14 +1,10 @@ export default { "$id": "https://json-schema.org/draft/2019-09/meta/content", "$schema": "https://json-schema.org/draft/2019-09/schema", - "$vocabulary": { - "https://json-schema.org/draft/2019-09/vocab/content": true - }, "$recursiveAnchor": true, "title": "Content vocabulary meta-schema", - "type": ["object", "boolean"], "properties": { "contentMediaType": { "type": "string" }, "contentEncoding": { "type": "string" }, diff --git a/draft-2019-09/meta/core.js b/draft-2019-09/meta/core.js index ea5fefa1..a01d8c05 100644 --- a/draft-2019-09/meta/core.js +++ b/draft-2019-09/meta/core.js @@ -1,13 +1,9 @@ export default { "$id": "https://json-schema.org/draft/2019-09/meta/core", "$schema": "https://json-schema.org/draft/2019-09/schema", - "$vocabulary": { - "https://json-schema.org/draft/2019-09/vocab/core": true - }, "$recursiveAnchor": true, "title": "Core vocabulary meta-schema", - "type": ["object", "boolean"], "properties": { "$id": { "type": "string", diff --git a/draft-2019-09/meta/format.js b/draft-2019-09/meta/format.js index 888ddf1b..faa5fdfc 100644 --- a/draft-2019-09/meta/format.js +++ b/draft-2019-09/meta/format.js @@ -1,13 +1,9 @@ export default { "$id": "https://json-schema.org/draft/2019-09/meta/format", "$schema": "https://json-schema.org/draft/2019-09/schema", - "$vocabulary": { - "https://json-schema.org/draft/2019-09/vocab/format": true - }, "$recursiveAnchor": true, "title": "Format vocabulary meta-schema", - "type": ["object", "boolean"], "properties": { "format": { "type": "string" } } diff --git a/draft-2019-09/meta/meta-data.js b/draft-2019-09/meta/meta-data.js index 63f0c4c2..ff5ac181 100644 --- a/draft-2019-09/meta/meta-data.js +++ b/draft-2019-09/meta/meta-data.js @@ -1,14 +1,10 @@ export default { "$id": "https://json-schema.org/draft/2019-09/meta/meta-data", "$schema": "https://json-schema.org/draft/2019-09/schema", - "$vocabulary": { - "https://json-schema.org/draft/2019-09/vocab/meta-data": true - }, "$recursiveAnchor": true, "title": "Meta-data vocabulary meta-schema", - "type": ["object", "boolean"], "properties": { "title": { "type": "string" diff --git a/draft-2019-09/meta/validation.js b/draft-2019-09/meta/validation.js index 5dec0619..0d67c712 100644 --- a/draft-2019-09/meta/validation.js +++ b/draft-2019-09/meta/validation.js @@ -1,13 +1,9 @@ export default { "$id": "https://json-schema.org/draft/2019-09/meta/validation", "$schema": "https://json-schema.org/draft/2019-09/schema", - "$vocabulary": { - "https://json-schema.org/draft/2019-09/vocab/validation": true - }, "$recursiveAnchor": true, "title": "Validation vocabulary meta-schema", - "type": ["object", "boolean"], "properties": { "multipleOf": { "type": "number", @@ -57,13 +53,19 @@ export default { "items": true }, "type": { - "anyOf": [ - { "$ref": "#/$defs/simpleTypes" }, + "type": ["string", "array"], + "allOf": [ + { + "if": { "type": "string" }, + "then": { "$ref": "#/$defs/simpleTypes" } + }, { - "type": "array", - "items": { "$ref": "#/$defs/simpleTypes" }, - "minItems": 1, - "uniqueItems": true + "if": { "type": "array" }, + "then": { + "items": { "$ref": "#/$defs/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } } ] } diff --git a/draft-2019-09/schema.js b/draft-2019-09/schema.js index 76add3e1..e0e1d4d1 100644 --- a/draft-2019-09/schema.js +++ b/draft-2019-09/schema.js @@ -26,7 +26,9 @@ export default { "$comment": "While no longer an official keyword as it is replaced by $defs, this keyword is retained in the meta-schema to prevent incompatible extensions as it remains in common use.", "type": "object", "additionalProperties": { "$recursiveRef": "#" }, - "default": {} + "default": {}, + "deprecated": true, + "x-deprecationMessage": "Use '$defs'. 'definitions' was replaced with '$defs' in 2019-09" }, "dependencies": { "$comment": "\"dependencies\" is no longer a keyword, but schema authors should avoid redefining it to facilitate a smooth transition to \"dependentSchemas\" and \"dependentRequired\"", diff --git a/draft-2020-12/meta/applicator.js b/draft-2020-12/meta/applicator.js index cce8a3a2..754ab73b 100644 --- a/draft-2020-12/meta/applicator.js +++ b/draft-2020-12/meta/applicator.js @@ -4,7 +4,6 @@ export default { "$dynamicAnchor": "meta", "title": "Applicator vocabulary meta-schema", - "type": ["object", "boolean"], "properties": { "prefixItems": { "$ref": "#/$defs/schemaArray" }, "items": { "$dynamicRef": "#meta" }, diff --git a/draft-2020-12/meta/content.js b/draft-2020-12/meta/content.js index f6bd4171..8c542eb2 100644 --- a/draft-2020-12/meta/content.js +++ b/draft-2020-12/meta/content.js @@ -5,7 +5,6 @@ export default { "title": "Content vocabulary meta-schema", - "type": ["object", "boolean"], "properties": { "contentMediaType": { "type": "string" }, "contentEncoding": { "type": "string" }, diff --git a/draft-2020-12/meta/core.js b/draft-2020-12/meta/core.js index f2473e40..80dd8f17 100644 --- a/draft-2020-12/meta/core.js +++ b/draft-2020-12/meta/core.js @@ -4,7 +4,6 @@ export default { "$dynamicAnchor": "meta", "title": "Core vocabulary meta-schema", - "type": ["object", "boolean"], "properties": { "$id": { "type": "string", diff --git a/draft-2020-12/meta/format-annotation.js b/draft-2020-12/meta/format-annotation.js index 4d7675d6..f20e1ec3 100644 --- a/draft-2020-12/meta/format-annotation.js +++ b/draft-2020-12/meta/format-annotation.js @@ -4,7 +4,6 @@ export default { "$dynamicAnchor": "meta", "title": "Format vocabulary meta-schema for annotation results", - "type": ["object", "boolean"], "properties": { "format": { "type": "string" } } diff --git a/draft-2020-12/meta/format-assertion.js b/draft-2020-12/meta/format-assertion.js index 09248f38..b253ec71 100644 --- a/draft-2020-12/meta/format-assertion.js +++ b/draft-2020-12/meta/format-assertion.js @@ -4,7 +4,6 @@ export default { "$dynamicAnchor": "meta", "title": "Format vocabulary meta-schema for assertion results", - "type": ["object", "boolean"], "properties": { "format": { "type": "string" } } diff --git a/draft-2020-12/meta/meta-data.js b/draft-2020-12/meta/meta-data.js index 7da3361f..8c0afcbe 100644 --- a/draft-2020-12/meta/meta-data.js +++ b/draft-2020-12/meta/meta-data.js @@ -5,7 +5,6 @@ export default { "title": "Meta-data vocabulary meta-schema", - "type": ["object", "boolean"], "properties": { "title": { "type": "string" diff --git a/draft-2020-12/meta/unevaluated.js b/draft-2020-12/meta/unevaluated.js index 1a9ab172..289a3e81 100644 --- a/draft-2020-12/meta/unevaluated.js +++ b/draft-2020-12/meta/unevaluated.js @@ -4,7 +4,6 @@ export default { "$dynamicAnchor": "meta", "title": "Unevaluated applicator vocabulary meta-schema", - "type": ["object", "boolean"], "properties": { "unevaluatedItems": { "$dynamicRef": "#meta" }, "unevaluatedProperties": { "$dynamicRef": "#meta" } diff --git a/draft-2020-12/meta/validation.js b/draft-2020-12/meta/validation.js index 944ea40a..0f22cef6 100644 --- a/draft-2020-12/meta/validation.js +++ b/draft-2020-12/meta/validation.js @@ -4,7 +4,6 @@ export default { "$dynamicAnchor": "meta", "title": "Validation vocabulary meta-schema", - "type": ["object", "boolean"], "properties": { "multipleOf": { "type": "number", @@ -54,13 +53,19 @@ export default { "items": true }, "type": { - "anyOf": [ - { "$ref": "#/$defs/simpleTypes" }, + "type": ["string", "array"], + "allOf": [ { - "type": "array", - "items": { "$ref": "#/$defs/simpleTypes" }, - "minItems": 1, - "uniqueItems": true + "if": { "type": "string" }, + "then": { "$ref": "#/$defs/simpleTypes" } + }, + { + "if": { "type": "array" }, + "then": { + "items": { "$ref": "#/$defs/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } } ] } diff --git a/draft-2020-12/schema.js b/draft-2020-12/schema.js index d01a036d..cca26806 100644 --- a/draft-2020-12/schema.js +++ b/draft-2020-12/schema.js @@ -28,7 +28,9 @@ export default { "$comment": "While no longer an official keyword as it is replaced by $defs, this keyword is retained in the meta-schema to prevent incompatible extensions as it remains in common use.", "type": "object", "additionalProperties": { "$dynamicRef": "#meta" }, - "default": {} + "default": {}, + "deprecated": true, + "x-deprecationMessage": "Use '$defs'. 'definitions' was replaced with '$defs' in 2019-09" }, "dependencies": { "$comment": "\"dependencies\" is no longer a keyword, but schema authors should avoid redefining it to facilitate a smooth transition to \"dependentSchemas\" and \"dependentRequired\"", diff --git a/lib/experimental.d.ts b/lib/experimental.d.ts index 6cdaa96e..e5bf118c 100644 --- a/lib/experimental.d.ts +++ b/lib/experimental.d.ts @@ -62,9 +62,12 @@ export const getKeyword: (id: string) => Keyword; export const getKeywordByName: (keywordName: string, dialectId: string) => Keyword; export const getKeywordId: (keywordName: string, dialectId: string) => string; export const defineVocabulary: (id: string, keywords: Record) => void; +export const hasVocabulary: (vocabularyId: string) => boolean; export const loadDialect: (dialectId: string, dialect: Record, allowUnknownKeywords?: boolean) => void; export const unloadDialect: (dialectId: string) => void; export const hasDialect: (dialectId: string) => boolean; +export const getDialectIds: () => string[]; +export const getDialect: (dialectId: string) => Record; export type Keyword = { id: string; diff --git a/lib/experimental.js b/lib/experimental.js index ed548d98..9056da54 100644 --- a/lib/experimental.js +++ b/lib/experimental.js @@ -1,8 +1,8 @@ export { compile, interpret, BASIC, DETAILED } from "./core.js"; export { addKeyword, getKeyword, getKeywordByName, getKeywordName, getKeywordId, - defineVocabulary, - loadDialect, unloadDialect, hasDialect + defineVocabulary, hasVocabulary, + loadDialect, unloadDialect, hasDialect, getDialectIds, getDialect } from "./keywords.js"; export { getSchema, toSchema, canonicalUri, buildSchemaDocument } from "./schema.js"; export { default as Validation } from "./keywords/validation.js"; diff --git a/lib/get-dialect-ids.spec.ts b/lib/get-dialect-ids.spec.ts new file mode 100644 index 00000000..97e8816d --- /dev/null +++ b/lib/get-dialect-ids.spec.ts @@ -0,0 +1,67 @@ +import { test, expect, describe } from "vitest"; +import { getDialectIds } from "./experimental.js"; +import { registerSchema } from "./schema.js"; +import "../draft-2020-12/index.js"; +import "../draft-2019-09/index.js"; +import "../draft-04/index.js"; +import "../draft-06/index.js"; +import "../draft-07/index.js"; +import "../openapi-3-0/index.js"; +import "../openapi-3-1/index.js"; +import "../stable/index.js"; + + +describe("getDialectIds function", () => { + test("should return only imported schema in the array if no custom dialects are loaded", () => { + const dialectIds = getDialectIds(); + expect(dialectIds).toEqual([ + "https://json-schema.org/draft/2020-12/schema", + "https://json-schema.org/draft/2019-09/schema", + "http://json-schema.org/draft-04/schema", + "http://json-schema.org/draft-06/schema", + "http://json-schema.org/draft-07/schema", + "https://spec.openapis.org/oas/3.0/dialect", + "https://spec.openapis.org/oas/3.0/schema", + "https://spec.openapis.org/oas/3.1/dialect/base", + "https://spec.openapis.org/oas/3.1/schema-base", + "https://spec.openapis.org/oas/3.1/schema-base/latest", + "https://spec.openapis.org/oas/3.1/schema-draft-2020-12", + "https://spec.openapis.org/oas/3.1/schema-draft-2019-09", + "https://spec.openapis.org/oas/3.1/schema-draft-07", + "https://spec.openapis.org/oas/3.1/schema-draft-06", + "https://spec.openapis.org/oas/3.1/schema-draft-04", + "https://json-schema.org/validation" + ]); + }); + + test("returns an array of dialect identifiers that are either imported in the file or loaded as custom dialects", () => { + registerSchema({ + "$id": "http://example.com/dialect1", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/core": true, + "https://json-schema.org/draft/2020-12/vocab/applicator": true + } + }); + const dialectIds = getDialectIds(); + expect(dialectIds).toEqual([ + "https://json-schema.org/draft/2020-12/schema", + "https://json-schema.org/draft/2019-09/schema", + "http://json-schema.org/draft-04/schema", + "http://json-schema.org/draft-06/schema", + "http://json-schema.org/draft-07/schema", + "https://spec.openapis.org/oas/3.0/dialect", + "https://spec.openapis.org/oas/3.0/schema", + "https://spec.openapis.org/oas/3.1/dialect/base", + "https://spec.openapis.org/oas/3.1/schema-base", + "https://spec.openapis.org/oas/3.1/schema-base/latest", + "https://spec.openapis.org/oas/3.1/schema-draft-2020-12", + "https://spec.openapis.org/oas/3.1/schema-draft-2019-09", + "https://spec.openapis.org/oas/3.1/schema-draft-07", + "https://spec.openapis.org/oas/3.1/schema-draft-06", + "https://spec.openapis.org/oas/3.1/schema-draft-04", + "https://json-schema.org/validation", + "http://example.com/dialect1" + ]); + }); +}); diff --git a/lib/keywords.js b/lib/keywords.js index 59bcac97..3ee174b7 100644 --- a/lib/keywords.js +++ b/lib/keywords.js @@ -34,6 +34,8 @@ export const defineVocabulary = (id, keywords) => { _vocabularies[id] = keywords; }; +export const hasVocabulary = (vocabularyId) => vocabularyId in _vocabularies; + const _dialects = {}; export const getKeywordId = (keyword, dialectId) => { @@ -53,7 +55,7 @@ export const getKeywordName = (dialectId, keywordId) => { } }; -const getDialect = (dialectId) => { +export const getDialect = (dialectId) => { if (!(dialectId in _dialects)) { throw Error(`Encountered unknown dialect '${dialectId}'`); } @@ -92,3 +94,5 @@ export const unloadDialect = (dialectId) => { delete _dialects[dialectId]; } }; + +export const getDialectIds = () => Object.keys(_dialects); diff --git a/openapi-3-1/meta/base.js b/openapi-3-1/meta/base.js index fdaba27e..4ee7ae74 100644 --- a/openapi-3-1/meta/base.js +++ b/openapi-3-1/meta/base.js @@ -4,7 +4,6 @@ export default { "$dynamicAnchor": "meta", "title": "OAS Base vocabulary", - "type": ["object", "boolean"], "properties": { "example": true, "discriminator": { "$ref": "#/$defs/discriminator" }, diff --git a/stable/meta/core.js b/stable/meta/core.js index c5400508..c13c84fc 100644 --- a/stable/meta/core.js +++ b/stable/meta/core.js @@ -4,7 +4,6 @@ export default { "$dynamicAnchor": "meta", - "type": ["object", "boolean"], "properties": { "$id": { "type": "string", diff --git a/stable/validation.js b/stable/validation.js index ba5c9548..3b10fded 100644 --- a/stable/validation.js +++ b/stable/validation.js @@ -13,6 +13,7 @@ export default { "$dynamicAnchor": "meta", + "type": ["object", "boolean"], "allOf": [ { "$ref": "meta/core" }, { "$ref": "meta/applicator" },