@hyperjump/json-schema
Advanced tools
Comparing version 1.3.1 to 1.4.0
@@ -73,4 +73,3 @@ import { v4 as uuid } from "uuid"; | ||
Validation.collectExternalIds = (schemaUri, externalIds, ast, dynamicAnchors) => { | ||
const nodes = ast[schemaUri][2]; | ||
if (externalIds.has(schemaUri) || typeof nodes === "boolean") { | ||
if (externalIds.has(schemaUri) || typeof ast[schemaUri] === "boolean") { | ||
return; | ||
@@ -81,3 +80,3 @@ } | ||
const id = toAbsoluteUri(schemaUri); | ||
for (const [keywordId, , keywordValue] of nodes) { | ||
for (const [keywordId, , keywordValue] of ast[schemaUri]) { | ||
const keyword = getKeyword(keywordId); | ||
@@ -84,0 +83,0 @@ |
@@ -21,3 +21,3 @@ import * as Schema from "../schema.js"; | ||
const interpret = ([propertyNames, propertyNamePatterns, additionalProperties], instance, ast, dynamicAnchors) => { | ||
const interpret = ([propertyNames, propertyNamePatterns, additionalProperties], instance, ast, dynamicAnchors, quiet) => { | ||
if (!Instance.typeOf(instance, "object")) { | ||
@@ -29,9 +29,24 @@ return true; | ||
.filter(([propertyName]) => !propertyNames.includes(propertyName) && !propertyNamePatterns.some((pattern) => pattern.test(propertyName))) | ||
.every(([, property]) => Validation.interpret(additionalProperties, property, ast, dynamicAnchors)); | ||
.every(([, property]) => Validation.interpret(additionalProperties, property, ast, dynamicAnchors, quiet)); | ||
}; | ||
const collectEvaluatedProperties = (keywordValue, instance, ast, dynamicAnchors) => { | ||
return interpret(keywordValue, instance, ast, dynamicAnchors) && [new RegExp("")]; | ||
const collectEvaluatedProperties = ([propertyNames, propertyNamePatterns, additionalProperties], instance, ast, dynamicAnchors) => { | ||
if (!Instance.typeOf(instance, "object")) { | ||
return true; | ||
} | ||
const evaluatedPropertyNames = new Set(); | ||
for (const [propertyName, property] of Instance.entries(instance)) { | ||
if (!propertyNames.includes(propertyName) && !propertyNamePatterns.some((pattern) => pattern.test(propertyName))) { | ||
if (!Validation.interpret(additionalProperties, property, ast, dynamicAnchors, true)) { | ||
return false; | ||
} | ||
evaluatedPropertyNames.add(propertyName); | ||
} | ||
} | ||
return evaluatedPropertyNames; | ||
}; | ||
export default { id, compile, interpret, collectEvaluatedProperties }; |
@@ -13,4 +13,4 @@ import * as Pact from "@hyperjump/pact"; | ||
const interpret = (allOf, instance, ast, dynamicAnchors) => { | ||
return allOf.every((schemaUrl) => Validation.interpret(schemaUrl, instance, ast, dynamicAnchors)); | ||
const interpret = (allOf, instance, ast, dynamicAnchors, quiet) => { | ||
return allOf.every((schemaUrl) => Validation.interpret(schemaUrl, instance, ast, dynamicAnchors, quiet)); | ||
}; | ||
@@ -17,0 +17,0 @@ |
@@ -13,4 +13,4 @@ import * as Pact from "@hyperjump/pact"; | ||
const interpret = (anyOf, instance, ast, dynamicAnchors) => { | ||
const matches = anyOf.filter((schemaUrl) => Validation.interpret(schemaUrl, instance, ast, dynamicAnchors)); | ||
const interpret = (anyOf, instance, ast, dynamicAnchors, quiet) => { | ||
const matches = anyOf.filter((schemaUrl) => Validation.interpret(schemaUrl, instance, ast, dynamicAnchors, quiet)); | ||
return matches.length > 0; | ||
@@ -17,0 +17,0 @@ }; |
@@ -23,3 +23,3 @@ import * as Schema from "../schema.js"; | ||
const interpret = ({ contains, minContains, maxContains }, instance, ast, dynamicAnchors) => { | ||
const interpret = ({ contains, minContains, maxContains }, instance, ast, dynamicAnchors, quiet) => { | ||
if (!Instance.typeOf(instance, "array") && !Instance.typeOf(instance, "object")) { | ||
@@ -30,3 +30,3 @@ return true; | ||
const matches = Instance.entries(instance).reduce((matches, [, item]) => { | ||
return Validation.interpret(contains, item, ast, dynamicAnchors) ? matches + 1 : matches; | ||
return Validation.interpret(contains, item, ast, dynamicAnchors, quiet) ? matches + 1 : matches; | ||
}, 0); | ||
@@ -36,21 +36,21 @@ return matches >= minContains && matches <= maxContains; | ||
const collectEvaluatedItems = (keywordValue, instance, ast, dynamicAnchors) => { | ||
return interpret(keywordValue, instance, ast, dynamicAnchors) | ||
&& Instance.typeOf(instance, "array") | ||
&& Instance.reduce((matchedIndexes, item, itemIndex) => { | ||
return Validation.interpret(keywordValue.contains, item, ast, dynamicAnchors) ? matchedIndexes.add(itemIndex) : matchedIndexes; | ||
}, new Set(), instance); | ||
}; | ||
const collectEvaluatedProperties = (keywordValue, instance, ast, dynamicAnchors) => { | ||
return interpret(keywordValue, instance, ast, dynamicAnchors) | ||
return interpret(keywordValue, instance, ast, dynamicAnchors, true) | ||
&& Instance.typeOf(instance, "object") | ||
&& Instance.entries(instance).reduce((matchedPropertyNames, [propertyName, item]) => { | ||
if (Validation.interpret(keywordValue.contains, item, ast, dynamicAnchors)) { | ||
matchedPropertyNames.push(propertyName); | ||
if (Validation.interpret(keywordValue.contains, item, ast, dynamicAnchors, true)) { | ||
matchedPropertyNames.add(propertyName); | ||
} | ||
return matchedPropertyNames; | ||
}, [], instance); | ||
}, new Set(), instance); | ||
}; | ||
const collectEvaluatedItems = (keywordValue, instance, ast, dynamicAnchors) => { | ||
return interpret(keywordValue, instance, ast, dynamicAnchors, true) | ||
&& Instance.typeOf(instance, "array") | ||
&& Instance.reduce((matchedIndexes, item, itemIndex) => { | ||
return Validation.interpret(keywordValue.contains, item, ast, dynamicAnchors, true) ? matchedIndexes.add(itemIndex) : matchedIndexes; | ||
}, new Set(), instance); | ||
}; | ||
export default { id, compile, interpret, collectEvaluatedItems, collectEvaluatedProperties }; |
@@ -1,4 +0,9 @@ | ||
import metaData from "./meta-data.js"; | ||
import * as Schema from "../schema.js"; | ||
export default { id: "https://json-schema.org/keyword/contentSchema", ...metaData }; | ||
const id = "https://json-schema.org/keyword/contentSchema"; | ||
const compile = (contentSchema) => Schema.uri(contentSchema); | ||
const interpret = () => true; | ||
export default { id, compile, interpret }; |
@@ -15,7 +15,7 @@ import * as Pact from "@hyperjump/pact"; | ||
const interpret = (dependentSchemas, instance, ast, dynamicAnchors) => { | ||
const interpret = (dependentSchemas, instance, ast, dynamicAnchors, quiet) => { | ||
const value = Instance.value(instance); | ||
return !Instance.typeOf(instance, "object") || dependentSchemas.every(([propertyName, dependentSchema]) => { | ||
return !(propertyName in value) || Validation.interpret(dependentSchema, instance, ast, dynamicAnchors); | ||
return !(propertyName in value) || Validation.interpret(dependentSchema, instance, ast, dynamicAnchors, quiet); | ||
}); | ||
@@ -25,12 +25,22 @@ }; | ||
const collectEvaluatedProperties = (dependentSchemas, instance, ast, dynamicAnchors) => { | ||
return dependentSchemas.reduce((acc, [propertyName, dependentSchema]) => { | ||
if (!acc || !Instance.has(propertyName, instance)) { | ||
return acc; | ||
const value = Instance.value(instance); | ||
if (!Instance.typeOf(instance, "object")) { | ||
return false; | ||
} | ||
const evaluatedPropertyNames = new Set(); | ||
for (const [propertyName, dependentSchema] of dependentSchemas) { | ||
if (propertyName in value) { | ||
const propertyNames = Validation.collectEvaluatedProperties(dependentSchema, instance, ast, dynamicAnchors); | ||
if (propertyNames === false) { | ||
return false; | ||
} | ||
propertyNames.forEach(Set.prototype.add.bind(evaluatedPropertyNames)); | ||
} | ||
} | ||
const propertyNames = Validation.collectEvaluatedProperties(dependentSchema, instance, ast, dynamicAnchors); | ||
return propertyNames !== false && acc.concat(propertyNames); | ||
}, []); | ||
return evaluatedPropertyNames; | ||
}; | ||
export default { id, compile, interpret, collectEvaluatedProperties }; |
@@ -10,7 +10,7 @@ import * as Schema from "../schema.js"; | ||
const interpret = (fragment, instance, ast, dynamicAnchors) => { | ||
const interpret = (fragment, instance, ast, dynamicAnchors, quiet) => { | ||
if (!(fragment in dynamicAnchors)) { | ||
throw Error(`No dynamic anchor found for "${fragment}"`); | ||
} | ||
return Validation.interpret(dynamicAnchors[fragment], instance, ast, dynamicAnchors); | ||
return Validation.interpret(dynamicAnchors[fragment], instance, ast, dynamicAnchors, quiet); | ||
}; | ||
@@ -17,0 +17,0 @@ |
import * as Schema from "../schema.js"; | ||
import { getKeywordName, getKeyword } from "../keywords.js"; | ||
import { getKeywordName } from "../keywords.js"; | ||
import Validation from "./validation.js"; | ||
@@ -18,31 +18,24 @@ | ||
const interpret = ([guard, block], instance, ast, dynamicAnchors) => { | ||
return guard === undefined || quietInterpretSchema(guard, instance, ast, dynamicAnchors) || Validation.interpret(block, instance, ast, dynamicAnchors); | ||
const interpret = ([ifSchema, elseSchema], instance, ast, dynamicAnchors, quiet) => { | ||
return ifSchema === undefined | ||
|| Validation.interpret(ifSchema, instance, ast, dynamicAnchors, true) | ||
|| Validation.interpret(elseSchema, instance, ast, dynamicAnchors, quiet); | ||
}; | ||
// Interpret a schema without events being emitted | ||
const quietInterpretSchema = (url, instance, ast, dynamicAnchors) => { | ||
const nodes = ast[url][2]; | ||
return typeof nodes === "boolean" ? nodes : nodes.every(([keywordId, , keywordValue]) => { | ||
return getKeyword(keywordId).interpret(keywordValue, instance, ast, dynamicAnchors); | ||
}); | ||
}; | ||
const collectEvaluatedProperties = ([guard, block], instance, ast, dynamicAnchors) => { | ||
if (guard === undefined || quietInterpretSchema(guard, instance, ast, dynamicAnchors)) { | ||
return []; | ||
const collectEvaluatedProperties = ([ifSchema, elseSchema], instance, ast, dynamicAnchors) => { | ||
if (ifSchema === undefined || Validation.interpret(ifSchema, instance, ast, dynamicAnchors, true)) { | ||
return new Set(); | ||
} | ||
return Validation.collectEvaluatedProperties(block, instance, ast, dynamicAnchors); | ||
return Validation.collectEvaluatedProperties(elseSchema, instance, ast, dynamicAnchors); | ||
}; | ||
const collectEvaluatedItems = ([guard, block], instance, ast, dynamicAnchors) => { | ||
if (guard === undefined || quietInterpretSchema(guard, instance, ast, dynamicAnchors)) { | ||
const collectEvaluatedItems = ([ifSchema, elseSchema], instance, ast, dynamicAnchors) => { | ||
if (ifSchema === undefined || Validation.interpret(ifSchema, instance, ast, dynamicAnchors, true)) { | ||
return new Set(); | ||
} | ||
return Validation.collectEvaluatedItems(block, instance, ast, dynamicAnchors); | ||
return Validation.collectEvaluatedItems(elseSchema, instance, ast, dynamicAnchors); | ||
}; | ||
export default { id, compile, interpret, collectEvaluatedProperties, collectEvaluatedItems }; |
@@ -8,4 +8,4 @@ import Validation from "./validation.js"; | ||
const interpret = (ifSchema, instance, ast, dynamicAnchors) => { | ||
Validation.interpret(ifSchema, instance, ast, dynamicAnchors); | ||
const interpret = (ifSchema, instance, ast, dynamicAnchors, quiet) => { | ||
Validation.interpret(ifSchema, instance, ast, dynamicAnchors, quiet); | ||
return true; | ||
@@ -12,0 +12,0 @@ }; |
@@ -17,3 +17,3 @@ import * as Schema from "../schema.js"; | ||
const interpret = ([numberOfPrefixItems, items], instance, ast, dynamicAnchors) => { | ||
const interpret = ([numberOfPrefixItems, items], instance, ast, dynamicAnchors, quiet) => { | ||
if (!Instance.typeOf(instance, "array")) { | ||
@@ -23,7 +23,7 @@ return true; | ||
return Instance.every((item, ndx) => ndx < numberOfPrefixItems || Validation.interpret(items, item, ast, dynamicAnchors), instance); | ||
return Instance.every((item, ndx) => ndx < numberOfPrefixItems || Validation.interpret(items, item, ast, dynamicAnchors, quiet), instance); | ||
}; | ||
const collectEvaluatedItems = (keywordValue, instance, ast, dynamicAnchors) => { | ||
if (!interpret(keywordValue, instance, ast, dynamicAnchors)) { | ||
if (!interpret(keywordValue, instance, ast, dynamicAnchors, true)) { | ||
return false; | ||
@@ -30,0 +30,0 @@ } |
@@ -7,4 +7,4 @@ import Validation from "./validation.js"; | ||
const compile = Validation.compile; | ||
const interpret = (not, instance, ast, dynamicAnchors) => !Validation.interpret(not, instance, ast, dynamicAnchors); | ||
const interpret = (not, instance, ast, dynamicAnchors, quiet) => !Validation.interpret(not, instance, ast, dynamicAnchors, quiet); | ||
export default { id, compile, interpret }; |
@@ -12,6 +12,6 @@ import * as Schema from "../schema.js"; | ||
const interpret = (oneOf, instance, ast, dynamicAnchors) => { | ||
const interpret = (oneOf, instance, ast, dynamicAnchors, quiet) => { | ||
let validCount = 0; | ||
for (const schemaUrl of oneOf) { | ||
if (Validation.interpret(schemaUrl, instance, ast, dynamicAnchors)) { | ||
if (Validation.interpret(schemaUrl, instance, ast, dynamicAnchors, quiet)) { | ||
validCount++; | ||
@@ -18,0 +18,0 @@ } |
@@ -15,7 +15,7 @@ import * as Pact from "@hyperjump/pact"; | ||
const interpret = (patternProperties, instance, ast, dynamicAnchors) => { | ||
const interpret = (patternProperties, instance, ast, dynamicAnchors, quiet) => { | ||
return !Instance.typeOf(instance, "object") || patternProperties.every(([pattern, schemaUrl]) => { | ||
return Instance.entries(instance) | ||
.filter(([propertyName]) => pattern.test(propertyName)) | ||
.every(([, propertyValue]) => Validation.interpret(schemaUrl, propertyValue, ast, dynamicAnchors)); | ||
.every(([, propertyValue]) => Validation.interpret(schemaUrl, propertyValue, ast, dynamicAnchors, quiet)); | ||
}); | ||
@@ -25,5 +25,22 @@ }; | ||
const collectEvaluatedProperties = (patternProperties, instance, ast, dynamicAnchors) => { | ||
return interpret(patternProperties, instance, ast, dynamicAnchors) && patternProperties.map(([pattern]) => pattern); | ||
if (!Instance.typeOf(instance, "object")) { | ||
return false; | ||
} | ||
const evaluatedPropertyNames = new Set(); | ||
for (const [pattern, propertySchema] of patternProperties) { | ||
for (const [propertyName, property] of Instance.entries(instance)) { | ||
if (pattern.test(propertyName)) { | ||
if (!Validation.interpret(propertySchema, property, ast, dynamicAnchors, true)) { | ||
return false; | ||
} | ||
evaluatedPropertyNames.add(propertyName); | ||
} | ||
} | ||
} | ||
return evaluatedPropertyNames; | ||
}; | ||
export default { id, compile, interpret, collectEvaluatedProperties }; |
@@ -16,3 +16,3 @@ import * as Pact from "@hyperjump/pact"; | ||
const interpret = (items, instance, ast, dynamicAnchors) => { | ||
const interpret = (items, instance, ast, dynamicAnchors, quiet) => { | ||
if (!Instance.typeOf(instance, "array")) { | ||
@@ -22,9 +22,9 @@ return true; | ||
return Instance.every((item, ndx) => !(ndx in items) || Validation.interpret(items[ndx], item, ast, dynamicAnchors), instance); | ||
return Instance.every((item, ndx) => !(ndx in items) || Validation.interpret(items[ndx], item, ast, dynamicAnchors, quiet), instance); | ||
}; | ||
const collectEvaluatedItems = (items, instance, ast, dynamicAnchors) => { | ||
return interpret(items, instance, ast, dynamicAnchors) && new Set(items.map((item, ndx) => ndx)); | ||
return interpret(items, instance, ast, dynamicAnchors, true) && new Set(items.map((item, ndx) => ndx)); | ||
}; | ||
export default { id, compile, interpret, collectEvaluatedItems }; |
@@ -17,15 +17,27 @@ import * as Pact from "@hyperjump/pact"; | ||
const interpret = (properties, instance, ast, dynamicAnchors) => { | ||
const interpret = (properties, instance, ast, dynamicAnchors, quiet) => { | ||
return !Instance.typeOf(instance, "object") || Instance.entries(instance) | ||
.filter(([propertyName]) => propertyName in properties) | ||
.every(([propertyName, schemaUrl]) => Validation.interpret(properties[propertyName], schemaUrl, ast, dynamicAnchors)); | ||
.every(([propertyName, schemaUrl]) => Validation.interpret(properties[propertyName], schemaUrl, ast, dynamicAnchors, quiet)); | ||
}; | ||
const collectEvaluatedProperties = (properties, instance, ast, dynamicAnchors) => { | ||
return interpret(properties, instance, ast, dynamicAnchors) && Object.keys(properties) | ||
.map((propertyName) => new RegExp(`^${escapeRegExp(propertyName)}$`)); | ||
if (!Instance.typeOf(instance, "object")) { | ||
return false; | ||
} | ||
const evaluatedPropertyNames = new Set(); | ||
for (const [propertyName, property] of Instance.entries(instance)) { | ||
if (propertyName in properties) { | ||
if (!Validation.interpret(properties[propertyName], property, ast, dynamicAnchors, true)) { | ||
return false; | ||
} | ||
evaluatedPropertyNames.add(propertyName); | ||
} | ||
} | ||
return evaluatedPropertyNames; | ||
}; | ||
const escapeRegExp = (string) => string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); | ||
export default { id, compile, interpret, collectEvaluatedProperties }; |
@@ -26,3 +26,3 @@ import * as Pact from "@hyperjump/pact"; | ||
const interpret = (propertyDependencies, instance, ast, dynamicAnchors) => { | ||
const interpret = (propertyDependencies, instance, ast, dynamicAnchors, quiet) => { | ||
return !Instance.typeOf(instance, "object") || Object.entries(propertyDependencies).every(([propertyName, valueMappings]) => { | ||
@@ -32,3 +32,3 @@ const propertyValue = Instance.value(instance)[propertyName]; | ||
|| !(propertyValue in valueMappings) | ||
|| Validation.interpret(valueMappings[propertyValue], instance, ast, dynamicAnchors); | ||
|| Validation.interpret(valueMappings[propertyValue], instance, ast, dynamicAnchors, quiet); | ||
}); | ||
@@ -35,0 +35,0 @@ }; |
@@ -11,5 +11,5 @@ import * as Instance from "../instance.js"; | ||
return !Instance.typeOf(instance, "object") || Instance.keys(instance) | ||
.every((key) => Validation.interpret(propertyNames, Instance.cons(key), ast, dynamicAnchors)); | ||
.every((key) => Validation.interpret(propertyNames, Instance.cons(key), ast, dynamicAnchors, true)); | ||
}; | ||
export default { id, compile, interpret }; |
import * as Schema from "../schema.js"; | ||
import { getKeywordName, getKeyword } from "../keywords.js"; | ||
import { getKeywordName } from "../keywords.js"; | ||
import Validation from "./validation.js"; | ||
@@ -18,31 +18,24 @@ | ||
const interpret = ([guard, block], instance, ast, dynamicAnchors) => { | ||
return guard === undefined || !quietInterpretSchema(guard, instance, ast, dynamicAnchors) || Validation.interpret(block, instance, ast, dynamicAnchors); | ||
const interpret = ([ifSchema, thenSchema], instance, ast, dynamicAnchors, quiet) => { | ||
return ifSchema === undefined | ||
|| !Validation.interpret(ifSchema, instance, ast, dynamicAnchors, true) | ||
|| Validation.interpret(thenSchema, instance, ast, dynamicAnchors, quiet); | ||
}; | ||
// Interpret a schema without events being emitted | ||
const quietInterpretSchema = (url, instance, ast, dynamicAnchors) => { | ||
const nodes = ast[url][2]; | ||
return typeof nodes === "boolean" ? nodes : nodes.every(([keywordId, , keywordValue]) => { | ||
return getKeyword(keywordId).interpret(keywordValue, instance, ast, dynamicAnchors); | ||
}); | ||
}; | ||
const collectEvaluatedProperties = ([guard, block], instance, ast, dynamicAnchors) => { | ||
if (guard === undefined || !quietInterpretSchema(guard, instance, ast, dynamicAnchors)) { | ||
return []; | ||
const collectEvaluatedProperties = ([ifSchema, thenSchema], instance, ast, dynamicAnchors) => { | ||
if (ifSchema === undefined || !Validation.interpret(ifSchema, instance, ast, dynamicAnchors, true)) { | ||
return new Set(); | ||
} | ||
return Validation.collectEvaluatedProperties(block, instance, ast, dynamicAnchors); | ||
return Validation.collectEvaluatedProperties(thenSchema, instance, ast, dynamicAnchors); | ||
}; | ||
const collectEvaluatedItems = ([guard, block], instance, ast, dynamicAnchors) => { | ||
if (guard === undefined || !quietInterpretSchema(guard, instance, ast, dynamicAnchors)) { | ||
const collectEvaluatedItems = ([ifSchema, thenSchema], instance, ast, dynamicAnchors) => { | ||
if (ifSchema === undefined || !Validation.interpret(ifSchema, instance, ast, dynamicAnchors, true)) { | ||
return new Set(); | ||
} | ||
return Validation.collectEvaluatedItems(block, instance, ast, dynamicAnchors); | ||
return Validation.collectEvaluatedItems(thenSchema, instance, ast, dynamicAnchors); | ||
}; | ||
export default { id, compile, interpret, collectEvaluatedProperties, collectEvaluatedItems }; |
@@ -12,3 +12,3 @@ import * as Schema from "../schema.js"; | ||
const interpret = ([schemaUrl, unevaluatedItems], instance, ast, dynamicAnchors) => { | ||
const interpret = ([schemaUrl, unevaluatedItems], instance, ast, dynamicAnchors, quiet) => { | ||
if (!Instance.typeOf(instance, "array")) { | ||
@@ -20,3 +20,3 @@ return true; | ||
return itemIndexes === false || Instance.every((item, itemIndex) => { | ||
return itemIndexes.has(itemIndex) || Validation.interpret(unevaluatedItems, Instance.step(itemIndex, instance), ast, dynamicAnchors); | ||
return itemIndexes.has(itemIndex) || Validation.interpret(unevaluatedItems, Instance.step(itemIndex, instance), ast, dynamicAnchors, quiet); | ||
}, instance); | ||
@@ -23,0 +23,0 @@ }; |
@@ -12,3 +12,3 @@ import * as Schema from "../schema.js"; | ||
const interpret = ([schemaUrl, unevaluatedProperties], instance, ast, dynamicAnchors) => { | ||
const interpret = ([schemaUrl, unevaluatedProperties], instance, ast, dynamicAnchors, quiet) => { | ||
if (!Instance.typeOf(instance, "object")) { | ||
@@ -21,10 +21,30 @@ return true; | ||
return !evaluatedPropertyNames || Instance.entries(instance) | ||
.filter(([propertyName]) => !evaluatedPropertyNames.some((pattern) => propertyName.match(pattern))) | ||
.every(([, property]) => Validation.interpret(unevaluatedProperties, property, ast, dynamicAnchors)); | ||
.filter(([propertyName]) => !evaluatedPropertyNames.has(propertyName)) | ||
.every(([, property]) => Validation.interpret(unevaluatedProperties, property, ast, dynamicAnchors, quiet)); | ||
}; | ||
const collectEvaluatedProperties = (keywordValue, instance, ast, dynamicAnchors) => { | ||
return interpret(keywordValue, instance, ast, dynamicAnchors) && [new RegExp("")]; | ||
const collectEvaluatedProperties = ([schemaUrl, unevaluatedProperties], instance, ast, dynamicAnchors) => { | ||
if (!Instance.typeOf(instance, "object")) { | ||
return true; | ||
} | ||
const evaluatedPropertyNames = Validation.collectEvaluatedProperties(schemaUrl, instance, ast, dynamicAnchors, true); | ||
if (!evaluatedPropertyNames) { | ||
return false; | ||
} | ||
for (const [propertyName, property] of Instance.entries(instance)) { | ||
if (!evaluatedPropertyNames.has(propertyName)) { | ||
if (!Validation.interpret(unevaluatedProperties, property, ast, dynamicAnchors, true)) { | ||
return false; | ||
} | ||
evaluatedPropertyNames.add(propertyName); | ||
} | ||
} | ||
return evaluatedPropertyNames; | ||
}; | ||
export default { id, compile, interpret, collectEvaluatedProperties }; |
@@ -33,27 +33,23 @@ import * as Pact from "@hyperjump/pact"; | ||
ast[url] = [ | ||
id, | ||
Schema.uri(schema), | ||
typeof schemaValue === "boolean" ? schemaValue : await Pact.pipeline([ | ||
Schema.entries, | ||
Pact.map(async ([keyword, keywordSchema]) => { | ||
const keywordId = getKeywordId(schema.dialectId, keyword); | ||
if (!keywordId) { | ||
throw Error(`Encountered unknown keyword '${keyword}' at ${url}`); | ||
} | ||
ast[url] = typeof schemaValue === "boolean" ? schemaValue : await Pact.pipeline([ | ||
Schema.entries, | ||
Pact.map(async ([keyword, keywordSchema]) => { | ||
const keywordId = getKeywordId(schema.dialectId, keyword); | ||
if (!keywordId) { | ||
throw Error(`Encountered unknown keyword '${keyword}' at ${url}`); | ||
} | ||
const keywordHandler = getKeyword(keywordId); | ||
if (!keywordHandler) { | ||
throw Error(`Encountered unsupported keyword ${keyword} at '${url}'. You can provide an implementation for the '${keywordId}' keyword using the 'addKeyword' function.`); | ||
} | ||
if (keywordHandler.experimental && !isExperimentalKeywordEnabled(keywordId)) { | ||
throw Error(`Encountered experimental keyword ${keyword} at '${url}'. You can enable this keyword with: setExperimentalKeywordEnabled('${keywordId}', true)`); | ||
} | ||
const keywordHandler = getKeyword(keywordId); | ||
if (!keywordHandler) { | ||
throw Error(`Encountered unsupported keyword ${keyword} at '${url}'. You can provide an implementation for the '${keywordId}' keyword using the 'addKeyword' function.`); | ||
} | ||
if (keywordHandler.experimental && !isExperimentalKeywordEnabled(keywordId)) { | ||
throw Error(`Encountered experimental keyword ${keyword} at '${url}'. You can enable this keyword with: setExperimentalKeywordEnabled('${keywordId}', true)`); | ||
} | ||
const keywordAst = await keywordHandler.compile(keywordSchema, ast, schema); | ||
return [keywordId, Schema.uri(keywordSchema), keywordAst]; | ||
}), | ||
Pact.all | ||
], schema) | ||
]; | ||
const keywordAst = await keywordHandler.compile(keywordSchema, ast, schema); | ||
return [keywordId, Schema.uri(keywordSchema), keywordAst]; | ||
}), | ||
Pact.all | ||
], schema); | ||
} | ||
@@ -64,13 +60,11 @@ | ||
const interpret = (url, instance, ast, dynamicAnchors) => { | ||
const [keywordId, schemaUrl, nodes] = ast[url]; | ||
const interpret = (url, instance, ast, dynamicAnchors, quiet = false) => { | ||
dynamicAnchors = { ...ast.metaData[toAbsoluteUri(url)].dynamicAnchors, ...dynamicAnchors }; | ||
publish("result.start"); | ||
const isValid = typeof nodes === "boolean" ? nodes : nodes.every(([keywordId, schemaUrl, keywordValue]) => { | ||
publish("result.start"); | ||
const isValid = getKeyword(keywordId).interpret(keywordValue, instance, ast, dynamicAnchors); | ||
!quiet && publish("result.start"); | ||
const isValid = typeof ast[url] === "boolean" ? ast[url] : ast[url].every(([keywordId, schemaUrl, keywordValue]) => { | ||
!quiet && publish("result.start"); | ||
const isValid = getKeyword(keywordId).interpret(keywordValue, instance, ast, dynamicAnchors, quiet); | ||
publish("result", { | ||
!quiet && publish("result", { | ||
keyword: keywordId, | ||
@@ -82,9 +76,9 @@ absoluteKeywordLocation: schemaUrl, | ||
}); | ||
publish("result.end"); | ||
!quiet && publish("result.end"); | ||
return isValid; | ||
}); | ||
publish("result", { | ||
keyword: keywordId, | ||
absoluteKeywordLocation: schemaUrl, | ||
!quiet && publish("result", { | ||
keyword: id, | ||
absoluteKeywordLocation: url, | ||
instanceLocation: Instance.uri(instance), | ||
@@ -94,16 +88,14 @@ valid: isValid, | ||
}); | ||
publish("result.end"); | ||
!quiet && publish("result.end"); | ||
return isValid; | ||
}; | ||
const emptyPropertyNames = []; | ||
const emptyPropertyNames = new Set(); | ||
const collectEvaluatedProperties = (url, instance, ast, dynamicAnchors, isTop = false) => { | ||
const nodes = ast[url][2]; | ||
if (typeof nodes === "boolean") { | ||
return nodes ? [] : false; | ||
if (typeof ast[url] === "boolean") { | ||
return ast[url] ? [] : false; | ||
} | ||
const accumulatedPropertyNames = []; | ||
for (const [keywordId, , keywordValue] of nodes) { | ||
const accumulatedPropertyNames = new Set(); | ||
for (const [keywordId, , keywordValue] of ast[url]) { | ||
if (isTop && keywordId === "https://json-schema.org/keyword/unevaluatedProperties") { | ||
@@ -116,3 +108,3 @@ continue; | ||
? keywordHandler.collectEvaluatedProperties(keywordValue, instance, ast, dynamicAnchors, isTop) | ||
: keywordHandler.interpret(keywordValue, instance, ast, dynamicAnchors, isTop) && emptyPropertyNames; | ||
: keywordHandler.interpret(keywordValue, instance, ast, dynamicAnchors, true) && emptyPropertyNames; | ||
@@ -123,3 +115,3 @@ if (propertyNames === false) { | ||
Array.prototype.push.apply(accumulatedPropertyNames, propertyNames); | ||
propertyNames.forEach(Set.prototype.add.bind(accumulatedPropertyNames)); | ||
} | ||
@@ -132,10 +124,8 @@ | ||
const collectEvaluatedItems = (url, instance, ast, dynamicAnchors, isTop = false) => { | ||
const nodes = ast[url][2]; | ||
if (typeof nodes === "boolean") { | ||
return nodes ? new Set() : false; | ||
if (typeof ast[url] === "boolean") { | ||
return ast[url] ? new Set() : false; | ||
} | ||
const accumulatedItemIndexes = new Set(); | ||
for (const [keywordId, , keywordValue] of nodes) { | ||
for (const [keywordId, , keywordValue] of ast[url]) { | ||
if (isTop && keywordId === "https://json-schema.org/keyword/unevaluatedItems") { | ||
@@ -148,3 +138,3 @@ continue; | ||
? keywordHandler.collectEvaluatedItems(keywordValue, instance, ast, dynamicAnchors, isTop) | ||
: keywordHandler.interpret(keywordValue, instance, ast, dynamicAnchors, isTop) && emptyItemIndexes; | ||
: keywordHandler.interpret(keywordValue, instance, ast, dynamicAnchors, true) && emptyItemIndexes; | ||
@@ -151,0 +141,0 @@ if (itemIndexes === false) { |
{ | ||
"name": "@hyperjump/json-schema", | ||
"version": "1.3.1", | ||
"version": "1.4.0", | ||
"description": "A JSON Schema validator with support for custom keywords, vocabularies, and dialects", | ||
@@ -19,2 +19,4 @@ "type": "module", | ||
"./instance/experimental": "./lib/instance.js", | ||
"./annotations/experimental": "./annotations/index.js", | ||
"./annotated-instance/experimental": "./annotations/annotated-instance.js", | ||
"./bundle": "./bundle/index.js" | ||
@@ -28,3 +30,3 @@ }, | ||
"lint": "eslint lib stable draft-* openapi-*", | ||
"test": "mocha 'lib/**/*.spec.ts' 'stable/**/*.spec.ts' 'draft-*/**/*.spec.ts' 'openapi-*/**/*.spec.ts' 'bundle/**/*.spec.ts'" | ||
"test": "mocha 'lib/**/*.spec.ts' 'stable/**/*.spec.ts' 'draft-*/**/*.spec.ts' 'openapi-*/**/*.spec.ts' 'bundle/**/*.spec.ts' 'annotations/**/*.spec.ts'" | ||
}, | ||
@@ -31,0 +33,0 @@ "repository": "github:hyperjump-io/json-schema", |
121
README.md
@@ -18,2 +18,3 @@ # Hyperjump - JSON Schema | ||
* Provides utilities for building non-validation JSON Schema tooling | ||
* Provides utilities for working with annotations | ||
@@ -242,7 +243,7 @@ ## Install | ||
### Usage | ||
You can bundle schemas with external references into single deliverable using | ||
You can bundle schemas with external references into a single deliverable using | ||
the official JSON Schema bundling process introduced in the 2020-12 | ||
specification. Given a schema with external references, any external schemas | ||
will be embedded in the schema resulting in a Compound Schema Document with all | ||
the schemas necessary to evaluate the given schema in one document. | ||
the schemas necessary to evaluate the given schema in a single JSON document. | ||
@@ -379,5 +380,5 @@ The bundling process allows schemas to be embedded without needing to modify any | ||
interpret: (implies, instance, ast, dynamicAnchors) => { | ||
interpret: (implies, instance, ast, dynamicAnchors, quiet) => { | ||
return implies.reduce((acc, schema) => { | ||
return !acc || Validation.interpret(schema, instance, ast, dynamicAnchors); | ||
return !acc || Validation.interpret(schema, instance, ast, dynamicAnchors, quiet); | ||
}, true); | ||
@@ -524,3 +525,3 @@ } | ||
might effect this one. | ||
* interpret: (compiledKeywordValue: A, instance: JsonDocument, ast: AST, dynamicAnchors: Anchors) => boolean | ||
* interpret: (compiledKeywordValue: A, instance: JsonDocument, ast: AST, dynamicAnchors: Anchors, quiet: boolean) => boolean | ||
@@ -650,2 +651,112 @@ This function takes the value returned by the `compile` function and the | ||
## Annotations (Experimental) | ||
JSON Schema is for annotating JSON instances as well as validating them. This | ||
module provides utilities for working with JSON documents annotated with JSON | ||
Schema. | ||
### Usage | ||
An annotated JSON document is represented as an AnnotatedInstance object. This | ||
object is a wrapper around your JSON document with functions that allow you to | ||
traverse the data structure and get annotations for the values within. | ||
```javascript | ||
import { annotate, annotatedWith, addSchema } from "@hyperjump/json-schema/annotations/experimental"; | ||
import * as AnnotatedInstance from "@hyperjump/json-schema/annotated-instance/experimental"; | ||
const schemaId = "https://example.com/foo"; | ||
const dialectId = "https://json-schema.org/draft/2020-12/schema"; | ||
addSchema({ | ||
"$schema": dialectId, | ||
"title": "Person", | ||
"unknown": "foo", | ||
"type": "object", | ||
"properties": { | ||
"name": { | ||
"$ref": "#/$defs/name", | ||
"deprecated": true | ||
}, | ||
"givenName": { | ||
"$ref": "#/$defs/name", | ||
"title": "Given Name" | ||
}, | ||
"familyName": { | ||
"$ref": "#/$defs/name", | ||
"title": "Family Name" | ||
} | ||
}, | ||
"$defs": { | ||
"name": { | ||
"type": "string", | ||
"title": "Name" | ||
} | ||
} | ||
}, schemaId); | ||
const instance = await annotate(schemaId, { | ||
name: "Jason Desrosiers", | ||
givenName: "Jason", | ||
familyName: "Desrosiers" | ||
}); | ||
// Get the title of the instance | ||
const titles = AnnotatedInstance.annotation(instance, "title", dialectId); // => ["Person"] | ||
// Unknown keywords are collected as annotations | ||
const unknowns = AnnotatedInstance.annotation(instance, "unknown", dialectId); // => ["foo"] | ||
// The type keyword doesn't produce annotations | ||
const types = AnnotatedInstance.annotation(instance, "type", dialectId); // => [] | ||
// Get the title of each of the properties in the object | ||
AnnotatedInstance.entries(instance) | ||
.map(([propertyName, propertyInstance]) => { | ||
return { propertyName, titles: Instance.annotation(propertyInstance, "title", dialectId) }; // => (Example) { propertyName: "givenName", titles: ["Given Name", "Name"] }); | ||
// List all locations in the instance that are deprecated | ||
for (const deprecated of AnnotatedInstance.annotatedWith(instance, "deprecated", dialectId)) { | ||
if (AnnotatedInstance.annotation(instance, "deprecated", dialectId)[0]) { | ||
logger.warn(`The value at '${deprecated.pointer}' has been deprecated.`); // => (Example) "WARN: The value at '/name' has been deprecated." | ||
} | ||
} | ||
``` | ||
### API | ||
These are available from the `@hyperjump/json-schema/annotations/experimental` | ||
export. | ||
* **annotate**: (schemaUri: string, instance: any, outputFormat: OutputFormat = FLAG) => Promise<AnnotatedInstance> | ||
Annotate an instance using the given schema. The function is curried to | ||
allow compiling the schema once and applying it to multiple instances. This | ||
may throw an [InvalidSchemaError](#api) if there is a problem with the | ||
schema or a ValidationError if the instance doesn't validate against the | ||
schema. | ||
* **ValidationError**: | ||
output: OutputUnit -- The errors that were found while validating the | ||
instance. | ||
### AnnotatedInstance API | ||
These are available from the | ||
`@hyperjump/json-schema/annotated-instance/experimental` export. The | ||
following functions are available in addition to the functions available in the | ||
[Instance API](#instance-api). | ||
* **annotation**: (instance: AnnotatedInstance, keyword: string, dialectId?: string) => [any] | ||
Get the annotations for a given keyword at the location represented by the | ||
instance object. | ||
* **annotatedWith**: (instance: AnnotatedInstance, keyword: string, dialectId?: string) => [AnnotatedInstance] | ||
Get an array of instances for all the locations that are annotated with the | ||
given keyword. | ||
* **annotate**: (instance: AnnotatedInstance, keywordId: string, value: any) => AnnotatedInstance | ||
Add an annotation to an instance. This is used internally, you probably | ||
don't need it. | ||
## Low-level Utilities (Experimental) | ||
@@ -652,0 +763,0 @@ ### API |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
347155
177
10132
785