Skip to content

Commit

Permalink
refactor(json-schema-parser): add dereferenceJsonSchema
Browse files Browse the repository at this point in the history
  • Loading branch information
notaphplover committed Sep 8, 2023
1 parent c567b1e commit 451db1e
Show file tree
Hide file tree
Showing 6 changed files with 270 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import { afterAll, beforeAll, describe, expect, it, jest } from '@jest/globals';

jest.mock('../actions/traverseJsonSchema');
jest.mock('./getJsonSchemaBaseUri');

import {
JsonSchema,
JsonSchemaObject,
} from '@cuaklabs/json-schema-types/2020-12';

import { traverseJsonSchema } from '../actions/traverseJsonSchema';
import { JsonRootSchemaFixtures } from '../fixtures/JsonRootSchemaFixtures';
import { DereferenceFunction } from '../models/DereferenceFunction';
import { TraverseJsonSchemaCallbackParams } from '../models/TraverseJsonSchemaCallbackParams';
import { TraverseJsonSchemaParams } from '../models/TraverseJsonSchemaParams';
import { UriOptions } from '../models/UriOptions';
import { dereferenceJsonSchema } from './dereferenceJsonSchema';
import { getJsonSchemaBaseUri } from './getJsonSchemaBaseUri';

describe(dereferenceJsonSchema.name, () => {
let derefMock: jest.Mock<DereferenceFunction>;
let schemaFixture: JsonSchema;
let uriOptionsFixture: UriOptions;

beforeAll(() => {
derefMock = jest.fn();
schemaFixture = JsonRootSchemaFixtures.any;
uriOptionsFixture = {};
});

describe('when called, and traverseJsonSchema() does not call callback', () => {
let baseUriFixture: string;
let referenceMapFixture: Map<string, JsonSchema>;

let result: unknown;

beforeAll(async () => {
baseUriFixture = 'base://fixture';
referenceMapFixture = new Map();

(
getJsonSchemaBaseUri as jest.Mock<typeof getJsonSchemaBaseUri>
).mockReturnValueOnce(baseUriFixture);

(
traverseJsonSchema as jest.Mock<typeof traverseJsonSchema>
).mockReturnValueOnce(undefined);

result = await dereferenceJsonSchema(
derefMock,
schemaFixture,
referenceMapFixture,
uriOptionsFixture,
);
});

afterAll(() => {
jest.clearAllMocks();
});

it('should call getJsonSchemaBaseUri()', () => {
expect(getJsonSchemaBaseUri).toHaveBeenCalledTimes(1);
expect(getJsonSchemaBaseUri).toHaveBeenCalledWith(
schemaFixture,
uriOptionsFixture,
);
});

it('should call traverseJsonSchema()', () => {
const expectedParams: TraverseJsonSchemaParams = {
schema: schemaFixture,
};

expect(traverseJsonSchema).toHaveBeenCalledTimes(1);
expect(traverseJsonSchema).toHaveBeenCalledWith(
expectedParams,
expect.any(Function),
);
});

it('should resolve to undefined', () => {
expect(result).toBeUndefined();
});
});

describe('when called, and traverseJsonSchema() calls callback twice with an schema with a reference', () => {
let dereferencedSchemaFixture: JsonSchema;
let subSchemaFixture: JsonSchemaObject;
let baseUriFixture: string;
let referenceMapFixture: Map<string, JsonSchema>;

let result: unknown;

beforeAll(async () => {
dereferencedSchemaFixture = JsonRootSchemaFixtures.any;
subSchemaFixture = JsonRootSchemaFixtures.withRef;
baseUriFixture = 'base://fixture';
referenceMapFixture = new Map();

(
getJsonSchemaBaseUri as jest.Mock<typeof getJsonSchemaBaseUri>
).mockReturnValueOnce(baseUriFixture);

(traverseJsonSchema as jest.Mock<typeof traverseJsonSchema>)
.mockImplementationOnce(
(
params: TraverseJsonSchemaParams,
callback: (params: TraverseJsonSchemaCallbackParams) => void,
) => {
callback({
jsonPointer: params.jsonPointer ?? '',
parentJsonPointer: params.jsonPointer,
parentSchema: schemaFixture,
schema: subSchemaFixture,
});
callback({
jsonPointer: params.jsonPointer ?? '',
parentJsonPointer: params.jsonPointer,
parentSchema: schemaFixture,
schema: subSchemaFixture,
});
},
)
.mockImplementationOnce(() => undefined);

derefMock.mockResolvedValueOnce(dereferencedSchemaFixture);

result = await dereferenceJsonSchema(
derefMock,
schemaFixture,
referenceMapFixture,
uriOptionsFixture,
);
});

afterAll(() => {
jest.clearAllMocks();
});

it('should call getJsonSchemaBaseUri()', () => {
expect(getJsonSchemaBaseUri).toHaveBeenCalledTimes(2);
expect(getJsonSchemaBaseUri).toHaveBeenNthCalledWith(
1,
schemaFixture,
uriOptionsFixture,
);
expect(getJsonSchemaBaseUri).toHaveBeenNthCalledWith(
2,
dereferencedSchemaFixture,
uriOptionsFixture,
);
});

it('should call traverseJsonSchema()', () => {
const expectedFirstParams: TraverseJsonSchemaParams = {
schema: schemaFixture,
};

const expectedSecondParams: TraverseJsonSchemaParams = {
schema: dereferencedSchemaFixture,
};

expect(traverseJsonSchema).toHaveBeenCalledTimes(2);
expect(traverseJsonSchema).toHaveBeenNthCalledWith(
1,
expectedFirstParams,
expect.any(Function),
);
expect(traverseJsonSchema).toHaveBeenNthCalledWith(
2,
expectedSecondParams,
expect.any(Function),
);
});

it('should push references', () => {
expect([...referenceMapFixture.entries()]).toStrictEqual([
[subSchemaFixture.$ref, dereferencedSchemaFixture],
]);
});

it('should resolve to undefined', () => {
expect(result).toBeUndefined();
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { JsonSchema } from '@cuaklabs/json-schema-types/2020-12';
import { Uri } from '@cuaklabs/uri';

import { traverseJsonSchema } from '../actions/traverseJsonSchema';
import { DereferenceFunction } from '../models/DereferenceFunction';
import { TraverseJsonSchemaCallbackParams } from '../models/TraverseJsonSchemaCallbackParams';
import { UriOptions } from '../models/UriOptions';
import { getJsonSchemaBaseUri } from './getJsonSchemaBaseUri';

export async function dereferenceJsonSchema(
deref: DereferenceFunction,
schema: JsonSchema,
referenceMap: Map<string, JsonSchema>,
uriOptions: UriOptions | undefined,
): Promise<void> {
const baseUri: string = getJsonSchemaBaseUri(schema, uriOptions);

const schemaUris: string[] = [...new Set(getSchemaUris(schema, baseUri))];

const missingSchemaUris: string[] = schemaUris.filter(
(schemaUri: string) => !referenceMap.has(schemaUri),
);

await Promise.all(
missingSchemaUris.map(async (schemaUri: string): Promise<void> => {
const dereferencedSchema: JsonSchema = await deref(
schema,
baseUri,
schemaUri,
);

referenceMap.set(schemaUri, dereferencedSchema);

await dereferenceJsonSchema(
deref,
dereferencedSchema,
referenceMap,
uriOptions,
);
}),
);
}

function getSchemaUris(schema: JsonSchema, baseUri: string): string[] {
const schemaUris: string[] = [];

traverseJsonSchema(
{
schema,
},
(params: TraverseJsonSchemaCallbackParams): void => {
if (
typeof params.schema !== 'boolean' &&
params.schema.$ref !== undefined
) {
const refUri: string = new Uri(params.schema.$ref, baseUri).toString();

schemaUris.push(refUri);
}
},
);

return schemaUris;
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import { JsonSchema } from '@cuaklabs/json-schema-types/2020-12';
import { getBaseUri } from '@cuaklabs/uri';

export interface GetJsonSchemaBaseUriOptions {
encapsulatingDocumentBaseUri?: string | undefined;
retrievalUri?: string | undefined;
}
import { UriOptions } from '../models/UriOptions';

export function getJsonSchemaBaseUri(
schema: JsonSchema,
options?: GetJsonSchemaBaseUriOptions,
options?: UriOptions,
): string {
const documentBaseUri: string | undefined =
typeof schema === 'boolean' ? undefined : schema.$id;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ export class JsonRootSchemaFixtures {
};
}

public static get withRef(): JsonRootSchemaObject {
return {
...JsonRootSchemaFixtures.any,
$ref: 'https://schema.id',
};
}

public static get withNoId(): JsonRootSchemaObject {
const fixture: JsonRootSchemaObject = JsonRootSchemaFixtures.any;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { JsonSchema } from '@cuaklabs/json-schema-types/2020-12';

export type DereferenceFunction = (
schema: JsonSchema,
baseUri: string,
uri: string,
) => Promise<JsonSchema>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface UriOptions {
encapsulatingDocumentBaseUri?: string | undefined;
retrievalUri?: string | undefined;
}

0 comments on commit 451db1e

Please sign in to comment.