Skip to content

Commit 670f0e5

Browse files
committed
fix: hande nullish $refs
1 parent 97d1aae commit 670f0e5

File tree

11 files changed

+58
-16
lines changed

11 files changed

+58
-16
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"$ref": null
3+
}

src/components/__tests__/Property.spec.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,27 @@ describe('Property component', () => {
5858
expect(wrapper).not.toBeEmptyRender();
5959
});
6060

61+
it('should handle nullish $ref', () => {
62+
const treeNode: SchemaTreeListNode = {
63+
id: 'foo',
64+
name: '',
65+
parent: null,
66+
};
67+
68+
const schema: JSONSchema4 = {
69+
$ref: null as any,
70+
};
71+
72+
metadataStore.set(treeNode, {
73+
schemaNode: walk(schema).next().value,
74+
path: [],
75+
schema,
76+
});
77+
78+
const wrapper = shallow(<Property node={treeNode} onGoToRef={jest.fn()} />);
79+
expect(wrapper).not.toBeEmptyRender();
80+
});
81+
6182
describe('properties counter', () => {
6283
it('given missing properties property, should not display the counter', () => {
6384
const treeNode: SchemaTreeListNode = {

src/components/shared/Property.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ function shouldShowPropertyName(treeNode: SchemaTreeListNode) {
4040
}
4141

4242
function isExternalRefSchemaNode(schemaNode: SchemaNode) {
43-
return '$ref' in schemaNode && !isLocalRef(schemaNode.$ref);
43+
return isRefNode(schemaNode) && schemaNode.$ref !== null && !isLocalRef(schemaNode.$ref);
4444
}
4545

4646
export const Property: React.FunctionComponent<IProperty> = ({ node: treeNode, onGoToRef }) => {
@@ -65,7 +65,7 @@ export const Property: React.FunctionComponent<IProperty> = ({ node: treeNode, o
6565
}, [node]);
6666

6767
const handleGoToRef = React.useCallback<React.MouseEventHandler>(() => {
68-
if (onGoToRef && isRefNode(node)) {
68+
if (onGoToRef && isRefNode(node) && node.$ref !== null) {
6969
onGoToRef(node.$ref, node);
7070
}
7171
}, [onGoToRef, node]);
@@ -75,7 +75,7 @@ export const Property: React.FunctionComponent<IProperty> = ({ node: treeNode, o
7575
{path.length > 0 && shouldShowPropertyName(treeNode) && <div className="mr-2">{path[path.length - 1]}</div>}
7676

7777
<Types type={type} subtype={subtype}>
78-
{'$ref' in node ? `[${node.$ref}]` : null}
78+
{isRefNode(node) && node.$ref !== null ? `[${node.$ref}]` : null}
7979
</Types>
8080

8181
{onGoToRef && isExternalRefSchemaNode(node) ? (

src/tree/__tests__/__snapshots__/populateTree.spec.ts.snap

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,21 @@ Object {
226226
}
227227
`;
228228

229+
exports[`populateTree util should match nullish-ref.schema.json 1`] = `
230+
Object {
231+
"children": Array [
232+
Object {
233+
"id": "random-id",
234+
"name": "",
235+
"parent": [Circular],
236+
},
237+
],
238+
"id": "random-id",
239+
"name": "",
240+
"parent": null,
241+
}
242+
`;
243+
229244
exports[`populateTree util should match ref/original.json 1`] = `
230245
Object {
231246
"children": Array [

src/tree/__tests__/populateTree.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ describe('populateTree util', () => {
2020
'array-of-allofs.json',
2121
'todo-allof.schema.json',
2222
'tickets.schema.json',
23+
'nullish-ref.schema.json',
2324
])('should match %s', filename => {
2425
const schema = JSON.parse(fs.readFileSync(path.resolve(BASE_PATH, filename), 'utf8'));
2526
const root = Tree.createArtificialRoot();

src/tree/populateTree.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export const populateTree: Walker = (schema, parent, level, path, options): unde
4343
path,
4444
});
4545

46-
if (isRefNode(node) && isLocalRef(node.$ref) && node.$ref !== '#') {
46+
if (isRefNode(node) && node.$ref !== null && isLocalRef(node.$ref) && node.$ref !== '#') {
4747
(treeNode as TreeListParentNode).children = [];
4848
} else if (!isCombinerNode(node)) {
4949
switch (getPrimaryType(node)) {

src/tree/tree.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export class SchemaTree extends Tree {
3737
populateTree(this.schema, this.root, 0, [], {
3838
mergeAllOf: this.mergeAllOf,
3939
onNode: (node, parentTreeNode, level): boolean => {
40-
if (isRefNode(node) && isLocalRef(node.$ref)) {
40+
if (isRefNode(node) && node.$ref !== null && isLocalRef(node.$ref)) {
4141
expanded[node.id] = false;
4242
}
4343

@@ -81,7 +81,9 @@ export class SchemaTree extends Tree {
8181

8282
const metadata = getNodeMetadata(node);
8383
const { path, schemaNode, schema } = metadata;
84-
if (isRefNode(schemaNode)) {
84+
if (!isRefNode(schemaNode)) {
85+
this.populateTreeFragment(node, schema, path);
86+
} else if (schemaNode.$ref !== null) {
8587
const refPath = pointerToPath(schemaNode.$ref);
8688
const schemaFragment = this.resolveRef ? this.resolveRef(refPath, this.schema) : _get(this.schema, refPath);
8789
if (!_isObject(schemaFragment)) {
@@ -91,7 +93,7 @@ export class SchemaTree extends Tree {
9193
this.populateTreeFragment(node, schemaFragment, path);
9294
metadata.schema = schemaFragment;
9395
} else {
94-
this.populateTreeFragment(node, schema, path);
96+
throw new Error(`I do know not how not expand node ${path.join('.')}`);
9597
}
9698

9799
this.visited.add(node);

src/tree/walk.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { JSONSchema4 } from 'json-schema';
2-
import { IArrayNode, IBaseNode, ICombinerNode, IObjectNode, IRefNode, SchemaKind, SchemaNode } from '../types';
2+
import { IArrayNode, IBaseNode, ICombinerNode, IObjectNode, SchemaKind, SchemaNode } from '../types';
33
import { flattenTypes } from '../utils/flattenTypes';
44
import { generateId } from '../utils/generateId';
55
import { getAnnotations } from '../utils/getAnnotations';
@@ -65,8 +65,8 @@ function processNode(node: JSONSchema4): SchemaNode | void {
6565
if ('$ref' in node) {
6666
return {
6767
id: generateId(),
68-
$ref: node.$ref,
69-
} as IRefNode;
68+
$ref: typeof node.$ref !== 'string' ? null : node.$ref,
69+
};
7070
}
7171

7272
// if ('not' in node) {

src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export interface IBaseNode extends Pick<JSONSchema4, 'enum'> {
3838

3939
export interface IRefNode {
4040
id: string;
41-
$ref: string;
41+
$ref: string | null;
4242
}
4343

4444
export interface IArrayNode extends IBaseNode, Pick<JSONSchema4, 'items' | 'additionalItems'> {}

src/utils/getPrimaryType.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { JSONSchema4 } from 'json-schema';
2-
import { SchemaKind } from '../types';
2+
import { SchemaKind, SchemaNode } from '../types';
33
import { inferType } from './inferType';
44

5-
export function getPrimaryType(node: JSONSchema4) {
6-
if (node.type !== undefined) {
5+
export function getPrimaryType(node: JSONSchema4 | SchemaNode) {
6+
if ('type' in node && node.type !== undefined) {
77
if (Array.isArray(node.type)) {
88
if (node.type.includes(SchemaKind.Object)) {
99
return SchemaKind.Object;

0 commit comments

Comments
 (0)