From 565068bcd36c96d3ad5509d3d41a9b3fc30346e9 Mon Sep 17 00:00:00 2001 From: JonLuca DeCaro Date: Wed, 6 Mar 2024 14:47:53 -0800 Subject: [PATCH] feat(generics): introduce generics for the input schema and the options --- lib/bundle.ts | 42 +-- lib/dereference.ts | 38 +-- lib/index.ts | 267 +++++++++++------- lib/normalize-args.ts | 21 +- lib/options.ts | 102 +++---- lib/parse.ts | 32 ++- lib/pointer.ts | 11 +- lib/ref.ts | 23 +- lib/refs.ts | 26 +- lib/resolve-external.ts | 35 ++- lib/resolvers/file.ts | 4 +- lib/resolvers/http.ts | 12 +- lib/types/index.ts | 10 +- lib/util/errors.ts | 20 +- lib/util/plugins.ts | 29 +- test/specs/blank/blank.spec.ts | 2 - test/specs/callbacks.spec.ts | 2 + .../circular-external.spec.ts | 1 - .../specs/deep-circular/deep-circular.spec.ts | 1 - test/specs/deep/deep.spec.ts | 1 - .../external-multiple.spec.ts | 1 - .../external-partial/external-partial.spec.ts | 1 - test/specs/parsers/parsers.spec.ts | 4 - 23 files changed, 393 insertions(+), 292 deletions(-) diff --git a/lib/bundle.ts b/lib/bundle.ts index 870b4663..ff76fbdb 100644 --- a/lib/bundle.ts +++ b/lib/bundle.ts @@ -1,10 +1,10 @@ import $Ref from "./ref.js"; import Pointer from "./pointer.js"; import * as url from "./util/url.js"; -import type $RefParserOptions from "./options.js"; import type $Refs from "./refs.js"; - -export default bundle; +import type $RefParser from "./index"; +import type { ParserOptions } from "./index"; +import type { JSONSchema } from "./index"; /** * Bundles all external JSON references into the main JSON schema, thus resulting in a schema that @@ -14,12 +14,15 @@ export default bundle; * @param parser * @param options */ -function bundle(parser: any, options: any) { +function bundle( + parser: $RefParser, + options: O, +) { // console.log('Bundling $ref pointers in %s', parser.$refs._root$Ref.path); // Build an inventory of all $ref pointers in the JSON Schema const inventory: any = []; - crawl(parser, "schema", parser.$refs._root$Ref.path + "#", "#", 0, inventory, parser.$refs, options); + crawl(parser, "schema", parser.$refs._root$Ref.path + "#", "#", 0, inventory, parser.$refs, options); // Remap all $ref pointers remap(inventory); @@ -32,19 +35,20 @@ function bundle(parser: any, options: any) { * @param key - The property key of `parent` to be crawled * @param path - The full path of the property being crawled, possibly with a JSON Pointer in the hash * @param pathFromRoot - The path of the property being crawled, from the schema root + * @param indirections * @param inventory - An array of already-inventoried $ref pointers * @param $refs * @param options */ -function crawl( +function crawl( parent: any, - key: any, - path: any, - pathFromRoot: any, - indirections: any, - inventory: any, - $refs: any, - options: any, + key: string | null, + path: string, + pathFromRoot: string, + indirections: number, + inventory: unknown[], + $refs: $Refs, + options: O, ) { const obj = key === null ? parent : parent[key]; @@ -98,15 +102,15 @@ function crawl( * @param $refs * @param options */ -function inventory$Ref( +function inventory$Ref( $refParent: any, $refKey: any, path: string, pathFromRoot: any, indirections: any, inventory: any, - $refs: $Refs, - options: $RefParserOptions, + $refs: $Refs, + options: O, ) { const $ref = $refKey === null ? $refParent : $refParent[$refKey]; const $refPath = url.resolve(path, $ref.$ref); @@ -248,9 +252,8 @@ function remap(inventory: any) { * TODO */ function findInInventory(inventory: any, $refParent: any, $refKey: any) { - for (let i = 0; i < inventory.length; i++) { - const existingEntry = inventory[i]; - if (existingEntry.parent === $refParent && existingEntry.key === $refKey) { + for (const existingEntry of inventory) { + if (existingEntry && existingEntry.parent === $refParent && existingEntry.key === $refKey) { return existingEntry; } } @@ -260,3 +263,4 @@ function removeFromInventory(inventory: any, entry: any) { const index = inventory.indexOf(entry); inventory.splice(index, 1); } +export default bundle; diff --git a/lib/dereference.ts b/lib/dereference.ts index cbcb486c..1dc37b71 100644 --- a/lib/dereference.ts +++ b/lib/dereference.ts @@ -3,7 +3,9 @@ import Pointer from "./pointer.js"; import { ono } from "@jsdevtools/ono"; import * as url from "./util/url.js"; import type $Refs from "./refs.js"; -import type $RefParserOptions from "./options.js"; +import type { DereferenceOptions, ParserOptions } from "./options.js"; +import type { JSONSchema } from "./types"; +import type $RefParser from "./index"; export default dereference; @@ -14,11 +16,14 @@ export default dereference; * @param parser * @param options */ -function dereference(parser: any, options: any) { +function dereference( + parser: $RefParser, + options: O, +) { // console.log('Dereferencing $ref pointers in %s', parser.$refs._root$Ref.path); - const dereferenced = crawl( + const dereferenced = crawl( parser.schema, - parser.$refs._root$Ref.path, + parser.$refs._root$Ref.path!, "#", new Set(), new Set(), @@ -43,15 +48,15 @@ function dereference(parser: any, options: any) { * @param options * @returns */ -function crawl( +function crawl( obj: any, path: string, pathFromRoot: string, parents: Set, processedObjects: Set, dereferencedCache: any, - $refs: $Refs, - options: $RefParserOptions, + $refs: $Refs, + options: O, ) { let dereferenced; const result = { @@ -59,9 +64,10 @@ function crawl( circular: false, }; - const isExcludedPath = options.dereference.excludedPathMatcher || (() => false); + const derefOptions = (options.dereference || {}) as DereferenceOptions; + const isExcludedPath = derefOptions.excludedPathMatcher || (() => false); - if (options.dereference.circular === "ignore" || !processedObjects.has(obj)) { + if (derefOptions?.circular === "ignore" || !processedObjects.has(obj)) { if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj) && !isExcludedPath(pathFromRoot)) { parents.add(obj); processedObjects.add(obj); @@ -106,9 +112,7 @@ function crawl( // Avoid pointless mutations; breaks frozen objects to no profit if (obj[key] !== dereferenced.value) { obj[key] = dereferenced.value; - if (options.dereference.onDereference) { - options.dereference.onDereference(value.$ref, obj[key], obj, key); - } + derefOptions?.onDereference?.(value.$ref, obj[key], obj, key); } } else { if (!parents.has(value)) { @@ -157,18 +161,18 @@ function crawl( * @param options * @returns */ -function dereference$Ref( +function dereference$Ref( $ref: any, path: string, pathFromRoot: string, parents: Set, processedObjects: any, dereferencedCache: any, - $refs: $Refs, - options: $RefParserOptions, + $refs: $Refs, + options: O, ) { const isExternalRef = $Ref.isExternal$Ref($ref); - const shouldResolveOnCwd = isExternalRef && options?.dereference.externalReferenceResolution === "root"; + const shouldResolveOnCwd = isExternalRef && options?.dereference?.externalReferenceResolution === "root"; const $refPath = url.resolve(shouldResolveOnCwd ? url.cwd() : path, $ref.$ref); const cache = dereferencedCache.get($refPath); @@ -225,7 +229,7 @@ function dereference$Ref( dereferencedValue = dereferenced.value; } - if (circular && !directCircular && options.dereference.circular === "ignore") { + if (circular && !directCircular && options.dereference?.circular === "ignore") { // The user has chosen to "ignore" circular references, so don't change the value dereferencedValue = $ref; } diff --git a/lib/index.ts b/lib/index.ts index cf22293c..6b03ec96 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -19,7 +19,15 @@ import { import { ono } from "@jsdevtools/ono"; import maybe from "./util/maybe.js"; import type { ParserOptions } from "./options.js"; -import type { $RefsCallback, JSONSchema, SchemaCallback } from "./types/index.js"; +import type { + $RefsCallback, + JSONSchema, + SchemaCallback, + FileInfo, + Plugin, + ResolverOptions, + HTTPResolverOptions, +} from "./types/index.js"; export type RefParserSchema = string | JSONSchema; @@ -29,14 +37,14 @@ export type RefParserSchema = string | JSONSchema; * * @class */ -export class $RefParser { +export class $RefParser { /** * The parsed (and possibly dereferenced) JSON schema object * * @type {object} * @readonly */ - public schema: JSONSchema | null = null; + public schema: S | null = null; /** * The resolved JSON references @@ -44,7 +52,7 @@ export class $RefParser { * @type {$Refs} * @readonly */ - $refs = new $Refs(); + $refs = new $Refs(); /** * Parses the given JSON schema. @@ -57,19 +65,14 @@ export class $RefParser { * @param [callback] - An error-first callback. The second parameter is the parsed JSON schema object. * @returns - The returned promise resolves with the parsed JSON schema object. */ - public parse(schema: RefParserSchema): Promise; - public parse(schema: RefParserSchema, callback: SchemaCallback): Promise; - public parse(schema: RefParserSchema, options: ParserOptions): Promise; - public parse(schema: RefParserSchema, options: ParserOptions, callback: SchemaCallback): Promise; - public parse(baseUrl: string, schema: RefParserSchema, options: ParserOptions): Promise; - public parse( - baseUrl: string, - schema: RefParserSchema, - options: ParserOptions, - callback: SchemaCallback, - ): Promise; + public parse(schema: S | string): Promise; + public parse(schema: S | string, callback: SchemaCallback): Promise; + public parse(schema: S | string, options: O): Promise; + public parse(schema: S | string, options: O, callback: SchemaCallback): Promise; + public parse(baseUrl: string, schema: S | string, options: O): Promise; + public parse(baseUrl: string, schema: S | string, options: O, callback: SchemaCallback): Promise; async parse() { - const args = normalizeArgs(arguments as any); + const args = normalizeArgs(arguments as any); let promise; if (!args.path && !args.schema) { @@ -112,7 +115,7 @@ export class $RefParser { promise = Promise.resolve(args.schema); } else { // Parse the schema file/url - promise = _parse(args.path, this.$refs, args.options); + promise = _parse(args.path, this.$refs, args.options); } try { @@ -140,19 +143,35 @@ export class $RefParser { } } - public static parse(schema: RefParserSchema): Promise; - public static parse(schema: RefParserSchema, callback: SchemaCallback): Promise; - public static parse(schema: RefParserSchema, options: ParserOptions): Promise; - public static parse(schema: RefParserSchema, options: ParserOptions, callback: SchemaCallback): Promise; - public static parse(baseUrl: string, schema: RefParserSchema, options: ParserOptions): Promise; - public static parse( + public static parse(schema: S | string): Promise; + public static parse( + schema: S | string, + callback: SchemaCallback, + ): Promise; + public static parse( + schema: S | string, + options: O, + ): Promise; + public static parse( + schema: S | string, + options: O, + callback: SchemaCallback, + ): Promise; + public static parse( + baseUrl: string, + schema: S | string, + options: O, + ): Promise; + public static parse( baseUrl: string, - schema: RefParserSchema, - options: ParserOptions, - callback: SchemaCallback, + schema: S | string, + options: O, + callback: SchemaCallback, ): Promise; - public static parse(): Promise | Promise { - const parser = new $RefParser(); + public static parse(): + | Promise + | Promise { + const parser = new $RefParser(); return parser.parse.apply(parser, arguments as any); } @@ -167,19 +186,14 @@ export class $RefParser { * @param options (optional) * @param callback (optional) A callback that will receive a `$Refs` object */ - public resolve(schema: RefParserSchema): Promise<$Refs>; - public resolve(schema: RefParserSchema, callback: $RefsCallback): Promise; - public resolve(schema: RefParserSchema, options: ParserOptions): Promise<$Refs>; - public resolve(schema: RefParserSchema, options: ParserOptions, callback: $RefsCallback): Promise; - public resolve(baseUrl: string, schema: RefParserSchema, options: ParserOptions): Promise<$Refs>; - public resolve( - baseUrl: string, - schema: RefParserSchema, - options: ParserOptions, - callback: $RefsCallback, - ): Promise; + public resolve(schema: S | string): Promise<$Refs>; + public resolve(schema: S | string, callback: $RefsCallback): Promise; + public resolve(schema: S | string, options: O): Promise<$Refs>; + public resolve(schema: S | string, options: O, callback: $RefsCallback): Promise; + public resolve(baseUrl: string, schema: S | string, options: O): Promise<$Refs>; + public resolve(baseUrl: string, schema: S | string, options: O, callback: $RefsCallback): Promise; async resolve() { - const args = normalizeArgs(arguments); + const args = normalizeArgs(arguments); try { await this.parse(args.path, args.schema, args.options); @@ -202,19 +216,35 @@ export class $RefParser { * @param options (optional) * @param callback (optional) A callback that will receive a `$Refs` object */ - public static resolve(schema: RefParserSchema): Promise<$Refs>; - public static resolve(schema: RefParserSchema, callback: $RefsCallback): Promise; - public static resolve(schema: RefParserSchema, options: ParserOptions): Promise<$Refs>; - public static resolve(schema: RefParserSchema, options: ParserOptions, callback: $RefsCallback): Promise; - public static resolve(baseUrl: string, schema: RefParserSchema, options: ParserOptions): Promise<$Refs>; - public static resolve( + public static resolve(schema: S | string): Promise<$Refs>; + public static resolve( + schema: S | string, + callback: $RefsCallback, + ): Promise; + public static resolve( + schema: S | string, + options: O, + ): Promise<$Refs>; + public static resolve( + schema: S | string, + options: O, + callback: $RefsCallback, + ): Promise; + public static resolve( baseUrl: string, - schema: RefParserSchema, - options: ParserOptions, - callback: $RefsCallback, + schema: S | string, + options: O, + ): Promise<$Refs>; + public static resolve( + baseUrl: string, + schema: S | string, + options: O, + callback: $RefsCallback, ): Promise; - static resolve(): Promise | Promise { - const instance = new $RefParser(); + static resolve(): + | Promise + | Promise { + const instance = new $RefParser(); return instance.resolve.apply(instance, arguments as any); } @@ -229,19 +259,37 @@ export class $RefParser { * @param options (optional) * @param callback (optional) A callback that will receive the bundled schema object */ - public static bundle(schema: RefParserSchema): Promise; - public static bundle(schema: RefParserSchema, callback: SchemaCallback): Promise; - public static bundle(schema: RefParserSchema, options: ParserOptions): Promise; - public static bundle(schema: RefParserSchema, options: ParserOptions, callback: SchemaCallback): Promise; - public static bundle(baseUrl: string, schema: RefParserSchema, options: ParserOptions): Promise; - public static bundle( + public static bundle( + schema: S | string, + ): Promise; + public static bundle( + schema: S | string, + callback: SchemaCallback, + ): Promise; + public static bundle( + schema: S | string, + options: O, + ): Promise; + public static bundle( + schema: S | string, + options: O, + callback: SchemaCallback, + ): Promise; + public static bundle( baseUrl: string, - schema: RefParserSchema, - options: ParserOptions, - callback: SchemaCallback, - ): Promise; - static bundle(): Promise | Promise { - const instance = new $RefParser(); + schema: S | string, + options: O, + ): Promise; + public static bundle( + baseUrl: string, + schema: S | string, + options: O, + callback: SchemaCallback, + ): Promise; + static bundle(): + | Promise + | Promise { + const instance = new $RefParser(); return instance.bundle.apply(instance, arguments as any); } @@ -256,22 +304,17 @@ export class $RefParser { * @param options (optional) * @param callback (optional) A callback that will receive the bundled schema object */ - public bundle(schema: RefParserSchema): Promise; - public bundle(schema: RefParserSchema, callback: SchemaCallback): Promise; - public bundle(schema: RefParserSchema, options: ParserOptions): Promise; - public bundle(schema: RefParserSchema, options: ParserOptions, callback: SchemaCallback): Promise; - public bundle(baseUrl: string, schema: RefParserSchema, options: ParserOptions): Promise; - public bundle( - baseUrl: string, - schema: RefParserSchema, - options: ParserOptions, - callback: SchemaCallback, - ): Promise; + public bundle(schema: S | string): Promise; + public bundle(schema: S | string, callback: SchemaCallback): Promise; + public bundle(schema: S | string, options: O): Promise; + public bundle(schema: S | string, options: O, callback: SchemaCallback): Promise; + public bundle(baseUrl: string, schema: S | string, options: O): Promise; + public bundle(baseUrl: string, schema: S | string, options: O, callback: SchemaCallback): Promise; async bundle() { - const args = normalizeArgs(arguments); + const args = normalizeArgs(arguments); try { await this.resolve(args.path, args.schema, args.options); - _bundle(this, args.options); + _bundle(this, args.options); finalize(this); return maybe(args.callback, Promise.resolve(this.schema!)); } catch (err) { @@ -290,19 +333,37 @@ export class $RefParser { * @param options (optional) * @param callback (optional) A callback that will receive the dereferenced schema object */ - public static dereference(schema: RefParserSchema): Promise; - public static dereference(schema: RefParserSchema, callback: SchemaCallback): Promise; - public static dereference(schema: RefParserSchema, options: ParserOptions): Promise; - public static dereference(schema: RefParserSchema, options: ParserOptions, callback: SchemaCallback): Promise; - public static dereference(baseUrl: string, schema: RefParserSchema, options: ParserOptions): Promise; - public static dereference( + public static dereference( + schema: S | string, + ): Promise; + public static dereference( + schema: S | string, + callback: SchemaCallback, + ): Promise; + public static dereference( + schema: S | string, + options: O, + ): Promise; + public static dereference( + schema: S | string, + options: O, + callback: SchemaCallback, + ): Promise; + public static dereference( + baseUrl: string, + schema: S | string, + options: O, + ): Promise; + public static dereference( baseUrl: string, - schema: RefParserSchema, - options: ParserOptions, - callback: SchemaCallback, + schema: S | string, + options: O, + callback: SchemaCallback, ): Promise; - static dereference(): Promise | Promise { - const instance = new $RefParser(); + static dereference(): + | Promise + | Promise { + const instance = new $RefParser(); return instance.dereference.apply(instance, arguments as any); } @@ -313,37 +374,35 @@ export class $RefParser { * * See https://apitools.dev/json-schema-ref-parser/docs/ref-parser.html#dereferenceschema-options-callback * + * @param baseUrl * @param schema A JSON Schema object, or the file path or URL of a JSON Schema file. See the `parse` method for more info. * @param options (optional) * @param callback (optional) A callback that will receive the dereferenced schema object */ - public dereference( - baseUrl: string, - schema: RefParserSchema, - options: ParserOptions, - callback: SchemaCallback, - ): Promise; - public dereference(schema: RefParserSchema, options: ParserOptions, callback: SchemaCallback): Promise; - public dereference(schema: RefParserSchema, callback: SchemaCallback): Promise; - public dereference(baseUrl: string, schema: RefParserSchema, options: ParserOptions): Promise; - public dereference(schema: RefParserSchema, options: ParserOptions): Promise; - public dereference(schema: RefParserSchema): Promise; + public dereference(baseUrl: string, schema: S | string, options: O, callback: SchemaCallback): Promise; + public dereference(schema: S | string, options: O, callback: SchemaCallback): Promise; + public dereference(schema: S | string, callback: SchemaCallback): Promise; + public dereference(baseUrl: string, schema: S | string, options: O): Promise; + public dereference(schema: S | string, options: O): Promise; + public dereference(schema: S | string): Promise; async dereference() { - const args = normalizeArgs(arguments); + 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)); + return maybe(args.callback, Promise.resolve(this.schema!) as Promise); } catch (err) { - return maybe(args.callback, Promise.reject(err)); + return maybe(args.callback, Promise.reject(err)); } } } export default $RefParser; -function finalize(parser: any) { +function finalize( + parser: $RefParser, +) { const errors = JSONParserErrorGroup.getParserErrors(parser); if (errors.length > 0) { throw new JSONParserErrorGroup(parser); @@ -369,4 +428,8 @@ export { isHandledError, JSONParserErrorGroup, SchemaCallback, + FileInfo, + Plugin, + ResolverOptions, + HTTPResolverOptions, }; diff --git a/lib/normalize-args.ts b/lib/normalize-args.ts index fdba080f..2e928e76 100644 --- a/lib/normalize-args.ts +++ b/lib/normalize-args.ts @@ -1,20 +1,25 @@ +import type { Options, ParserOptions } from "./options.js"; import { getNewOptions } from "./options.js"; import type { JSONSchema, SchemaCallback } from "./types"; -import type $RefParserOptions from "./options"; // I really dislike this function and the way it's written. It's not clear what it's doing, and it's way too flexible // In the future, I'd like to deprecate the api and accept only named parameters in index.ts -export interface NormalizedArguments { +export interface NormalizedArguments { path: string; - schema: JSONSchema; - options: T; - callback: SchemaCallback; + schema: S; + options: O & Options; + callback: SchemaCallback; } /** * Normalizes the given arguments, accounting for optional args. */ -export function normalizeArgs(_args: Partial): NormalizedArguments { - let path, schema, options, callback; +export function normalizeArgs( + _args: Partial, +): NormalizedArguments { + let path; + let schema; + let options: Options & O; + let callback; const args = Array.prototype.slice.call(_args) as any[]; if (typeof args[args.length - 1] === "function") { @@ -42,7 +47,7 @@ export function normalizeArgs(_args: Partial) } try { - options = getNewOptions(options); + options = getNewOptions(options); } catch (e) { console.error(`JSON Schema Ref Parser: Error normalizing options: ${e}`); } diff --git a/lib/options.ts b/lib/options.ts index 0b0e5ed3..1dc15ff1 100644 --- a/lib/options.ts +++ b/lib/options.ts @@ -5,20 +5,55 @@ import binaryParser from "./parsers/binary.js"; import fileResolver from "./resolvers/file.js"; import httpResolver from "./resolvers/http.js"; -import type { HTTPResolverOptions, JSONSchemaObject, Plugin, ResolverOptions } from "./types/index.js"; +import type { HTTPResolverOptions, JSONSchema, JSONSchemaObject, Plugin, ResolverOptions } from "./types/index.js"; export type DeepPartial = T extends object ? { [P in keyof T]?: DeepPartial; } : T; +export interface DereferenceOptions { + /** + * Determines whether circular `$ref` pointers are handled. + * + * If set to `false`, then a `ReferenceError` will be thrown if the schema contains any circular references. + * + * If set to `"ignore"`, then circular references will simply be ignored. No error will be thrown, but the `$Refs.circular` property will still be set to `true`. + */ + circular?: boolean | "ignore"; + + /** + * A function, called for each path, which can return true to stop this path and all + * subpaths from being dereferenced further. This is useful in schemas where some + * subpaths contain literal $ref keys that should not be dereferenced. + */ + excludedPathMatcher?(path: string): boolean; + + /** + * Callback invoked during dereferencing. + * + * @argument {string} path - The path being dereferenced (ie. the `$ref` string) + * @argument {JSONSchemaObject} value - The JSON-Schema that the `$ref` resolved to + * @argument {JSONSchemaObject} parent - The parent of the dereferenced object + * @argument {string} parentPropName - The prop name of the parent object whose value was dereferenced + */ + onDereference?(path: string, value: JSONSchemaObject, parent?: JSONSchemaObject, parentPropName?: string): void; + + /** + * Whether a reference should resolve relative to its directory/path, or from the cwd + * + * Default: `relative` + */ + externalReferenceResolution?: "relative" | "root"; +} + /** * Options that determine how JSON schemas are parsed, resolved, and dereferenced. * * @param [options] - Overridden options * @class */ -export interface $RefParserOptions { +export interface $RefParserOptions { /** * The `parse` options determine how different types of files will be parsed. * @@ -42,10 +77,10 @@ export interface $RefParserOptions { * Determines whether external $ref pointers will be resolved. If this option is disabled, then external `$ref` pointers will simply be ignored. */ external?: boolean; - file?: Partial | boolean; - http?: HTTPResolverOptions | boolean; + file?: Partial> | boolean; + http?: HTTPResolverOptions | boolean; } & { - [key: string]: Partial | HTTPResolverOptions | boolean | undefined; + [key: string]: Partial> | HTTPResolverOptions | boolean | undefined; }; /** @@ -58,47 +93,14 @@ export interface $RefParserOptions { /** * The `dereference` options control how JSON Schema `$Ref` Parser will dereference `$ref` pointers within the JSON schema. */ - dereference: { - /** - * Determines whether circular `$ref` pointers are handled. - * - * If set to `false`, then a `ReferenceError` will be thrown if the schema contains any circular references. - * - * If set to `"ignore"`, then circular references will simply be ignored. No error will be thrown, but the `$Refs.circular` property will still be set to `true`. - */ - circular?: boolean | "ignore"; - - /** - * A function, called for each path, which can return true to stop this path and all - * subpaths from being dereferenced further. This is useful in schemas where some - * subpaths contain literal $ref keys that should not be dereferenced. - */ - excludedPathMatcher?(path: string): boolean; - - /** - * Callback invoked during dereferencing. - * - * @argument {string} path - The path being dereferenced (ie. the `$ref` string) - * @argument {JSONSchemaObject} value - The JSON-Schema that the `$ref` resolved to - * @argument {JSONSchemaObject} parent - The parent of the dereferenced object - * @argument {string} parentPropName - The prop name of the parent object whose value was dereferenced - */ - onDereference?(path: string, value: JSONSchemaObject, parent?: JSONSchemaObject, parentPropName?: string): void; + dereference: DereferenceOptions; - /** - * Whether a reference should resolve relative to its directory/path, or from the cwd - * - * Default: `relative` - */ - externalReferenceResolution?: "relative" | "root"; - - /** - * Whether to clone the schema before dereferencing it. - * This is useful when you want to dereference the same schema multiple times, but you don't want to modify the original schema. - * Default: `true` due to mutating the input being the default behavior historically - */ - mutateInputSchema?: boolean; - }; + /** + * Whether to clone the schema before dereferencing it. + * This is useful when you want to dereference the same schema multiple times, but you don't want to modify the original schema. + * Default: `true` due to mutating the input being the default behavior historically + */ + mutateInputSchema?: boolean; } export const getJsonSchemaRefParserDefaultOptions = () => { @@ -168,19 +170,19 @@ export const getJsonSchemaRefParserDefaultOptions = () => { }, mutateInputSchema: true, - } as $RefParserOptions; + } as $RefParserOptions; return defaults; }; -export const getNewOptions = (options: DeepPartial<$RefParserOptions> | undefined): $RefParserOptions => { +export const getNewOptions = (options: O | undefined): O & $RefParserOptions => { const newOptions = getJsonSchemaRefParserDefaultOptions(); if (options) { merge(newOptions, options); } - return newOptions; + return newOptions as O & $RefParserOptions; }; -export type Options = $RefParserOptions; -export type ParserOptions = DeepPartial<$RefParserOptions>; +export type Options = $RefParserOptions; +export type ParserOptions = DeepPartial<$RefParserOptions>; /** * Merges the properties of the source object into the target object. * diff --git a/lib/parse.ts b/lib/parse.ts index 1c741178..399122b7 100644 --- a/lib/parse.ts +++ b/lib/parse.ts @@ -10,14 +10,16 @@ import { } from "./util/errors.js"; import type $Refs from "./refs.js"; import type { Options } from "./options.js"; -import type { FileInfo } from "./types/index.js"; - -export default parse; +import type { FileInfo, JSONSchema } from "./types/index.js"; /** * Reads and parses the specified file path or URL. */ -async function parse(path: string, $refs: $Refs, options: Options) { +async function parse( + path: string, + $refs: $Refs, + options: O, +) { // Remove the URL fragment, if any const hashIndex = path.indexOf("#"); let hash = ""; @@ -40,11 +42,11 @@ async function parse(path: string, $refs: $Refs, options: Options) { // Read the file and then parse the data try { - const resolver = await readFile(file, options, $refs); + const resolver = await readFile(file, options, $refs); $ref.pathType = resolver.plugin.name; file.data = resolver.result; - const parser = await parseFile(file, options, $refs); + const parser = await parseFile(file, options, $refs); $ref.value = parser.result; return parser.result; @@ -64,11 +66,15 @@ async function parse(path: string, $refs: $Refs, options: Options) { * @param file.url - The full URL of the referenced file * @param file.extension - The lowercased file extension (e.g. ".txt", ".html", etc.) * @param options - * + * @param $refs * @returns * The promise resolves with the raw file contents and the resolver that was used. */ -async function readFile(file: FileInfo, options: Options, $refs: $Refs): Promise { +async function readFile( + file: FileInfo, + options: O, + $refs: $Refs, +): Promise { // console.log('Reading %s', file.url); // Find the resolvers that can read this file @@ -105,11 +111,16 @@ async function readFile(file: FileInfo, options: Options, $refs: $Refs): Promise * @param file.extension - The lowercased file extension (e.g. ".txt", ".html", etc.) * @param file.data - The file contents. This will be whatever data type was returned by the resolver * @param options + * @param $refs * * @returns * The promise resolves with the parsed file contents and the parser that was used. */ -async function parseFile(file: FileInfo, options: Options, $refs: $Refs) { +async function parseFile( + file: FileInfo, + options: O, + $refs: $Refs, +) { // Find the parsers that can read this file type. // If none of the parsers are an exact match for this file, then we'll try ALL of them. // This handles situations where the file IS a supported type, just with an unknown extension. @@ -120,7 +131,7 @@ async function parseFile(file: FileInfo, options: Options, $refs: $Refs) { // Run the parsers, in order, until one of them succeeds plugins.sort(parsers); try { - const parser = await plugins.run(parsers, "parse", file, $refs); + const parser = await plugins.run(parsers, "parse", file, $refs); if (!parser.plugin.allowEmpty && isEmpty(parser.result)) { throw ono.syntax(`Error parsing "${file.url}" as ${parser.plugin.name}. \nParsed value is empty`); } else { @@ -156,3 +167,4 @@ function isEmpty(value: any) { (Buffer.isBuffer(value) && value.length === 0) ); } +export default parse; diff --git a/lib/pointer.ts b/lib/pointer.ts index e5b5d62a..fef7b237 100644 --- a/lib/pointer.ts +++ b/lib/pointer.ts @@ -3,6 +3,7 @@ import type $RefParserOptions from "./options.js"; import $Ref from "./ref.js"; import * as url from "./util/url.js"; import { JSONParserError, InvalidPointerError, MissingPointerError, isHandledError } from "./util/errors.js"; +import type { JSONSchema } from "./types"; const slashes = /\//g; const tildes = /~/g; @@ -25,11 +26,11 @@ const safeDecodeURIComponent = (encodedURIComponent: string): string => { * @param [friendlyPath] - The original user-specified path (used for error messages) * @class */ -class Pointer { +class Pointer { /** * The {@link $Ref} object that contains this {@link Pointer} object. */ - $ref: $Ref; + $ref: $Ref; /** * The file path or URL, containing the JSON pointer in the hash. @@ -58,7 +59,7 @@ class Pointer { */ indirections: number; - constructor($ref: $Ref, path: string, friendlyPath?: string) { + constructor($ref: $Ref, path: string, friendlyPath?: string) { this.$ref = $ref; this.path = path; @@ -85,7 +86,7 @@ class Pointer { * the {@link Pointer#$ref} and {@link Pointer#path} will reflect the resolution path * of the resolved value. */ - resolve(obj: any, options?: $RefParserOptions, pathFromRoot?: string) { + resolve(obj: any, options?: $RefParserOptions, pathFromRoot?: string) { const tokens = Pointer.parse(this.path, this.originalPath); // Crawl the object, one token at a time @@ -143,7 +144,7 @@ class Pointer { * @returns * Returns the modified object, or an entirely new object if the entire object is overwritten. */ - set(obj: any, value: any, options?: $RefParserOptions) { + set(obj: any, value: any, options?: $RefParserOptions) { const tokens = Pointer.parse(this.path); let token; diff --git a/lib/ref.ts b/lib/ref.ts index 68945acb..3f0a1573 100644 --- a/lib/ref.ts +++ b/lib/ref.ts @@ -4,6 +4,7 @@ import { InvalidPointerError, isHandledError, normalizeError } from "./util/erro import { safePointerToPath, stripHash, getHash } from "./util/url.js"; import type $Refs from "./refs.js"; import type $RefParserOptions from "./options.js"; +import type { ParserOptions } from "./options.js"; import type { JSONSchema } from "./types"; export type $RefError = JSONParserError | ResolverError | ParserError | MissingPointerError; @@ -13,7 +14,7 @@ export type $RefError = JSONParserError | ResolverError | ParserError | MissingP * * @class */ -class $Ref { +class $Ref { /** * The file path or URL of the referenced file. * This path is relative to the path of the main JSON schema file. @@ -39,7 +40,7 @@ class $Ref { * * @type {$Refs} */ - $refs: $Refs; + $refs: $Refs; /** * Indicates the type of {@link $Ref#path} (e.g. "file", "http", etc.) @@ -51,7 +52,7 @@ class $Ref { */ errors: Array<$RefError> = []; - constructor($refs: $Refs) { + constructor($refs: $Refs) { this.$refs = $refs; } @@ -87,7 +88,7 @@ class $Ref { * @param options * @returns */ - exists(path: string, options?: $RefParserOptions) { + exists(path: string, options?: $RefParserOptions) { try { this.resolve(path, options); return true; @@ -103,7 +104,7 @@ class $Ref { * @param options * @returns - Returns the resolved value */ - get(path: any, options: $RefParserOptions) { + get(path: string, options?: $RefParserOptions) { return this.resolve(path, options)?.value; } @@ -116,8 +117,8 @@ class $Ref { * @param pathFromRoot - The path of `obj` from the schema root * @returns */ - resolve(path: any, options?: $RefParserOptions, friendlyPath?: string, pathFromRoot?: string) { - const pointer = new Pointer(this, path, friendlyPath); + resolve(path: string, options?: $RefParserOptions, friendlyPath?: string, pathFromRoot?: string) { + const pointer = new Pointer(this, path, friendlyPath); try { return pointer.resolve(this.value, options, pathFromRoot); } catch (err: any) { @@ -185,12 +186,12 @@ class $Ref { * @param options * @returns */ - static isAllowed$Ref(value: unknown, options?: $RefParserOptions) { + static isAllowed$Ref(value: unknown, options?: ParserOptions) { if (this.is$Ref(value)) { if (value.$ref.substring(0, 2) === "#/" || value.$ref === "#") { // It's a JSON Pointer reference, which is always allowed return true; - } else if (value.$ref[0] !== "#" && (!options || options.resolve.external)) { + } else if (value.$ref[0] !== "#" && (!options || options.resolve?.external)) { // It's an external reference, which is allowed by the options return true; } @@ -266,7 +267,7 @@ class $Ref { * @param resolvedValue - The resolved value, which can be any type * @returns - Returns the dereferenced value */ - static dereference($ref: $Ref, resolvedValue: JSONSchema): JSONSchema { + static dereference($ref: $Ref, resolvedValue: S): S { if (resolvedValue && typeof resolvedValue === "object" && $Ref.isExtended$Ref($ref)) { const merged = {}; for (const key of Object.keys($ref)) { @@ -283,7 +284,7 @@ class $Ref { } } - return merged; + return merged as S; } else { // Completely replace the original reference with the resolved value return resolvedValue; diff --git a/lib/refs.ts b/lib/refs.ts index acc48d6f..8105d926 100644 --- a/lib/refs.ts +++ b/lib/refs.ts @@ -2,12 +2,12 @@ import { ono } from "@jsdevtools/ono"; import $Ref from "./ref.js"; import * as url from "./util/url.js"; import type { JSONSchema4Type, JSONSchema6Type, JSONSchema7Type } from "json-schema"; -import type { JSONSchema } from "./types/index.js"; import type $RefParserOptions from "./options.js"; import convertPathToPosix from "./util/convert-path-to-posix"; +import type { JSONSchema } from "./types"; -interface $RefsMap { - [url: string]: $Ref; +interface $RefsMap { + [url: string]: $Ref; } /** * When you call the resolve method, the value that gets passed to the callback function (or Promise) is a $Refs object. This same object is accessible via the parser.$refs property of $RefParser objects. @@ -16,7 +16,7 @@ interface $RefsMap { * * See https://apitools.dev/json-schema-ref-parser/docs/refs.html */ -export default class $Refs { +export default class $Refs { /** * This property is true if the schema contains any circular references. You may want to check this property before serializing the dereferenced schema as JSON, since JSON.stringify() does not support circular references by default. * @@ -45,13 +45,13 @@ export default class $Refs { * * @param types (optional) Optionally only return values from certain locations ("file", "http", etc.) */ - values(...types: string[]): JSONSchema { + values(...types: string[]): S { const $refs = this._$refs; const paths = getPaths($refs, types); return paths.reduce>((obj, path) => { obj[convertPathToPosix(path.decoded)] = $refs[path.encoded].value; return obj; - }, {}); + }, {}) as S; } /** @@ -84,17 +84,17 @@ export default class $Refs { * @param [options] * @returns - Returns the resolved value */ - get(path: string, options?: $RefParserOptions): JSONSchema4Type | JSONSchema6Type | JSONSchema7Type { + get(path: string, options?: $RefParserOptions): JSONSchema4Type | JSONSchema6Type | JSONSchema7Type { return this._resolve(path, "", options)!.value; } /** * Sets the value at the given path in the schema. If the property, or any of its parents, don't exist, they will be created. * - * @param $ref The JSON Reference path, optionally with a JSON Pointer in the hash + * @param path The JSON Reference path, optionally with a JSON Pointer in the hash * @param value The value to assign. Can be anything (object, string, number, etc.) */ - set(path: any, value: JSONSchema4Type | JSONSchema6Type | JSONSchema7Type) { + set(path: string, value: JSONSchema4Type | JSONSchema6Type | JSONSchema7Type) { const absPath = url.resolve(this._root$Ref.path!, path); const withoutHash = url.stripHash(absPath); const $ref = this._$refs[withoutHash]; @@ -126,7 +126,7 @@ export default class $Refs { _add(path: string) { const withoutHash = url.stripHash(path); - const $ref = new $Ref(this); + const $ref = new $Ref(this); $ref.path = withoutHash; this._$refs[withoutHash] = $ref; @@ -162,7 +162,7 @@ export default class $Refs { * @type {object} * @protected */ - _$refs: $RefsMap = {}; + _$refs: $RefsMap = {}; /** * The {@link $Ref} object that is the root of the JSON schema. @@ -170,7 +170,7 @@ export default class $Refs { * @type {$Ref} * @protected */ - _root$Ref: $Ref; + _root$Ref: $Ref; constructor() { /** @@ -215,7 +215,7 @@ export default class $Refs { * @param [types] - Only return paths of the given types ("file", "http", etc.) * @returns */ -function getPaths($refs: $RefsMap, types: string[]) { +function getPaths($refs: $RefsMap, types: string[]) { let paths = Object.keys($refs); // Filter the paths by type diff --git a/lib/resolve-external.ts b/lib/resolve-external.ts index 775abe71..d51577b8 100644 --- a/lib/resolve-external.ts +++ b/lib/resolve-external.ts @@ -4,12 +4,10 @@ import parse from "./parse.js"; import * as url from "./util/url.js"; import { isHandledError } from "./util/errors.js"; import type $Refs from "./refs.js"; -import type { Options } from "./options.js"; +import type { Options, ParserOptions } from "./options.js"; import type { JSONSchema } from "./types/index.js"; import type $RefParser from "./index.js"; -export default resolveExternal; - /** * Crawls the JSON schema, finds all external JSON references, and resolves their values. * This method does not mutate the JSON schema. The resolved values are added to {@link $RefParser#$refs}. @@ -20,7 +18,10 @@ export default resolveExternal; * The promise resolves once all JSON references in the schema have been resolved, * including nested references that are contained in externally-referenced files. */ -function resolveExternal(parser: $RefParser, options: Options) { +function resolveExternal( + parser: $RefParser, + options: Options, +) { if (!options.resolve.external) { // Nothing to resolve, so exit early return Promise.resolve(); @@ -51,10 +52,10 @@ function resolveExternal(parser: $RefParser, options: Options) { * If any of the JSON references point to files that contain additional JSON references, * then the corresponding promise will internally reference an array of promises. */ -function crawl( - obj: string | Buffer | JSONSchema | undefined | null, +function crawl( + obj: string | Buffer | S | undefined | null, path: string, - $refs: $Refs, + $refs: $Refs, options: Options, seen?: Set, external?: boolean, @@ -65,13 +66,13 @@ function crawl( if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj) && !seen.has(obj)) { seen.add(obj); // Track previously seen objects to avoid infinite recursion if ($Ref.isExternal$Ref(obj)) { - promises.push(resolve$Ref(obj, path, $refs, options)); + promises.push(resolve$Ref(obj, path, $refs, options)); } - const keys = Object.keys(obj) as (keyof typeof obj)[]; + const keys = Object.keys(obj) as string[]; for (const key of keys) { const keyPath = Pointer.join(path, key); - const value = obj[key] as string | JSONSchema | Buffer | undefined; + const value = obj[key as keyof typeof obj] as string | JSONSchema | Buffer | undefined; promises = promises.concat(crawl(value, keyPath, $refs, options, seen, external)); } } @@ -91,7 +92,12 @@ function crawl( * The promise resolves once all JSON references in the object have been resolved, * including nested references that are contained in externally-referenced files. */ -async function resolve$Ref($ref: JSONSchema, path: string, $refs: $Refs, options: Options) { +async function resolve$Ref( + $ref: S, + path: string, + $refs: $Refs, + options: Options, +) { const shouldResolveOnCwd = options.dereference.externalReferenceResolution === "root"; const resolvedPath = url.resolve(shouldResolveOnCwd ? url.cwd() : path, $ref.$ref!); const withoutHash = url.stripHash(resolvedPath); @@ -99,10 +105,10 @@ async function resolve$Ref($ref: JSONSchema, path: string, $refs: $Refs, options // $ref.$ref = url.relative($refs._root$Ref.path, resolvedPath); // Do we already have this $ref? - $ref = $refs._$refs[withoutHash]; - if ($ref) { + const ref = $refs._$refs[withoutHash]; + if (ref) { // We've already parsed this $ref, so use the existing value - return Promise.resolve($ref.value); + return Promise.resolve(ref.value); } // Parse the $referenced file/url @@ -127,3 +133,4 @@ async function resolve$Ref($ref: JSONSchema, path: string, $refs: $Refs, options return []; } } +export default resolveExternal; diff --git a/lib/resolvers/file.ts b/lib/resolvers/file.ts index e00ede2b..8cf209c9 100644 --- a/lib/resolvers/file.ts +++ b/lib/resolvers/file.ts @@ -2,7 +2,7 @@ import fs from "fs"; import { ono } from "@jsdevtools/ono"; import * as url from "../util/url.js"; import { ResolverError } from "../util/errors.js"; -import type { ResolverOptions } from "../types/index.js"; +import type { JSONSchema, ResolverOptions } from "../types/index.js"; import type { FileInfo } from "../types/index.js"; export default { @@ -36,4 +36,4 @@ export default { throw new ResolverError(ono(err, `Error opening file "${path}"`), path); } }, -} as ResolverOptions; +} as ResolverOptions; diff --git a/lib/resolvers/http.ts b/lib/resolvers/http.ts index c4c8971a..77885b98 100644 --- a/lib/resolvers/http.ts +++ b/lib/resolvers/http.ts @@ -1,7 +1,7 @@ import { ono } from "@jsdevtools/ono"; import * as url from "../util/url.js"; import { ResolverError } from "../util/errors.js"; -import type { FileInfo, HTTPResolverOptions } from "../types/index.js"; +import type { FileInfo, HTTPResolverOptions, JSONSchema } from "../types/index.js"; export default { /** @@ -59,14 +59,18 @@ export default { return download(u, this); }, -} as HTTPResolverOptions; +} as HTTPResolverOptions; /** * Downloads the given file. * @returns * The promise resolves with the raw downloaded data, or rejects if there is an HTTP error. */ -async function download(u: URL | string, httpOptions: HTTPResolverOptions, _redirects?: string[]): Promise { +async function download( + u: URL | string, + httpOptions: HTTPResolverOptions, + _redirects?: string[], +): Promise { u = url.parse(u); const redirects = _redirects || []; redirects.push(u.href); @@ -105,7 +109,7 @@ async function download(u: URL | string, httpOptions: HTTPResolverOptions, _redi * Sends an HTTP GET request. * The promise resolves with the HTTP Response object. */ -async function get(u: RequestInfo | URL, httpOptions: HTTPResolverOptions) { +async function get(u: RequestInfo | URL, httpOptions: HTTPResolverOptions) { let controller: any; let timeoutId: any; if (httpOptions.timeout) { diff --git a/lib/types/index.ts b/lib/types/index.ts index 93323119..36754fc0 100644 --- a/lib/types/index.ts +++ b/lib/types/index.ts @@ -10,14 +10,14 @@ import type $Refs from "../refs.js"; export type JSONSchema = JSONSchema4 | JSONSchema6 | JSONSchema7; export type JSONSchemaObject = JSONSchema4Object | JSONSchema6Object | JSONSchema7Object; -export type SchemaCallback = (err: Error | null, schema?: JSONSchema | object | null) => any; -export type $RefsCallback = (err: Error | null, $refs?: $Refs) => any; +export type SchemaCallback = (err: Error | null, schema?: S | object | null) => any; +export type $RefsCallback = (err: Error | null, $refs?: $Refs) => any; /** * See https://apitools.dev/json-schema-ref-parser/docs/options.html */ -export interface HTTPResolverOptions extends Partial { +export interface HTTPResolverOptions extends Partial> { /** * You can specify any HTTP headers that should be sent when downloading files. For example, some servers may require you to set the `Accept` or `Referrer` header. */ @@ -44,7 +44,7 @@ export interface HTTPResolverOptions extends Partial { * * See https://apitools.dev/json-schema-ref-parser/docs/plugins/resolvers.html */ -export interface ResolverOptions { +export interface ResolverOptions { name?: string; /** * All resolvers have an order property, even the built-in resolvers. If you don't specify an order property, then your resolver will run last. Specifying `order: 1`, like we did in this example, will make your resolver run first. Or you can squeeze your resolver in-between some of the built-in resolvers. For example, `order: 101` would make it run after the file resolver, but before the HTTP resolver. You can see the order of all the built-in resolvers by looking at their source code. @@ -69,7 +69,7 @@ export interface ResolverOptions { | (( file: FileInfo, callback?: (error: Error | null, data: string | null) => any, - ) => string | Buffer | JSONSchema | Promise); + ) => string | Buffer | S | Promise); } export interface Plugin { diff --git a/lib/util/errors.ts b/lib/util/errors.ts index ca59e295..e4e172a7 100644 --- a/lib/util/errors.ts +++ b/lib/util/errors.ts @@ -1,6 +1,8 @@ import { Ono } from "@jsdevtools/ono"; import { stripHash, toFileSystemPath } from "./url.js"; import type $RefParser from "../index.js"; +import type { ParserOptions } from "../index.js"; +import type { JSONSchema } from "../index.js"; import type $Ref from "../ref"; export type JSONParserErrorType = @@ -11,6 +13,7 @@ export type JSONParserErrorType = | "EUNMATCHEDRESOLVER" | "EMISSINGPOINTER" | "EINVALIDPOINTER"; + export class JSONParserError extends Error { public readonly name: string; public readonly message: string; @@ -34,10 +37,13 @@ export class JSONParserError extends Error { } } -export class JSONParserErrorGroup extends Error { - files: $RefParser; +export class JSONParserErrorGroup< + S extends JSONSchema = JSONSchema, + O extends ParserOptions = ParserOptions, +> extends Error { + files: $RefParser; - constructor(parser: $RefParser) { + constructor(parser: $RefParser) { super(); this.files = parser; @@ -49,10 +55,12 @@ export class JSONParserErrorGroup extends Error { Ono.extend(this); } - static getParserErrors(parser: any) { + static getParserErrors( + parser: $RefParser, + ) { const errors = []; - for (const $ref of Object.values(parser.$refs._$refs) as $Ref[]) { + for (const $ref of Object.values(parser.$refs._$refs) as $Ref[]) { if ($ref.errors) { errors.push(...$ref.errors); } @@ -70,7 +78,7 @@ export class JSONParserErrorGroup extends Error { | UnmatchedParserError | UnmatchedResolverError > { - return JSONParserErrorGroup.getParserErrors(this.files); + return JSONParserErrorGroup.getParserErrors(this.files); } } diff --git a/lib/util/plugins.ts b/lib/util/plugins.ts index 6901d21d..7768fbd8 100644 --- a/lib/util/plugins.ts +++ b/lib/util/plugins.ts @@ -3,7 +3,6 @@ import type $RefParserOptions from "../options.js"; import type { ResolverOptions } from "../types/index.js"; import type $Refs from "../refs.js"; import type { Plugin } from "../types/index.js"; -import type { JSONSchema } from "../types/index.js"; /** * Returns the given plugins as an array, rather than an object map. @@ -11,13 +10,13 @@ import type { JSONSchema } from "../types/index.js"; * * @returns */ -export function all(plugins: $RefParserOptions["resolve"]): Plugin[] { +export function all(plugins: $RefParserOptions["resolve"]): Plugin[] { return Object.keys(plugins) .filter((key) => { return typeof plugins[key] === "object"; }) .map((key) => { - (plugins[key] as ResolverOptions)!.name = key; + (plugins[key] as ResolverOptions)!.name = key; return plugins[key] as Plugin; }); } @@ -44,9 +43,9 @@ export function sort(plugins: Plugin[]) { }); } -export interface PluginResult { +export interface PluginResult { plugin: Plugin; - result?: string | Buffer | JSONSchema; + result?: string | Buffer | S; error?: any; } @@ -58,17 +57,17 @@ export interface PluginResult { * If the promise rejects, or the callback is called with an error, then the next plugin is called. * If ALL plugins fail, then the last error is thrown. */ -export async function run( +export async function run( plugins: Plugin[], - method: keyof Plugin | keyof ResolverOptions, + method: keyof Plugin | keyof ResolverOptions, file: FileInfo, - $refs: $Refs, + $refs: $Refs, ) { let plugin: Plugin; - let lastError: PluginResult; + let lastError: PluginResult; let index = 0; - return new Promise((resolve, reject) => { + return new Promise>((resolve, reject) => { runNextPlugin(); function runNextPlugin() { @@ -95,7 +94,7 @@ export async function run( } } - function callback(err: PluginResult["error"], result: PluginResult["result"]) { + function callback(err: PluginResult["error"], result: PluginResult["result"]) { if (err) { onError(err); } else { @@ -103,7 +102,7 @@ export async function run( } } - function onSuccess(result: PluginResult["result"]) { + function onSuccess(result: PluginResult["result"]) { // console.log(' success'); resolve({ plugin, @@ -111,7 +110,7 @@ export async function run( }); } - function onError(error: PluginResult["error"]) { + function onError(error: PluginResult["error"]) { // console.log(' %s', err.message || err); lastError = { plugin, @@ -128,9 +127,9 @@ export async function run( * If the value is a RegExp, then it will be tested against the file URL. * If the value is an array, then it will be compared against the file extension. */ -function getResult( +function getResult( obj: Plugin, - prop: keyof Plugin | keyof ResolverOptions, + prop: keyof Plugin | keyof ResolverOptions, file: FileInfo, callback?: (err?: Error, result?: any) => void, $refs?: any, diff --git a/test/specs/blank/blank.spec.ts b/test/specs/blank/blank.spec.ts index eea6e27f..37dc8443 100644 --- a/test/specs/blank/blank.spec.ts +++ b/test/specs/blank/blank.spec.ts @@ -82,14 +82,12 @@ describe("Blank files", () => { it("should dereference successfully", async () => { const schema = await $RefParser.dereference(path.rel("test/specs/blank/blank.yaml")); - // @ts-expect-error TS(2339): Property 'binary' does not exist on type 'JSONSche... Remove this comment to see the full error message schema.binary = helper.convertNodeBuffersToPOJOs(schema.binary); expect(schema).to.deep.equal(dereferencedSchema); }); it("should bundle successfully", async () => { const schema = await $RefParser.bundle(path.rel("test/specs/blank/blank.yaml")); - // @ts-expect-error TS(2339): Property 'binary' does not exist on type 'JSONSche... Remove this comment to see the full error message schema.binary = helper.convertNodeBuffersToPOJOs(schema.binary); expect(schema).to.deep.equal(dereferencedSchema); }); diff --git a/test/specs/callbacks.spec.ts b/test/specs/callbacks.spec.ts index d61f491c..6cba3931 100644 --- a/test/specs/callbacks.spec.ts +++ b/test/specs/callbacks.spec.ts @@ -42,6 +42,7 @@ describe("Callback & Promise syntax", () => { function testCallbackError(method: typeof methods[number]) { return () => new Promise((resolve, reject) => { + // @ts-ignore $RefParser[method](path.rel("test/specs/invalid/invalid.yaml"), (err: any, result: any) => { try { expect(err).to.be.an.instanceOf(ParserError); @@ -72,6 +73,7 @@ describe("Callback & Promise syntax", () => { function testPromiseError(method: typeof methods[number]) { return async function () { try { + // @ts-ignore await $RefParser[method](path.rel("test/specs/invalid/invalid.yaml")); helper.shouldNotGetCalled(); } catch (err: any) { diff --git a/test/specs/circular-external/circular-external.spec.ts b/test/specs/circular-external/circular-external.spec.ts index 528f731d..0f1b5379 100644 --- a/test/specs/circular-external/circular-external.spec.ts +++ b/test/specs/circular-external/circular-external.spec.ts @@ -25,7 +25,6 @@ describe("Schema with circular (recursive) external $refs", () => { helper.testResolve( path.rel("test/specs/circular-external/circular-external.yaml"), path.abs("test/specs/circular-external/circular-external.yaml"), - // @ts-expect-error TS(2554): Expected 2 arguments, but got 11. parsedSchema.schema, path.abs("test/specs/circular-external/definitions/pet.yaml"), parsedSchema.pet, diff --git a/test/specs/deep-circular/deep-circular.spec.ts b/test/specs/deep-circular/deep-circular.spec.ts index 3dc9c78f..ec432869 100644 --- a/test/specs/deep-circular/deep-circular.spec.ts +++ b/test/specs/deep-circular/deep-circular.spec.ts @@ -25,7 +25,6 @@ describe("Schema with deeply-nested circular $refs", () => { helper.testResolve( path.rel("test/specs/deep-circular/deep-circular.yaml"), path.abs("test/specs/deep-circular/deep-circular.yaml"), - // @ts-expect-error TS(2554): Expected 2 arguments, but got 7. parsedSchema.schema, path.abs("test/specs/deep-circular/definitions/name.yaml"), parsedSchema.name, diff --git a/test/specs/deep/deep.spec.ts b/test/specs/deep/deep.spec.ts index 0b7c6eef..509768e1 100644 --- a/test/specs/deep/deep.spec.ts +++ b/test/specs/deep/deep.spec.ts @@ -22,7 +22,6 @@ describe("Schema with deeply-nested $refs", () => { helper.testResolve( path.rel("test/specs/deep/deep.yaml"), path.abs("test/specs/deep/deep.yaml"), - // @ts-expect-error TS(2554): Expected 2 arguments, but got 7. parsedSchema.schema, path.abs("test/specs/deep/definitions/name.yaml"), parsedSchema.name, diff --git a/test/specs/external-multiple/external-multiple.spec.ts b/test/specs/external-multiple/external-multiple.spec.ts index f3acf838..e6fcd692 100644 --- a/test/specs/external-multiple/external-multiple.spec.ts +++ b/test/specs/external-multiple/external-multiple.spec.ts @@ -22,7 +22,6 @@ describe("Schema with multiple external $refs to different parts of a file", () helper.testResolve( path.rel("test/specs/external-multiple/external-multiple.yaml"), path.abs("test/specs/external-multiple/external-multiple.yaml"), - // @ts-expect-error TS(2554): Expected 2 arguments, but got 5. parsedSchema.schema, path.abs("test/specs/external-multiple/definitions.yaml"), parsedSchema.definitions, diff --git a/test/specs/external-partial/external-partial.spec.ts b/test/specs/external-partial/external-partial.spec.ts index a35e2e8a..854f0dcf 100644 --- a/test/specs/external-partial/external-partial.spec.ts +++ b/test/specs/external-partial/external-partial.spec.ts @@ -22,7 +22,6 @@ describe("Schema with $refs to parts of external files", () => { helper.testResolve( path.rel("test/specs/external-partial/external-partial.yaml"), path.abs("test/specs/external-partial/external-partial.yaml"), - // @ts-expect-error TS(2554): Expected 2 arguments, but got 9. parsedSchema.schema, path.abs("test/specs/external-partial/definitions/definitions.json"), parsedSchema.definitions, diff --git a/test/specs/parsers/parsers.spec.ts b/test/specs/parsers/parsers.spec.ts index c76b04fe..4853f8a0 100644 --- a/test/specs/parsers/parsers.spec.ts +++ b/test/specs/parsers/parsers.spec.ts @@ -163,7 +163,6 @@ describe("References to non-JSON files", () => { }, }, }); - // @ts-expect-error TS(2339): Property 'definitions' does not exist on type 'voi... Remove this comment to see the full error message schema.definitions.binary = helper.convertNodeBuffersToPOJOs(schema.definitions.binary); expect(schema).to.deep.equal(dereferencedSchema.customParser); }); @@ -181,7 +180,6 @@ describe("References to non-JSON files", () => { }, }, }); - // @ts-expect-error TS(2339): Property 'definitions' does not exist on type 'voi... Remove this comment to see the full error message schema.definitions.binary = helper.convertNodeBuffersToPOJOs(schema.definitions.binary); expect(schema).to.deep.equal(dereferencedSchema.customParser); }); @@ -201,7 +199,6 @@ describe("References to non-JSON files", () => { }, }, }); - // @ts-expect-error TS(2339): Property 'definitions' does not exist on type 'voi... Remove this comment to see the full error message schema.definitions.binary = helper.convertNodeBuffersToPOJOs(schema.definitions.binary); expect(schema).to.deep.equal(dereferencedSchema.customParser); }); @@ -220,7 +217,6 @@ describe("References to non-JSON files", () => { }, }, }); - // @ts-expect-error TS(2339): Property 'definitions' does not exist on type 'voi... Remove this comment to see the full error message schema.definitions.binary = helper.convertNodeBuffersToPOJOs(schema.definitions.binary); expect(schema).to.deep.equal(dereferencedSchema.defaultParsers); });