UNPKG

@utilize/json-schema

Version:
170 lines (163 loc) 4.53 kB
'use strict'; 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;