diff --git a/package-lock.json b/package-lock.json index 22f760b6..3c8145cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -938,20 +938,13 @@ "integrity": "sha512-BYY7IavBjwsWWSmVcMz2A9mKiDD9RvacnsItgmy1xV8cmgbtxFfKmKMtkVpD7pYtkx4mIW4800yZBXueVFIWPw==" }, "@json-schema-tools/dereferencer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@json-schema-tools/dereferencer/-/dereferencer-1.4.0.tgz", - "integrity": "sha512-Wzn9Ss9AaCWGGYJZNN3dMFq5GhZnrhzqgGkqN3hEP2A3/9gxIC9B+pm6ejFPmWF9M2xbO+zLn5BB8rEX4xdW/w==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@json-schema-tools/dereferencer/-/dereferencer-1.5.1.tgz", + "integrity": "sha512-CUpdGpxNTq1ebMkrgVxS03FHfwkGiw63c+GNzqFAqwqsxR0OsR79aqK8h2ybxTIEhdwiaknSnlUgtUIy7FJ+3A==", "requires": { - "@json-schema-tools/reference-resolver": "^1.0.7", + "@json-schema-tools/reference-resolver": "^1.2.1", "@json-schema-tools/traverse": "^1.7.5", "fast-safe-stringify": "^2.0.7" - }, - "dependencies": { - "fast-safe-stringify": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", - "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" - } } }, "@json-schema-tools/meta-schema": { @@ -960,23 +953,12 @@ "integrity": "sha512-zg04/H1ADj9fnkEzc5uNa6om00dOR8dUNil3YSuLh46wfTQva3a9JOcqKudAf7NECKldlYI94bSiF++NgLSbBw==" }, "@json-schema-tools/reference-resolver": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@json-schema-tools/reference-resolver/-/reference-resolver-1.1.1.tgz", - "integrity": "sha512-T0ntnhaMUOEBNzb37Y7LRJdCt34vlExEqyVvcRO1iScLpVnBhddgECS1XjT8eZQVc7UuH5KssTy8T0/g7m1xbw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@json-schema-tools/reference-resolver/-/reference-resolver-1.2.1.tgz", + "integrity": "sha512-zsiV4GPjLJ6qhWRytSdEWfENy+ycCgE5Uu62IL0E8vSviohOAMIpAuFf15ByNvlj6U8M/pFg0OfPWy7R8+vWWA==", "requires": { "@json-schema-spec/json-pointer": "^0.1.2", "isomorphic-fetch": "^3.0.0" - }, - "dependencies": { - "isomorphic-fetch": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", - "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", - "requires": { - "node-fetch": "^2.6.1", - "whatwg-fetch": "^3.4.1" - } - } } }, "@json-schema-tools/traverse": { diff --git a/package.json b/package.json index 963d88be..c15f9ad2 100644 --- a/package.json +++ b/package.json @@ -30,9 +30,9 @@ }, "homepage": "https://github.com/open-rpc/schema-utils-js#readme", "dependencies": { - "@json-schema-tools/dereferencer": "^1.4.0", + "@json-schema-tools/dereferencer": "^1.5.1", "@json-schema-tools/meta-schema": "^1.5.10", - "@json-schema-tools/reference-resolver": "^1.1.1", + "@json-schema-tools/reference-resolver": "^1.2.1", "@open-rpc/meta-schema": "^1.14.0", "ajv": "^6.10.0", "detect-node": "^2.0.4", diff --git a/src/dereference-document.test.ts b/src/dereference-document.test.ts index 3b8e8d0e..33edc75b 100644 --- a/src/dereference-document.test.ts +++ b/src/dereference-document.test.ts @@ -392,3 +392,40 @@ describe("dereferenceDocument", () => { } }); }); + +describe("custom protocols", () => { + it("can handle a a basic impl", async () => { + expect.assertions(1); + + const testDoc = { + openrpc: "1.2.4", + info: { + title: "foo", + version: "1", + }, + methods: [ + { + name: "foo", + params: [], + result: { + name: "fooResult", + schema: { + $ref: "ipfs://1231231231231" + } + } + } + ] + }; + + const result = await dereferenceDocument(testDoc as OpenrpcDocument, { + protocolHandlerMap: { + ipfs: (ref) => Promise.resolve({ + type: "string", + title: "schemaFromIpfs" + }) + } + }) as any; + + expect(result.methods[0].result.schema.title).toBe("schemaFromIpfs"); + }); +}); diff --git a/src/dereference-document.ts b/src/dereference-document.ts index faf39e8c..2ef8b637 100644 --- a/src/dereference-document.ts +++ b/src/dereference-document.ts @@ -1,7 +1,7 @@ -import Dereferencer from "@json-schema-tools/dereferencer"; +import Dereferencer, { DereferencerOptions } from "@json-schema-tools/dereferencer"; import { OpenrpcDocument as OpenRPC, ReferenceObject, ExamplePairingObject, JSONSchema, SchemaComponents, ContentDescriptorComponents, ContentDescriptorObject, OpenrpcDocument, MethodObject } from "@open-rpc/meta-schema"; import referenceResolver from "@json-schema-tools/reference-resolver"; -import safeStringify from "fast-safe-stringify"; +import safeStringify from "fast-safe-stringify"; /** * Provides an error interface for OpenRPC Document dereferencing problems @@ -20,12 +20,17 @@ export class OpenRPCDocumentDereferencingError implements Error { } } -const derefItem = async (item: ReferenceObject, doc: OpenRPC) => { +const derefItem = async (item: ReferenceObject, doc: OpenRPC, dereferencerOptions?: DereferencerOptions) => { const { $ref } = item; if ($ref === undefined) { return item; } try { - return await referenceResolver($ref, doc); + if (dereferencerOptions && dereferencerOptions.protocolHandlerMap) { + for (const k of Object.keys(dereferencerOptions.protocolHandlerMap)) { + referenceResolver.protocolHandlerMap[k] = dereferencerOptions.protocolHandlerMap[k]; + } + } + return await referenceResolver.resolve($ref, doc) as any; } catch (err) { throw new OpenRPCDocumentDereferencingError([ `unable to eval pointer against OpenRPC Document.`, @@ -38,41 +43,40 @@ const derefItem = async (item: ReferenceObject, doc: OpenRPC) => { } }; -const derefItems = async (items: ReferenceObject[], doc: OpenRPC) => { +const derefItems = async (items: ReferenceObject[], doc: OpenRPC, dereferencerOptions?: DereferencerOptions) => { const dereffed = []; for (const i of items) { - dereffed.push(await derefItem(i, doc)) + dereffed.push(await derefItem(i, doc, dereferencerOptions)) } return dereffed; }; -const handleSchemaWithSchemaComponents = async (s: JSONSchema, schemaComponents: SchemaComponents | undefined) => { +const handleSchemaWithSchemaComponents = async (s: JSONSchema, schemaComponents: SchemaComponents | undefined, dereferencerOptions?: DereferencerOptions) => { if (s === true || s === false) { return Promise.resolve(s); } - if (schemaComponents !== undefined) { - s.components = { schemas: schemaComponents } - } + const dereffer = new Dereferencer(s, { + ...dereferencerOptions, + rootSchema: { + components: { schemas: schemaComponents } + } + }); - const dereffer = new Dereferencer(s); try { - const dereffed = await dereffer.resolve(); - if (dereffed !== true && dereffed !== false) { - delete dereffed.components; - } - return dereffed; + return await dereffer.resolve(); } catch (e) { throw new OpenRPCDocumentDereferencingError([ "Unable to parse reference inside of JSONSchema", s.title ? `Schema Title: ${s.title}` : "", `error message: ${e.message}`, - `schema in question: ${safeStringify(s)}` + `schema in question: ${safeStringify(s)}`, + `underlying error ${e.message}`, ].join("\n")); } }; -const handleSchemaComponents = async (doc: OpenrpcDocument): Promise => { +const handleSchemaComponents = async (doc: OpenrpcDocument, dereferencerOptions?: DereferencerOptions): Promise => { if (doc.components === undefined) { return Promise.resolve(doc); } @@ -84,13 +88,13 @@ const handleSchemaComponents = async (doc: OpenrpcDocument): Promise => { +const handleSchemasInsideContentDescriptorComponents = async (doc: OpenrpcDocument, dereferencerOptions?: DereferencerOptions): Promise => { if (doc.components === undefined) { return Promise.resolve(doc); } @@ -107,35 +111,35 @@ const handleSchemasInsideContentDescriptorComponents = async (doc: OpenrpcDocume } for (const cdK of cdsKeys) { - cds[cdK].schema = await handleSchemaWithSchemaComponents(cds[cdK].schema, componentSchemas); + cds[cdK].schema = await handleSchemaWithSchemaComponents(cds[cdK].schema, componentSchemas, dereferencerOptions); } return doc; }; -const handleMethod = async (method: MethodObject, doc: OpenrpcDocument): Promise => { +const handleMethod = async (method: MethodObject, doc: OpenrpcDocument, dereferencerOptions?: DereferencerOptions): Promise => { if (method.tags !== undefined) { - method.tags = await derefItems(method.tags as ReferenceObject[], doc); + method.tags = await derefItems(method.tags as ReferenceObject[], doc, dereferencerOptions); } if (method.errors !== undefined) { - method.errors = await derefItems(method.errors as ReferenceObject[], doc); + method.errors = await derefItems(method.errors as ReferenceObject[], doc, dereferencerOptions); } if (method.links !== undefined) { - method.links = await derefItems(method.links as ReferenceObject[], doc); + method.links = await derefItems(method.links as ReferenceObject[], doc, dereferencerOptions); } if (method.examples !== undefined) { - method.examples = await derefItems(method.examples as ReferenceObject[], doc); + method.examples = await derefItems(method.examples as ReferenceObject[], doc, dereferencerOptions); for (const exPairing of method.examples as ExamplePairingObject[]) { - exPairing.params = await derefItems(exPairing.params as ReferenceObject[], doc); - exPairing.result = await derefItem(exPairing.result as ReferenceObject, doc); + exPairing.params = await derefItems(exPairing.params as ReferenceObject[], doc, dereferencerOptions); + exPairing.result = await derefItem(exPairing.result as ReferenceObject, doc, dereferencerOptions); } } - method.params = await derefItems(method.params as ReferenceObject[], doc); - method.result = await derefItem(method.result as ReferenceObject, doc); + method.params = await derefItems(method.params as ReferenceObject[], doc, dereferencerOptions); + method.result = await derefItem(method.result as ReferenceObject, doc, dereferencerOptions); let componentSchemas: SchemaComponents = {}; @@ -146,11 +150,11 @@ const handleMethod = async (method: MethodObject, doc: OpenrpcDocument): Promise const params = method.params as ContentDescriptorObject[]; for (const p of params) { - p.schema = await handleSchemaWithSchemaComponents(p.schema, componentSchemas); + p.schema = await handleSchemaWithSchemaComponents(p.schema, componentSchemas, dereferencerOptions); } const result = method.result as ContentDescriptorObject; - result.schema = await handleSchemaWithSchemaComponents(result.schema, componentSchemas); + result.schema = await handleSchemaWithSchemaComponents(result.schema, componentSchemas, dereferencerOptions); return method; }; @@ -179,14 +183,14 @@ const handleMethod = async (method: MethodObject, doc: OpenrpcDocument): Promise * ``` * */ -export default async function dereferenceDocument(openrpcDocument: OpenRPC): Promise { +export default async function dereferenceDocument(openrpcDocument: OpenRPC, dereferencerOptions?: DereferencerOptions): Promise { let derefDoc = { ...openrpcDocument }; - derefDoc = await handleSchemaComponents(derefDoc); - derefDoc = await handleSchemasInsideContentDescriptorComponents(derefDoc); + derefDoc = await handleSchemaComponents(derefDoc, dereferencerOptions); + derefDoc = await handleSchemasInsideContentDescriptorComponents(derefDoc, dereferencerOptions); const methods = [] as any; for (const method of derefDoc.methods) { - methods.push(await handleMethod(method, derefDoc)); + methods.push(await handleMethod(method, derefDoc, dereferencerOptions)); } derefDoc.methods = methods; diff --git a/src/parse-open-rpc-document.ts b/src/parse-open-rpc-document.ts index 0cfb6661..7502e1d9 100644 --- a/src/parse-open-rpc-document.ts +++ b/src/parse-open-rpc-document.ts @@ -3,6 +3,7 @@ import validateOpenRPCDocument, { OpenRPCDocumentValidationError } from "./valid import isUrl = require("is-url"); import { OpenrpcDocument } from "@open-rpc/meta-schema"; import { TGetOpenRPCDocument } from "./get-open-rpc-document"; +import { ProtocolHandlerMap } from "@json-schema-tools/reference-resolver/build/reference-resolver"; /** * @ignore @@ -33,11 +34,20 @@ export interface ParseOpenRPCDocumentOptions { * */ dereference?: boolean; + + /* + * If Dereferencing is enabled, this option allows the user to implement custom protocol handling for the parsed references. + * + * @default true + * + */ + dereferencerProtocolHandlerMap?: ProtocolHandlerMap; } const defaultParseOpenRPCDocumentOptions = { dereference: true, validate: true, + dereferencerProtocolHandlerMap: {}, }; const makeParseOpenRPCDocument = (fetchUrlSchema: TGetOpenRPCDocument, readSchemaFromFile: TGetOpenRPCDocument) => { @@ -104,7 +114,7 @@ const makeParseOpenRPCDocument = (fetchUrlSchema: TGetOpenRPCDocument, readSchem let postDeref: OpenrpcDocument = parsedSchema; if (parseOptions.dereference) { - postDeref = await dereferenceDocument(parsedSchema); + postDeref = await dereferenceDocument(parsedSchema, { protocolHandlerMap: options.dereferencerProtocolHandlerMap }); } if (parseOptions.validate) {