Skip to content

Commit

Permalink
feat(bundleTarget): add keyProvider option (#114)
Browse files Browse the repository at this point in the history
  • Loading branch information
marbemac authored Aug 5, 2022
1 parent a9928a8 commit 3d2ab04
Show file tree
Hide file tree
Showing 2 changed files with 175 additions and 8 deletions.
151 changes: 150 additions & 1 deletion src/__tests__/bundle.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { cloneDeep } from 'lodash';
import { cloneDeep, get as _get } from 'lodash';

import { BUNDLE_ROOT as BUNDLE_ROOT_POINTER, bundleTarget } from '../bundle';
import { safeStringify } from '../safeStringify';
Expand Down Expand Up @@ -920,6 +920,155 @@ describe('bundleTargetPath()', () => {
expect(Array.isArray(result.components.schemas)).toBe(false);
});

describe('when custom keyProvider is provided', () => {
it('should work', () => {
const document = {
definitions: {
user: {
id: 'foo',
address: {
$ref: '#/definitions/address',
},
'x-stoplight': { id: 'USER_ID' },
},
address: {
street: 'foo',
user: {
$ref: '#/definitions/user',
},
city: {
$ref: '#/definitions/city',
},
'x-stoplight': { id: 'ADDRESS_ID' },
},
city: {
name: 'foo',
},
card: {
zip: '20815',
'x-stoplight': { id: 'CARD_ID' },
},
},
__target__: {
entity: {
$ref: '#/definitions/user',
},
},
};

const clone = cloneDeep(document);

const result = bundleTarget({
document: clone,
path: '#/__target__',

/**
* This example fetches the x-stoplight.id value from the object the ref path points at, and
* returns that to use as the new $ref key in the bundled object (if id is present, otherwise falls back to default logic)
*/
keyProvider: ({ document, path }) => {
const target = _get(document, path);
const id = target?.['x-stoplight']?.['id'];
return id;
},
});

// Do not mutate document
expect(clone).toEqual(document);

expect(result).toEqual({
entity: {
$ref: `#/${BUNDLE_ROOT}/USER_ID`,
},
[BUNDLE_ROOT]: {
USER_ID: {
id: 'foo',
address: {
$ref: `#/${BUNDLE_ROOT}/ADDRESS_ID`,
},
'x-stoplight': { id: 'USER_ID' },
},
ADDRESS_ID: {
street: 'foo',
user: {
$ref: `#/${BUNDLE_ROOT}/USER_ID`,
},
city: {
$ref: `#/${BUNDLE_ROOT}/city`,
},
'x-stoplight': { id: 'ADDRESS_ID' },
},
city: {
name: 'foo',
},
},
});
});

it('should increment key if duplicate', () => {
const document = {
definitions: {
business_address: {
id: 'foo',
address: {
$ref: '#/definitions/address',
},
'x-stoplight': { id: 'ADDRESS_ID' },
},
address: {
street: 'foo',
'x-stoplight': { id: 'ADDRESS_ID' },
},
},
__target__: {
entity1: {
$ref: '#/definitions/business_address',
},
entity2: {
$ref: '#/definitions/address',
},
},
};

const clone = cloneDeep(document);

const result = bundleTarget({
document: clone,
path: '#/__target__',
keyProvider: ({ document, path }) => {
const target = _get(document, path);
const id = target?.['x-stoplight']?.['id'];
return id;
},
});

// Do not mutate document
expect(clone).toEqual(document);

expect(result).toEqual({
entity1: {
$ref: `#/${BUNDLE_ROOT}/ADDRESS_ID`,
},
entity2: {
$ref: `#/${BUNDLE_ROOT}/ADDRESS_ID_2`,
},
[BUNDLE_ROOT]: {
ADDRESS_ID: {
id: 'foo',
address: {
$ref: `#/${BUNDLE_ROOT}/ADDRESS_ID_2`,
},
'x-stoplight': { id: 'ADDRESS_ID' },
},
ADDRESS_ID_2: {
street: 'foo',
'x-stoplight': { id: 'ADDRESS_ID' },
},
},
});
});
});

describe('when custom bundleRoot is provided', () => {
it('should work', () => {
const bundleRoot = '#/__custom-root__';
Expand Down
32 changes: 25 additions & 7 deletions src/bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,23 @@ import { traverse } from './traverse';
export const BUNDLE_ROOT = '#/__bundled__';
export const ERRORS_ROOT = '#/__errors__';

type KeyProviderFn = (props: { document: unknown; path: JsonPath }) => string | void | undefined | null;

export const bundleTarget = <T = unknown>(
{
document,
path,
bundleRoot = BUNDLE_ROOT,
errorsRoot = ERRORS_ROOT,
cloneDocument = true,
keyProvider,
}: {
document: T;
path: string;
bundleRoot?: string;
errorsRoot?: string;
cloneDocument?: boolean;
keyProvider?: KeyProviderFn;
},
cur?: unknown,
) => {
Expand All @@ -32,10 +36,24 @@ export const bundleTarget = <T = unknown>(
}

const workingDocument = cloneDocument ? cloneDeep(document) : document;
return bundle(workingDocument, pointerToPath(bundleRoot), pointerToPath(errorsRoot))(path, { [path]: true }, cur);
return bundle(
workingDocument,
pointerToPath(bundleRoot),
pointerToPath(errorsRoot),
keyProvider,
)(path, { [path]: true }, cur);
};

const bundle = (document: unknown, bundleRoot: JsonPath, errorsRoot: JsonPath) => {
const defaultKeyProvider = ({ document, path }: { document: unknown; path: JsonPath }) => {
if (Array.isArray(get(document, path.slice(0, -1)))) {
const inventoryKeyRoot = path[path.length - 2];
return `${inventoryKeyRoot}_${path[path.length - 1]}`;
} else {
return String(path[path.length - 1]);
}
};

const bundle = (document: unknown, bundleRoot: JsonPath, errorsRoot: JsonPath, keyProvider?: KeyProviderFn) => {
const takenKeys = new Set<string | number>();

const _bundle = (
Expand Down Expand Up @@ -70,12 +88,12 @@ const bundle = (document: unknown, bundleRoot: JsonPath, errorsRoot: JsonPath) =
_path = pointerToPath($ref);

let _inventoryKey;
if (keyProvider) {
_inventoryKey = keyProvider({ document, path: _path });
}

if (Array.isArray(get(document, _path.slice(0, -1)))) {
const inventoryKeyRoot = _path[_path.length - 2];
_inventoryKey = `${inventoryKeyRoot}_${_path[_path.length - 1]}`;
} else {
_inventoryKey = _path[_path.length - 1];
if (!_inventoryKey) {
_inventoryKey = defaultKeyProvider({ document, path: _path });
}

inventoryKey = _inventoryKey;
Expand Down

0 comments on commit 3d2ab04

Please sign in to comment.