@utilize/json-schema
Version:
Utilities for working with JSON Schema.
170 lines (163 loc) • 4.53 kB
JavaScript
;
const path = require('path');
const jsonSchemaRefParser = require('@apidevtools/json-schema-ref-parser');
const Meta = Symbol("Meta");
const SchemaKeys = {
const: "const",
enum: "enum",
$defs: "$defs",
definitions: "definitions",
properties: "properties",
additionalProperties: "additionalProperties",
propertyNames: "propertyNames",
patternProperties: "patternProperties",
additionalItems: "additionalItems",
items: "items",
contains: "contains",
dependencies: "dependencies",
allOf: "allOf",
anyOf: "anyOf",
oneOf: "oneOf",
not: "not"
};
function isPlainObject(value) {
if (typeof value !== "object" || value === null) return false;
if (Object.prototype.toString.call(value) !== "[object Object]") return false;
const proto = Object.getPrototypeOf(value);
if (proto === null) return true;
const Ctor = Object.prototype.hasOwnProperty.call(proto, "constructor") && proto.constructor;
return typeof Ctor === "function" && Ctor instanceof Ctor && Function.prototype.call(Ctor) === Function.prototype.call(value);
}
function assert(condition, message) {
if (!condition) {
throw new Error(message ?? "Assertion failed");
}
}
function link({
schema,
parent = null,
path: path$1,
$refs,
stack,
filePath,
fileName
}) {
let pointer = [filePath, ...path$1].join("/");
let reference = void 0;
if (typeof schema !== "object" || schema === null) {
return schema;
}
if (schema.$ref) {
if (filePath !== "#" && schema.$ref.startsWith("#")) {
pointer = filePath + "#" + schema.$ref.slice(1);
} else if (schema.$ref.startsWith("./")) {
pointer = path.join(path.dirname(filePath), schema.$ref);
} else {
pointer = schema.$ref;
}
if ($refs.exists(pointer, {})) {
reference = $refs.get(pointer);
if (reference === void 0) {
throw new Error(`Reference not found: ${pointer}`);
}
}
}
if (reference && !Array.isArray(reference) && !isPlainObject(reference)) {
throw new Error(`Reference is not a valid object: ${pointer}`);
}
if (!reference && !Array.isArray(schema) && !isPlainObject(schema)) {
return schema;
}
if (Object.hasOwnProperty.call(schema, Meta)) {
return schema;
}
const meta = {
filePath,
fileName,
path: path$1,
parent
};
if (schema.$ref && $refs.exists(pointer, {})) {
const referencedSchema = $refs.get(pointer);
meta.reference = referencedSchema;
meta.isCircular = stack.has(referencedSchema);
Object.defineProperty(schema, Meta, {
enumerable: false,
value: meta,
writable: false
});
return schema;
}
Object.defineProperty(schema, Meta, {
enumerable: false,
value: meta,
writable: false
});
stack.add(schema);
if (Array.isArray(schema)) {
schema.forEach(
(item, index) => link({
schema: item,
parent: schema,
path: [...path$1, index],
filePath,
$refs,
stack,
fileName
})
);
}
for (const [key, value] of Object.entries(schema)) {
link({
schema: value,
parent: schema,
path: [...path$1, key],
filePath,
$refs,
stack,
fileName
});
}
stack.delete(schema);
return schema;
}
async function parse(schema, options) {
let cwd = options.cwd;
const rootFileName = options.fileName;
if (!cwd.endsWith("/")) {
cwd += "/";
}
const parser = new jsonSchemaRefParser.$RefParser();
const $refs = await parser.resolve(cwd, schema, {});
const entries = Object.entries($refs.values());
entries.forEach(([filePath, schema2]) => {
const rootSchema = $refs.get("#");
const isRootSchema = schema2 === rootSchema;
const prefixPath = isRootSchema ? "#" : filePath.replace(cwd, "");
const fileName = isRootSchema ? rootFileName : filePath.split(/[\\/]/).at(-1) ?? "Unknown";
link({
schema: schema2,
parent: null,
fileName,
filePath: prefixPath,
path: [],
$refs,
stack: /* @__PURE__ */ new Set()
});
});
console.log(
`Parsed ${entries.length} schemas from ${rootFileName} at ${cwd}`
);
const root = $refs.get("#");
return {
root,
get: ($ref) => $refs.get($ref),
referencedSchemas: entries.filter(([, schema2]) => schema2 !== root).map(([, schema2]) => schema2)
};
}
exports.Meta = Meta;
exports.SchemaKeys = SchemaKeys;
exports.assert = assert;
exports.isPlainObject = isPlainObject;
exports.link = link;
exports.parse = parse;