From 88bcbc7aa91a890b352865a8e4c1528e532d0254 Mon Sep 17 00:00:00 2001 From: redneckz Date: Thu, 27 Jul 2023 10:22:41 +0300 Subject: [PATCH] Add sync version of resolveRef --- .vscode/settings.json | 3 ++- package.json | 2 +- src/URIResolver.ts | 2 +- src/collectRef.ts | 8 +++++++ src/index.ts | 2 ++ src/mapJSONNode.ts | 29 ++++++++++++++++++++++++ src/mergeRecords.ts | 2 ++ src/resolveRef.spec.ts | 50 ++++++++++-------------------------------- src/resolveRef.sync.ts | 15 +++++++++++++ src/resolveRef.ts | 43 ++++++++++++++---------------------- src/visitRef.ts | 27 ++++++++++------------- 11 files changed, 99 insertions(+), 84 deletions(-) create mode 100644 src/collectRef.ts create mode 100644 src/mapJSONNode.ts create mode 100644 src/mergeRecords.ts create mode 100644 src/resolveRef.sync.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index f89ed5f..1c7bc55 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "editor.formatOnSave": true + "editor.formatOnSave": true, + "java.saveActions.organizeImports": false } diff --git a/package.json b/package.json index 9603487..fba2db0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@redneckz/json-ref", - "version": "0.0.4", + "version": "0.0.5", "license": "MIT", "author": { "name": "redneckz", diff --git a/src/URIResolver.ts b/src/URIResolver.ts index 3d3ae51..23883a2 100644 --- a/src/URIResolver.ts +++ b/src/URIResolver.ts @@ -1,3 +1,3 @@ import type { JSONNode } from './JSONNode'; -export type URIResolver = (uri: string) => Promise | JSONNode; +export type URIResolver | JSONNode = Promise> = (uri: string) => R; diff --git a/src/collectRef.ts b/src/collectRef.ts new file mode 100644 index 0000000..bce5668 --- /dev/null +++ b/src/collectRef.ts @@ -0,0 +1,8 @@ +import { type JSONNode } from './JSONNode'; +import { visitRef } from './visitRef'; + +export const collectRef = (json: JSONNode): string[] => { + const refs: string[] = []; + visitRef(json, _ => refs.push(_)); + return refs; +}; diff --git a/src/index.ts b/src/index.ts index cb43bb4..3cba98a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,7 @@ export { resolveRef } from './resolveRef'; +export { resolveRef as resolveRefSync } from './resolveRef.sync'; export { visitRef, type RefVisitor } from './visitRef'; +export { collectRef } from './collectRef'; export { resolveJPointer } from './resolveJPointer'; diff --git a/src/mapJSONNode.ts b/src/mapJSONNode.ts new file mode 100644 index 0000000..18f0fc1 --- /dev/null +++ b/src/mapJSONNode.ts @@ -0,0 +1,29 @@ +import { + isJSONArray, + isJSONRecord, + isJSONRef, + type JSONNode, + type JSONRecord, + type JSONRef, + type JSONScalar +} from './JSONNode'; + +export const mapJSONNode = | void>( + json: JSONNode, + handlers: { + ref: (json: JSONRef) => R; + record: (json: JSONRecord) => R; + array: (json: JSONNode[]) => R; + scalar?: (json: JSONScalar | null) => R; + } +): R => { + if (isJSONRef(json)) { + return handlers.ref(json); + } else if (isJSONRecord(json)) { + return handlers.record(json); + } else if (isJSONArray(json)) { + return handlers.array(json); + } else { + return handlers.scalar ? handlers.scalar(json) : (json as R); + } +}; diff --git a/src/mergeRecords.ts b/src/mergeRecords.ts new file mode 100644 index 0000000..e7046c1 --- /dev/null +++ b/src/mergeRecords.ts @@ -0,0 +1,2 @@ +export const mergeRecords = >(entries: R[]): R => + entries.reduce((acc, _) => Object.assign(acc, _), {} as R); diff --git a/src/resolveRef.spec.ts b/src/resolveRef.spec.ts index 9faa87e..511a5db 100644 --- a/src/resolveRef.spec.ts +++ b/src/resolveRef.spec.ts @@ -2,66 +2,46 @@ import { resolveRef } from './resolveRef'; describe('resolveRef', () => { it('should substitute JSON addressed by URI in place of "$ref" declared inside object', async () => { - const constResolver = () => ({ some: { remote: 'json' } }); + const constResolver = async () => ({ some: { remote: 'json' } }); const result = await resolveRef( { - foo: { - bar: { $ref: 'http://some-remote.json' }, - baz: [123] - } + foo: { bar: { $ref: 'http://some-remote.json' }, baz: [123] } }, constResolver ); expect(result).toEqual({ - foo: { - bar: { some: { remote: 'json' } }, - baz: [123] - } + foo: { bar: { some: { remote: 'json' } }, baz: [123] } }); }); it('should substitute JSON addressed by URI in place of "$ref" declared inside list', async () => { - const constResolver = () => ({ some: { remote: 'json' } }); + const constResolver = async () => ({ some: { remote: 'json' } }); const result = await resolveRef( { - foo: { - bar: 0, - baz: [123, { $ref: 'http://some-remote.json' }, 456] - } + foo: { bar: 0, baz: [123, { $ref: 'http://some-remote.json' }, 456] } }, constResolver ); expect(result).toEqual({ - foo: { - bar: 0, - baz: [123, { some: { remote: 'json' } }, 456] - } + foo: { bar: 0, baz: [123, { some: { remote: 'json' } }, 456] } }); }); it('should substitute JSON addressed by URI in place of "$ref" with regard to URI fragment', async () => { - const constResolver = () => ({ some: { remote: 'json' } }); + const constResolver = async () => ({ some: { remote: 'json' } }); const result = await resolveRef( { - foo: { - bar: { $ref: 'http://some-remote.json#/some/remote' }, - baz: [123] - } + foo: { bar: { $ref: 'http://some-remote.json#/some/remote' }, baz: [123] } }, constResolver ); - expect(result).toEqual({ - foo: { - bar: 'json', - baz: [123] - } - }); + expect(result).toEqual({ foo: { bar: 'json', baz: [123] } }); }); it('should "ask" URIResolver to resolve URI for each "$ref"', async () => { @@ -84,24 +64,18 @@ describe('resolveRef', () => { }); it('should recursively process "$ref" fields of fragments returned by URIresolver', async () => { - const constResolver = (uri: string) => + const constResolver = async (uri: string) => uri === '#some/remote/json' ? { some: { remote: { json: { $ref: '#/foo' } } } } : 'foo'; const result = await resolveRef( { - foo: { - bar: { $ref: '#some/remote/json' }, - baz: [123] - } + foo: { bar: { $ref: '#some/remote/json' }, baz: [123] } }, constResolver ); expect(result).toEqual({ - foo: { - bar: 'foo', - baz: [123] - } + foo: { bar: 'foo', baz: [123] } }); }); }); diff --git a/src/resolveRef.sync.ts b/src/resolveRef.sync.ts new file mode 100644 index 0000000..5a22d20 --- /dev/null +++ b/src/resolveRef.sync.ts @@ -0,0 +1,15 @@ +import { type JSONNode, type JSONRecord } from './JSONNode'; +import type { URIResolver } from './URIResolver'; +import { mapJSONNode } from './mapJSONNode'; +import { mergeRecords } from './mergeRecords'; +import { resolveJPointer } from './resolveJPointer'; + +export const resolveRef = (json: JSONNode, resolver: URIResolver): JSONNode => + mapJSONNode(json, { + ref: ({ $ref, ...rest }) => resolveRef($ref ? resolveJPointer(resolver($ref), $ref) : rest, resolver), + record: record => + mergeRecords( + Object.entries(record).map(([key, value]) => ({ [key]: resolveRef(value, resolver) } as JSONRecord)) + ), + array: list => list.map(_ => resolveRef(_, resolver)) + }); diff --git a/src/resolveRef.ts b/src/resolveRef.ts index d502a38..79988ca 100644 --- a/src/resolveRef.ts +++ b/src/resolveRef.ts @@ -1,30 +1,19 @@ -import { type JSONNode, type JSONRecord, type JSONRef, isJSONArray, isJSONRecord, isJSONRef } from './JSONNode'; +import { type JSONNode, type JSONRecord } from './JSONNode'; import type { URIResolver } from './URIResolver'; +import { mapJSONNode } from './mapJSONNode'; +import { mergeRecords } from './mergeRecords'; import { resolveJPointer } from './resolveJPointer'; -export const resolveRef = async (json: JSONNode, resolver: URIResolver): Promise => { - if (isJSONRef(json)) { - return mapJSONRef(json, resolver); - } else if (isJSONRecord(json)) { - return mapJSONRecord(json, resolver); - } else if (isJSONArray(json)) { - return mapJSONArray(json, resolver); - } else { - return json; - } -}; - -const mapJSONArray = async (list: JSONNode[], resolver: URIResolver): Promise => - Promise.all(list.map(_ => resolveRef(_, resolver))); - -const mapJSONRecord = async (record: JSONRecord, resolver: URIResolver): Promise => - ( - await Promise.all( - Object.entries(record).map(async ([key, value]) => ({ - [key]: await resolveRef(value, resolver) - })) - ) - ).reduce((acc, _) => Object.assign(acc, _), {}); - -const mapJSONRef = async ({ $ref, ...rest }: JSONRef, resolver: URIResolver): Promise => - resolveRef($ref ? resolveJPointer(await resolver($ref), $ref) : rest, resolver); +export const resolveRef = async (json: JSONNode, resolver: URIResolver): Promise => + mapJSONNode(json, { + ref: async ({ $ref, ...rest }) => resolveRef($ref ? resolveJPointer(await resolver($ref), $ref) : rest, resolver), + record: async record => + mergeRecords( + await Promise.all( + Object.entries(record).map( + async ([key, value]) => ({ [key]: await resolveRef(value, resolver) } as JSONRecord) + ) + ) + ), + array: async list => Promise.all(list.map(_ => resolveRef(_, resolver))) + }); diff --git a/src/visitRef.ts b/src/visitRef.ts index 997d4c8..73a04f7 100644 --- a/src/visitRef.ts +++ b/src/visitRef.ts @@ -1,22 +1,17 @@ -import { type JSONNode, isJSONArray, isJSONRecord, isJSONRef, type JSONRecord } from './JSONNode'; +import { type JSONNode } from './JSONNode'; +import { mapJSONNode } from './mapJSONNode'; type JSONNodePath = (string | number)[]; export type RefVisitor = (ref: string, path: JSONNodePath) => void; export const visitRef = (json: JSONNode, visitor: RefVisitor, path: JSONNodePath = []): void => { - if (isJSONRef(json)) { - visitor(json.$ref, path); - } else if (isJSONRecord(json)) { - visitJSONRecord(json, visitor, path); - } else if (isJSONArray(json)) { - visitJSONArray(json, visitor, path); - } -}; - -const visitJSONRecord = (record: JSONRecord, visitor: RefVisitor, path: JSONNodePath): void => { - for (const key in record) visitRef(record[key], visitor, [...path, key]); -}; - -const visitJSONArray = (list: JSONNode[], visitor: RefVisitor, path: JSONNodePath): void => { - for (let i = 0; i < list.length; i++) visitRef(list[i], visitor, [...path, i]); + mapJSONNode(json, { + ref: json => visitor(json.$ref, path), + record: record => { + for (const key in record) visitRef(record[key], visitor, [...path, key]); + }, + array: list => { + for (let i = 0; i < list.length; i++) visitRef(list[i], visitor, [...path, i]); + } + }); };