Skip to content

Commit 70e4df6

Browse files
authored
fix: collect errors while bundling (#57)
* fix: collect errors while bundling * fix: linting * chore: add test for invalid pointer
1 parent 8b5d37a commit 70e4df6

File tree

2 files changed

+213
-4
lines changed

2 files changed

+213
-4
lines changed

src/__tests__/bundle.spec.ts

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,196 @@ describe('bundleTargetPath()', () => {
125125
});
126126
});
127127

128+
it('should throw if invalid pointer for bundle target', () => {
129+
const document = {
130+
definitions: {
131+
user: {
132+
id: 'foo',
133+
address: {
134+
$ref: '#/definitions/address',
135+
},
136+
},
137+
address: {
138+
street: 'foo',
139+
user: {
140+
$ref: '#/definitions/user',
141+
},
142+
},
143+
card: {
144+
zip: '20815',
145+
},
146+
},
147+
__target__: {
148+
entity: {
149+
$ref: '#/definitions/user',
150+
},
151+
},
152+
};
153+
154+
const clone = cloneDeep(document);
155+
156+
expect(() =>
157+
bundleTarget({
158+
document: clone,
159+
path: 'invalid_pointer',
160+
}),
161+
).toThrow('Invalid JSON Pointer syntax; URI fragment identifiers must begin with a hash.');
162+
});
163+
164+
it('should handle invalid pointers for internal $refs', () => {
165+
const document = {
166+
definitions: {
167+
user: {
168+
id: 'foo',
169+
address: {
170+
$ref: '#/definitions/address',
171+
},
172+
},
173+
address: {
174+
street: 'foo',
175+
invalidPointer: {
176+
$ref: '#./definitions/card',
177+
},
178+
},
179+
card: {
180+
zip: '20815',
181+
},
182+
},
183+
__target__: {
184+
entity: {
185+
$ref: '#/definitions/user',
186+
},
187+
},
188+
};
189+
190+
const clone = cloneDeep(document);
191+
192+
const result = bundleTarget({
193+
document: clone,
194+
path: '#/__target__',
195+
});
196+
197+
// Do not mutate document
198+
expect(clone).toEqual(document);
199+
200+
expect(result).toEqual({
201+
entity: {
202+
$ref: '#/__bundled__/definitions/user',
203+
},
204+
__bundled__: {
205+
definitions: {
206+
user: {
207+
id: 'foo',
208+
address: {
209+
$ref: '#/__bundled__/definitions/address',
210+
},
211+
},
212+
address: {
213+
street: 'foo',
214+
invalidPointer: {
215+
$ref: '#./definitions/card',
216+
},
217+
},
218+
},
219+
},
220+
__errors__: {
221+
'#./definitions/card': 'Invalid JSON Pointer syntax.',
222+
},
223+
});
224+
});
225+
226+
it('should ignore invalid pointers', () => {
227+
const document = {
228+
practice: {
229+
title: 'Account',
230+
allOf: [
231+
{
232+
$ref: '#./UuidModel',
233+
},
234+
{
235+
type: 'object',
236+
properties: {
237+
address: {
238+
$ref: '#./Address',
239+
},
240+
email: {
241+
type: 'string',
242+
format: 'email',
243+
},
244+
name: {
245+
type: 'string',
246+
},
247+
phone: {
248+
type: 'string',
249+
},
250+
website: {
251+
type: 'string',
252+
format: 'uri',
253+
},
254+
owner: {
255+
$ref: '#./Account',
256+
},
257+
},
258+
required: ['name'],
259+
},
260+
],
261+
},
262+
__target__: {
263+
$ref: '#/practice',
264+
},
265+
};
266+
267+
const clone = cloneDeep(document);
268+
269+
const result = bundleTarget({
270+
document: clone,
271+
path: '#/practice',
272+
});
273+
274+
// Do not mutate document
275+
expect(clone).toEqual(document);
276+
277+
expect(result).toEqual({
278+
title: 'Account',
279+
allOf: [
280+
{
281+
$ref: '#./UuidModel',
282+
},
283+
{
284+
type: 'object',
285+
properties: {
286+
address: {
287+
$ref: '#./Address',
288+
},
289+
email: {
290+
type: 'string',
291+
format: 'email',
292+
},
293+
name: {
294+
type: 'string',
295+
},
296+
phone: {
297+
type: 'string',
298+
},
299+
website: {
300+
type: 'string',
301+
format: 'uri',
302+
},
303+
owner: {
304+
$ref: '#./Account',
305+
},
306+
},
307+
required: ['name'],
308+
},
309+
],
310+
__errors__: {
311+
'#./UuidModel': 'Invalid JSON Pointer syntax.',
312+
'#./Address': 'Invalid JSON Pointer syntax.',
313+
'#./Account': 'Invalid JSON Pointer syntax.',
314+
},
315+
});
316+
});
317+
128318
it('should mirror original source decision re arrays or objects', () => {
129319
const document = {
130320
parameters: [

src/bundle.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { pointerToPath } from './pointerToPath';
77
import { traverse } from './traverse';
88

99
export const BUNDLE_ROOT = '__bundled__';
10+
export const ERRORS_ROOT = '__errors__';
1011

1112
export const bundleTarget = <T = unknown>({ document, path }: { document: T; path: string }, cur?: unknown) =>
1213
_bundle(cloneDeep(document), path, cur);
@@ -17,21 +18,35 @@ const _bundle = (
1718
cur?: unknown,
1819
bundledRefInventory: any = {},
1920
bundledObj: any = {},
21+
errorsObj: any = {},
2022
) => {
2123
const objectToBundle = get(document, pointerToPath(path));
2224

2325
traverse(cur ? cur : objectToBundle, ({ parent }) => {
2426
if (hasRef(parent) && isLocalRef(parent.$ref)) {
27+
if (errorsObj[parent.$ref]) return;
28+
2529
if (bundledRefInventory[parent.$ref]) {
2630
parent.$ref = bundledRefInventory[parent.$ref];
2731

2832
// no need to continue, this $ref has already been bundled
2933
return;
3034
}
3135

32-
const _path = pointerToPath(parent.$ref);
33-
const inventoryPath = [BUNDLE_ROOT, ..._path];
34-
const inventoryRef = pathToPointer(inventoryPath);
36+
let _path;
37+
let inventoryPath;
38+
let inventoryRef;
39+
40+
try {
41+
_path = pointerToPath(parent.$ref);
42+
inventoryPath = [BUNDLE_ROOT, ..._path];
43+
inventoryRef = pathToPointer(inventoryPath);
44+
} catch (error) {
45+
errorsObj[parent.$ref] = error.message;
46+
}
47+
48+
// Ignore invalid $refs and carry on
49+
if (!_path || !inventoryPath || !inventoryRef) return;
3550

3651
const bundled$Ref = get(document, _path);
3752
if (bundled$Ref) {
@@ -56,12 +71,16 @@ const _bundle = (
5671
}
5772

5873
set(bundledObj, inventoryPath, bundled$Ref);
59-
_bundle(document, path, bundled$Ref, bundledRefInventory, bundledObj);
74+
_bundle(document, path, bundled$Ref, bundledRefInventory, bundledObj, errorsObj);
6075
}
6176
}
6277
});
6378

6479
set(objectToBundle, BUNDLE_ROOT, bundledObj[BUNDLE_ROOT]);
6580

81+
if (Object.keys(errorsObj).length) {
82+
set(objectToBundle, ERRORS_ROOT, errorsObj);
83+
}
84+
6685
return objectToBundle;
6786
};

0 commit comments

Comments
 (0)