Skip to content

Commit 8300b12

Browse files
authored
feat: support booleanish schemas & represent additional{Items,Properties} (#31)
BREAKING CHANGE: additionalProperties/additionalItems keywords are now processed as schemas BREAKING CHANGE: true/false schemas are now represented
1 parent 44abda7 commit 8300b12

28 files changed

+317
-59
lines changed

jest.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module.exports = {
22
rootDir: process.cwd(),
33
testEnvironment: 'node',
4+
roots: ['<rootDir>/src'],
45
setupFilesAfterEnv: ['./setupTests.ts'],
56
testMatch: ['<rootDir>/src/**/__tests__/*.(ts|js)?(x)'],
67
transform: {
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "array",
3+
"additionalItems": {}
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "array",
3+
"additionalItems": false
4+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"type": "array",
3+
"additionalItems": {
4+
"type": "object",
5+
"properties": {
6+
"baz": {
7+
"type": "number"
8+
}
9+
}
10+
}
11+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "array",
3+
"additionalItems": true
4+
}

src/__tests__/__fixtures__/arrays/with-multiple-arrayish-items.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,7 @@
1717
"type": "string"
1818
}
1919
},
20-
"required": [
21-
"code",
22-
"msg"
23-
]
20+
"required": ["code", "msg"]
2421
}
2522
]
2623
}

src/__tests__/__fixtures__/arrays/with-single-arrayish-items.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,7 @@
1414
"type": "string"
1515
}
1616
},
17-
"required": [
18-
"code",
19-
"msg"
20-
]
17+
"required": ["code", "msg"]
2118
}
2219
]
2320
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "object",
3+
"additionalProperties": {}
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "object",
3+
"additionalProperties": false
4+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"type": "object",
3+
"additionalProperties": {
4+
"type": "object",
5+
"properties": {
6+
"baz": {
7+
"type": "number"
8+
}
9+
}
10+
}
11+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "object",
3+
"additionalProperties": true
4+
}

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

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,61 @@ exports[`SchemaTree output compound keywords given oneOf combiner placed next to
296296
"
297297
`;
298298

299+
exports[`SchemaTree output should generate valid tree for arrays/additional-empty.json 1`] = `
300+
"└─ #
301+
├─ types
302+
│ └─ 0: array
303+
├─ primaryType: array
304+
└─ children
305+
└─ 0
306+
└─ #/additionalItems
307+
"
308+
`;
309+
310+
exports[`SchemaTree output should generate valid tree for arrays/additional-false.json 1`] = `
311+
"└─ #
312+
├─ types
313+
│ └─ 0: array
314+
├─ primaryType: array
315+
└─ children
316+
└─ 0
317+
└─ #/additionalItems
318+
└─ value: false
319+
"
320+
`;
321+
322+
exports[`SchemaTree output should generate valid tree for arrays/additional-schema.json 1`] = `
323+
"└─ #
324+
├─ types
325+
│ └─ 0: array
326+
├─ primaryType: array
327+
└─ children
328+
└─ 0
329+
└─ #/additionalItems
330+
├─ types
331+
│ └─ 0: object
332+
├─ primaryType: object
333+
└─ children
334+
└─ 0
335+
└─ #/additionalItems/properties/baz
336+
├─ types
337+
│ └─ 0: number
338+
└─ primaryType: number
339+
"
340+
`;
341+
342+
exports[`SchemaTree output should generate valid tree for arrays/additional-true.json 1`] = `
343+
"└─ #
344+
├─ types
345+
│ └─ 0: array
346+
├─ primaryType: array
347+
└─ children
348+
└─ 0
349+
└─ #/additionalItems
350+
└─ value: true
351+
"
352+
`;
353+
299354
exports[`SchemaTree output should generate valid tree for arrays/of-allofs.json 1`] = `
300355
"└─ #
301356
├─ types
@@ -823,7 +878,16 @@ exports[`SchemaTree output should generate valid tree for combiners/allOfs/neste
823878
│ └─ #/properties/order
824879
│ ├─ types
825880
│ │ └─ 0: object
826-
│ └─ primaryType: object
881+
│ ├─ primaryType: object
882+
│ └─ children
883+
│ └─ 0
884+
│ └─ #/properties/order/additionalProperties
885+
│ ├─ types
886+
│ │ └─ 0: string
887+
│ ├─ primaryType: string
888+
│ └─ enum
889+
│ ├─ 0: ASC
890+
│ └─ 1: DESC
827891
└─ 7
828892
└─ #/properties/nextToken
829893
├─ types
@@ -1246,6 +1310,61 @@ exports[`SchemaTree output should generate valid tree for formats-schema.json 1`
12461310
"
12471311
`;
12481312

1313+
exports[`SchemaTree output should generate valid tree for objects/additional-empty.json 1`] = `
1314+
"└─ #
1315+
├─ types
1316+
│ └─ 0: object
1317+
├─ primaryType: object
1318+
└─ children
1319+
└─ 0
1320+
└─ #/additionalProperties
1321+
"
1322+
`;
1323+
1324+
exports[`SchemaTree output should generate valid tree for objects/additional-false.json 1`] = `
1325+
"└─ #
1326+
├─ types
1327+
│ └─ 0: object
1328+
├─ primaryType: object
1329+
└─ children
1330+
└─ 0
1331+
└─ #/additionalProperties
1332+
└─ value: false
1333+
"
1334+
`;
1335+
1336+
exports[`SchemaTree output should generate valid tree for objects/additional-schema.json 1`] = `
1337+
"└─ #
1338+
├─ types
1339+
│ └─ 0: object
1340+
├─ primaryType: object
1341+
└─ children
1342+
└─ 0
1343+
└─ #/additionalProperties
1344+
├─ types
1345+
│ └─ 0: object
1346+
├─ primaryType: object
1347+
└─ children
1348+
└─ 0
1349+
└─ #/additionalProperties/properties/baz
1350+
├─ types
1351+
│ └─ 0: number
1352+
└─ primaryType: number
1353+
"
1354+
`;
1355+
1356+
exports[`SchemaTree output should generate valid tree for objects/additional-true.json 1`] = `
1357+
"└─ #
1358+
├─ types
1359+
│ └─ 0: object
1360+
├─ primaryType: object
1361+
└─ children
1362+
└─ 0
1363+
└─ #/additionalProperties
1364+
└─ value: true
1365+
"
1366+
`;
1367+
12491368
exports[`SchemaTree output should generate valid tree for references/base.json 1`] = `
12501369
"└─ #
12511370
├─ types

src/__tests__/tree.spec.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -907,6 +907,34 @@ describe('SchemaTree', () => {
907907
tree.root.children[0].children[1].annotations.description,
908908
).toEqual('_Everyone_ ~hates~ loves caves');
909909
});
910+
911+
it('should render true/false schemas', () => {
912+
const schema = {
913+
type: 'object',
914+
properties: {
915+
bear: true,
916+
cave: false,
917+
},
918+
};
919+
920+
const tree = new SchemaTree(schema);
921+
tree.populate();
922+
923+
expect(printTree(schema)).toMatchInlineSnapshot(`
924+
"└─ #
925+
├─ types
926+
│ └─ 0: object
927+
├─ primaryType: object
928+
└─ children
929+
├─ 0
930+
│ └─ #/properties/bear
931+
│ └─ value: true
932+
└─ 1
933+
└─ #/properties/cave
934+
└─ value: false
935+
"
936+
`);
937+
});
910938
});
911939

912940
describe('position', () => {

src/__tests__/utils/printTree.ts

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import { pathToPointer } from '@stoplight/json';
22
import type { Dictionary } from '@stoplight/types';
33
import * as treeify from 'treeify';
44

5-
import { isMirroredNode, isReferenceNode, isRegularNode } from '../../guards';
5+
import { isBooleanishNode, isMirroredNode, isReferenceNode, isRegularNode, isRootNode } from '../../guards';
66
import type { MirroredSchemaNode, ReferenceNode, RegularNode, SchemaNode } from '../../nodes';
7+
import type { BooleanishNode } from '../../nodes/BooleanishNode';
78
import type { SchemaTreeOptions } from '../../tree';
89
import { SchemaTree } from '../../tree';
910
import type { SchemaFragment } from '../../types';
@@ -41,22 +42,30 @@ function printReferenceNode(node: ReferenceNode) {
4142
};
4243
}
4344

45+
function printBooleanishNode(node: BooleanishNode) {
46+
return {
47+
value: node.fragment,
48+
};
49+
}
50+
4451
function printMirrorNode(node: MirroredSchemaNode): any {
4552
return {
4653
mirrors: pathToPointer(node.mirroredNode.path as string[]),
4754
};
4855
}
4956

5057
function printNode(node: SchemaNode) {
51-
return isMirroredNode(node)
52-
? printMirrorNode(node)
53-
: isRegularNode(node)
54-
? printRegularNode(node)
55-
: isReferenceNode(node)
56-
? printReferenceNode(node)
57-
: {
58-
kind: 'unknown node',
59-
};
58+
if (isMirroredNode(node)) {
59+
return printMirrorNode(node);
60+
} else if (isRegularNode(node)) {
61+
return printRegularNode(node);
62+
} else if (isReferenceNode(node)) {
63+
return printReferenceNode(node);
64+
} else if (isBooleanishNode(node)) {
65+
return printBooleanishNode(node);
66+
} else if (isRootNode(node)) {
67+
return {};
68+
}
6069
}
6170

6271
function prepareTree(node: SchemaNode) {

src/accessors/getValidations.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ const VALIDATION_TYPES: Partial<Dictionary<(keyof SchemaFragment)[], SchemaNodeK
1212
get integer() {
1313
return this.number;
1414
},
15-
object: ['additionalProperties', 'minProperties', 'maxProperties'],
16-
array: ['additionalItems', 'minItems', 'maxItems', 'uniqueItems'],
15+
object: ['minProperties', 'maxProperties'],
16+
array: ['minItems', 'maxItems', 'uniqueItems'],
1717
};
1818

1919
function getTypeValidations(types: SchemaNodeKind[]): (keyof SchemaFragment)[] | null {

src/guards/nodes.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
RootNode,
88
SchemaNode,
99
} from '../nodes';
10+
import type { BooleanishNode } from '../nodes/BooleanishNode';
1011

1112
export function isSchemaNode(node: unknown): node is SchemaNode {
1213
const name = Object.getPrototypeOf(node).constructor.name;
@@ -34,3 +35,7 @@ export function isMirroredNode(node: SchemaNode): node is MirroredSchemaNode {
3435
export function isReferenceNode(node: SchemaNode): node is ReferenceNode {
3536
return 'external' in node && 'value' in node;
3637
}
38+
39+
export function isBooleanishNode(node: SchemaNode): node is BooleanishNode {
40+
return typeof node.fragment === 'boolean';
41+
}

src/nodes/BaseNode.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { SchemaFragment } from '../types';
21
import type { MirroredRegularNode } from './mirrored';
32
import type { RegularNode } from './RegularNode';
43
import type { RootNode } from './RootNode';
@@ -35,7 +34,7 @@ export abstract class BaseNode {
3534
return this.pos === this.parentChildren.length - 1;
3635
}
3736

38-
protected constructor(public readonly fragment: SchemaFragment) {
37+
protected constructor() {
3938
this.id = String(SEED++);
4039
this.subpath = [];
4140
}

src/nodes/BooleanishNode.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { BaseNode } from './BaseNode';
2+
3+
export class BooleanishNode extends BaseNode {
4+
constructor(public readonly fragment: boolean) {
5+
super();
6+
}
7+
}

src/nodes/ReferenceNode.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import { BaseNode } from './BaseNode';
77
export class ReferenceNode extends BaseNode {
88
public readonly value: string | null;
99

10-
constructor(fragment: SchemaFragment, public readonly error: string | null) {
11-
super(fragment);
10+
constructor(public readonly fragment: SchemaFragment, public readonly error: string | null) {
11+
super();
1212

1313
this.value = unwrapStringOrNull(fragment.$ref);
1414
}

src/nodes/RegularNode.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { isDeprecated } from '../accessors/isDeprecated';
1010
import { unwrapArrayOrNull, unwrapStringOrNull } from '../accessors/unwrap';
1111
import type { SchemaFragment } from '../types';
1212
import { BaseNode } from './BaseNode';
13+
import type { BooleanishNode } from './BooleanishNode';
1314
import type { ReferenceNode } from './ReferenceNode';
1415
import { MirroredSchemaNode, SchemaAnnotations, SchemaCombinerName, SchemaNodeKind } from './types';
1516

@@ -25,14 +26,14 @@ export class RegularNode extends BaseNode {
2526
public readonly title: string | null;
2627
public readonly deprecated: boolean;
2728

28-
public children: (RegularNode | ReferenceNode | MirroredSchemaNode)[] | null | undefined;
29+
public children: (RegularNode | BooleanishNode | ReferenceNode | MirroredSchemaNode)[] | null | undefined;
2930

3031
public readonly annotations: Readonly<Partial<Dictionary<unknown, SchemaAnnotations>>>;
3132
public readonly validations: Readonly<Dictionary<unknown>>;
3233
public readonly originalFragment: SchemaFragment;
3334

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

3738
this.$id = unwrapStringOrNull('id' in fragment ? fragment.id : fragment.$id);
3839
this.types = getTypes(fragment);

0 commit comments

Comments
 (0)