@apidevtools/json-schema-ref-parser
Version:
Parse, Resolve, and Dereference JSON Schema $ref pointers
164 lines (163 loc) • 6.83 kB
JavaScript
import $Refs from "./refs.js";
import _parse from "./parse.js";
import normalizeArgs from "./normalize-args.js";
import resolveExternal from "./resolve-external.js";
import _bundle from "./bundle.js";
import _dereference from "./dereference.js";
import * as url from "./util/url.js";
import { JSONParserError, InvalidPointerError, MissingPointerError, ResolverError, ParserError, UnmatchedParserError, UnmatchedResolverError, isHandledError, JSONParserErrorGroup, } from "./util/errors.js";
import maybe from "./util/maybe.js";
import { getJsonSchemaRefParserDefaultOptions } from "./options.js";
import { isUnsafeUrl } from "./util/url.js";
/**
* This class parses a JSON schema, builds a map of its JSON references and their resolved values,
* and provides methods for traversing, manipulating, and dereferencing those references.
*
* @class
*/
export class $RefParser {
/**
* The parsed (and possibly dereferenced) JSON schema object
*
* @type {object}
* @readonly
*/
schema = null;
/**
* The resolved JSON references
*
* @type {$Refs}
* @readonly
*/
$refs = new $Refs();
async parse() {
const args = normalizeArgs(arguments);
let promise;
if (!args.path && !args.schema) {
const err = new Error(`Expected a file path, URL, or object. Got ${args.path || args.schema}`);
return maybe(args.callback, Promise.reject(err));
}
// Reset everything
this.schema = null;
this.$refs = new $Refs();
// If the path is a filesystem path, then convert it to a URL.
// NOTE: According to the JSON Reference spec, these should already be URLs,
// but, in practice, many people use local filesystem paths instead.
// So we're being generous here and doing the conversion automatically.
// This is not intended to be a 100% bulletproof solution.
// If it doesn't work for your use-case, then use a URL instead.
let pathType = "http";
if (url.isFileSystemPath(args.path)) {
args.path = url.fromFileSystemPath(args.path);
pathType = "file";
}
else if (!args.path && args.schema && "$id" in args.schema && args.schema.$id) {
// when schema id has defined an URL should use that hostname to request the references,
// instead of using the current page URL
const params = url.parse(args.schema.$id);
const port = params.port ?? (params.protocol === "https:" ? 443 : 80);
args.path = `${params.protocol}//${params.hostname}:${port}`;
}
// Resolve the absolute path of the schema
args.path = url.resolve(url.cwd(), args.path);
if (args.schema && typeof args.schema === "object") {
// A schema object was passed-in.
// So immediately add a new $Ref with the schema object as its value
const $ref = this.$refs._add(args.path);
$ref.value = args.schema;
$ref.pathType = pathType;
promise = Promise.resolve(args.schema);
}
else {
// Parse the schema file/url
promise = _parse(args.path, this.$refs, args.options);
}
try {
const result = await promise;
if (result !== null && typeof result === "object" && !Buffer.isBuffer(result)) {
this.schema = result;
return maybe(args.callback, Promise.resolve(this.schema));
}
else if (args.options.continueOnError) {
this.schema = null; // it's already set to null at line 79, but let's set it again for the sake of readability
return maybe(args.callback, Promise.resolve(this.schema));
}
else {
throw new SyntaxError(`"${this.$refs._root$Ref.path || result}" is not a valid JSON Schema`);
}
}
catch (err) {
if (!args.options.continueOnError || !isHandledError(err)) {
return maybe(args.callback, Promise.reject(err));
}
if (this.$refs._$refs[url.stripHash(args.path)]) {
this.$refs._$refs[url.stripHash(args.path)].addError(err);
}
return maybe(args.callback, Promise.resolve(null));
}
}
static parse() {
const parser = new $RefParser();
return parser.parse.apply(parser, arguments);
}
async resolve() {
const args = normalizeArgs(arguments);
try {
await this.parse(args.path, args.schema, args.options);
await resolveExternal(this, args.options);
finalize(this);
return maybe(args.callback, Promise.resolve(this.$refs));
}
catch (err) {
return maybe(args.callback, Promise.reject(err));
}
}
static resolve() {
const instance = new $RefParser();
return instance.resolve.apply(instance, arguments);
}
static bundle() {
const instance = new $RefParser();
return instance.bundle.apply(instance, arguments);
}
async bundle() {
const args = normalizeArgs(arguments);
try {
await this.resolve(args.path, args.schema, args.options);
_bundle(this, args.options);
finalize(this);
return maybe(args.callback, Promise.resolve(this.schema));
}
catch (err) {
return maybe(args.callback, Promise.reject(err));
}
}
static dereference() {
const instance = new $RefParser();
return instance.dereference.apply(instance, arguments);
}
async dereference() {
const args = normalizeArgs(arguments);
try {
await this.resolve(args.path, args.schema, args.options);
_dereference(this, args.options);
finalize(this);
return maybe(args.callback, Promise.resolve(this.schema));
}
catch (err) {
return maybe(args.callback, Promise.reject(err));
}
}
}
export default $RefParser;
function finalize(parser) {
const errors = JSONParserErrorGroup.getParserErrors(parser);
if (errors.length > 0) {
throw new JSONParserErrorGroup(parser);
}
}
export const parse = $RefParser.parse;
export const resolve = $RefParser.resolve;
export const bundle = $RefParser.bundle;
export const dereference = $RefParser.dereference;
export { UnmatchedResolverError, JSONParserError, InvalidPointerError, MissingPointerError, ResolverError, ParserError, UnmatchedParserError, isHandledError, JSONParserErrorGroup, _dereference as dereferenceInternal, normalizeArgs as jsonSchemaParserNormalizeArgs, getJsonSchemaRefParserDefaultOptions, $Refs, isUnsafeUrl, };