Skip to content

Commit 09869ca

Browse files
Marcell TothP0lip
andauthored
feat: preserve original ref info (#11)
* feat: preserve original ref info * fix: update MirroredRegularNode * chore: incorporate PR feedback * feat: preserve originalFragment for combined nodes, too * feat: preserve originalFragment for combined nodes, too * feat: preserve originalFragment for mirrored nodes * style: spacing Co-authored-by: Jakub Rożek <[email protected]>
1 parent e65082b commit 09869ca

File tree

5 files changed

+46
-18
lines changed

5 files changed

+46
-18
lines changed

src/__tests__/tree.spec.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import * as fs from 'fs';
33
import type { JSONSchema4 } from 'json-schema';
44
import * as path from 'path';
55

6+
import { isRegularNode } from '../guards';
7+
import type { RegularNode } from '../nodes';
68
import { SchemaTree } from '../tree';
79
import { printTree } from './utils/printTree';
810

@@ -102,6 +104,27 @@ describe('SchemaTree', () => {
102104
`);
103105
});
104106

107+
it('preserves the original $ref info', () => {
108+
const schema: JSONSchema4 = {
109+
type: 'object',
110+
properties: {
111+
foo: {
112+
$ref: '#/properties/bar',
113+
},
114+
bar: {
115+
type: 'boolean',
116+
},
117+
},
118+
};
119+
120+
const tree = new SchemaTree(schema);
121+
tree.populate();
122+
123+
const topLevelObject = tree.root.children[0] as RegularNode;
124+
const fooObj = topLevelObject.children!.find(child => child.path[child.path.length - 1] === 'foo')!;
125+
expect(isRegularNode(fooObj) && fooObj.originalFragment.$ref).toBe('#/properties/bar');
126+
});
127+
105128
it('given an array with $reffed items, should resolve', () => {
106129
const schema: JSONSchema4 = {
107130
type: 'object',

src/mergers/mergeOneOrAnyOf.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,13 @@ export function mergeOneOrAnyOf(
2828
const prunedSchema = { ...fragment };
2929
delete prunedSchema[combiner];
3030

31-
const resolvedItem =
32-
typeof item.$ref === 'string' && walkingOptions.resolveRef !== null
33-
? walkingOptions.resolveRef(null, item.$ref)
34-
: item;
35-
3631
if (Object.keys(prunedSchema).length === 0) {
37-
merged.push(resolvedItem);
32+
merged.push(item);
3833
} else {
34+
const resolvedItem =
35+
typeof item.$ref === 'string' && walkingOptions.resolveRef !== null
36+
? walkingOptions.resolveRef(null, item.$ref)
37+
: item;
3938
const mergedSchema = {
4039
allOf: [prunedSchema, resolvedItem],
4140
};

src/nodes/RegularNode.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@ export class RegularNode extends BaseNode {
3131
public readonly meta: Readonly<Partial<Dictionary<unknown, SchemaMeta>>>;
3232
public readonly annotations: Readonly<Partial<Dictionary<unknown, SchemaAnnotations>>>;
3333
public readonly validations: Readonly<Dictionary<unknown>>;
34+
public readonly originalFragment: SchemaFragment;
3435

35-
constructor(public readonly fragment: SchemaFragment) {
36+
constructor(public readonly fragment: SchemaFragment, context?: { originalFragment?: SchemaFragment }) {
3637
super(fragment);
3738

3839
this.types = getTypes(fragment);
@@ -48,6 +49,7 @@ export class RegularNode extends BaseNode {
4849
this.meta = getMeta(fragment);
4950
this.annotations = getAnnotations(fragment);
5051
this.validations = getValidations(fragment, this.types);
52+
this.originalFragment = context?.originalFragment ?? fragment;
5153

5254
this.children = void 0;
5355
}

src/nodes/mirrored/MirroredRegularNode.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Dictionary } from '@stoplight/types';
22

33
import { isRegularNode } from '../../guards';
4+
import type { SchemaFragment } from '../../types';
45
import { isNonNullable } from '../../utils';
56
import { BaseNode } from '../BaseNode';
67
import type { ReferenceNode } from '../ReferenceNode';
@@ -22,14 +23,16 @@ export class MirroredRegularNode extends BaseNode implements RegularNode {
2223
public readonly meta!: Readonly<Partial<Dictionary<unknown, SchemaMeta>>>;
2324
public readonly annotations!: Readonly<Partial<Dictionary<unknown, SchemaAnnotations>>>;
2425
public readonly validations!: Readonly<Dictionary<unknown>>;
26+
public readonly originalFragment!: SchemaFragment;
2527

2628
public readonly simple!: boolean;
2729
public readonly unknown!: boolean;
2830

2931
private readonly cache: WeakMap<RegularNode | ReferenceNode, MirroredRegularNode | MirroredReferenceNode>;
3032

31-
constructor(public readonly mirroredNode: RegularNode) {
33+
constructor(public readonly mirroredNode: RegularNode, context?: { originalFragment?: SchemaFragment }) {
3234
super(mirroredNode.fragment);
35+
this.originalFragment = context?.originalFragment ?? mirroredNode.originalFragment;
3336

3437
this.cache = new WeakMap();
3538

src/walker/walker.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -237,11 +237,14 @@ export class Walker extends EventEmitter<WalkerEmitter> {
237237
this.schemaNode = schemaNode;
238238
}
239239

240-
protected retrieveFromFragment(fragment: SchemaFragment): MirroredSchemaNode | void {
240+
protected retrieveFromFragment(
241+
fragment: SchemaFragment,
242+
originalFragment: SchemaFragment,
243+
): MirroredSchemaNode | void {
241244
const processedSchemaNode = this.processedFragments.get(fragment);
242245
if (processedSchemaNode !== void 0) {
243246
if (isRegularNode(processedSchemaNode)) {
244-
return new MirroredRegularNode(processedSchemaNode);
247+
return new MirroredRegularNode(processedSchemaNode, { originalFragment });
245248
}
246249

247250
if (isReferenceNode(processedSchemaNode)) {
@@ -254,10 +257,10 @@ export class Walker extends EventEmitter<WalkerEmitter> {
254257
}
255258

256259
protected processFragment(): SchemaNode {
257-
const { walkingOptions, path } = this;
260+
const { walkingOptions, path, fragment: originalFragment } = this;
258261
let { fragment } = this;
259262

260-
let retrieved = isNonNullable(fragment) ? this.retrieveFromFragment(fragment) : null;
263+
let retrieved = isNonNullable(fragment) ? this.retrieveFromFragment(fragment, originalFragment) : null;
261264

262265
if (retrieved) {
263266
return retrieved;
@@ -291,25 +294,23 @@ export class Walker extends EventEmitter<WalkerEmitter> {
291294
try {
292295
const merged = mergeOneOrAnyOf(fragment, path, walkingOptions);
293296
if (merged.length === 1) {
294-
return new RegularNode(merged[0]);
297+
return new RegularNode(merged[0], { originalFragment });
295298
} else {
296299
const combiner = SchemaCombinerName.OneOf in fragment ? SchemaCombinerName.OneOf : SchemaCombinerName.AnyOf;
297-
return new RegularNode({
298-
[combiner]: merged,
299-
});
300+
return new RegularNode({ [combiner]: merged }, { originalFragment });
300301
}
301302
} catch (ex) {
302303
super.emit('error', createMagicError(new MergingError(ex?.message ?? 'Unknown merging error')));
303304
// no the end of the world - we will render raw unprocessed fragment
304305
}
305306
}
306307

307-
retrieved = isNonNullable(fragment) ? this.retrieveFromFragment(fragment) : null;
308+
retrieved = isNonNullable(fragment) ? this.retrieveFromFragment(fragment, originalFragment) : null;
308309

309310
if (retrieved) {
310311
return retrieved;
311312
}
312313

313-
return new RegularNode(fragment);
314+
return new RegularNode(fragment, { originalFragment });
314315
}
315316
}

0 commit comments

Comments
 (0)