Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support booleanish schemas & represent additional{Items,Properties} #31

Merged
merged 5 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module.exports = {
rootDir: process.cwd(),
testEnvironment: 'node',
roots: ['<rootDir>/src'],
setupFilesAfterEnv: ['./setupTests.ts'],
testMatch: ['<rootDir>/src/**/__tests__/*.(ts|js)?(x)'],
transform: {
Expand Down
4 changes: 4 additions & 0 deletions src/__tests__/__fixtures__/arrays/additional-empty.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"type": "array",
"additionalItems": {}
}
4 changes: 4 additions & 0 deletions src/__tests__/__fixtures__/arrays/additional-false.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"type": "array",
"additionalItems": false
}
11 changes: 11 additions & 0 deletions src/__tests__/__fixtures__/arrays/additional-schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"type": "array",
"additionalItems": {
"type": "object",
"properties": {
"baz": {
"type": "number"
}
}
}
}
4 changes: 4 additions & 0 deletions src/__tests__/__fixtures__/arrays/additional-true.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"type": "array",
"additionalItems": true
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@
"type": "string"
}
},
"required": [
"code",
"msg"
]
"required": ["code", "msg"]
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@
"type": "string"
}
},
"required": [
"code",
"msg"
]
"required": ["code", "msg"]
}
]
}
4 changes: 4 additions & 0 deletions src/__tests__/__fixtures__/objects/additional-empty.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"type": "object",
"additionalProperties": {}
}
4 changes: 4 additions & 0 deletions src/__tests__/__fixtures__/objects/additional-false.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"type": "object",
"additionalProperties": false
}
11 changes: 11 additions & 0 deletions src/__tests__/__fixtures__/objects/additional-schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"type": "object",
"additionalProperties": {
"type": "object",
"properties": {
"baz": {
"type": "number"
}
}
}
}
4 changes: 4 additions & 0 deletions src/__tests__/__fixtures__/objects/additional-true.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"type": "object",
"additionalProperties": true
}
121 changes: 120 additions & 1 deletion src/__tests__/__snapshots__/tree.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,61 @@ exports[`SchemaTree output compound keywords given oneOf combiner placed next to
"
`;

exports[`SchemaTree output should generate valid tree for arrays/additional-empty.json 1`] = `
"└─ #
├─ types
│ └─ 0: array
├─ primaryType: array
└─ children
└─ 0
└─ #/additionalItems
"
`;

exports[`SchemaTree output should generate valid tree for arrays/additional-false.json 1`] = `
"└─ #
├─ types
│ └─ 0: array
├─ primaryType: array
└─ children
└─ 0
└─ #/additionalItems
└─ value: false
"
`;

exports[`SchemaTree output should generate valid tree for arrays/additional-schema.json 1`] = `
"└─ #
├─ types
│ └─ 0: array
├─ primaryType: array
└─ children
└─ 0
└─ #/additionalItems
├─ types
│ └─ 0: object
├─ primaryType: object
└─ children
└─ 0
└─ #/additionalItems/properties/baz
├─ types
│ └─ 0: number
└─ primaryType: number
"
`;

exports[`SchemaTree output should generate valid tree for arrays/additional-true.json 1`] = `
"└─ #
├─ types
│ └─ 0: array
├─ primaryType: array
└─ children
└─ 0
└─ #/additionalItems
└─ value: true
"
`;

exports[`SchemaTree output should generate valid tree for arrays/of-allofs.json 1`] = `
"└─ #
├─ types
Expand Down Expand Up @@ -823,7 +878,16 @@ exports[`SchemaTree output should generate valid tree for combiners/allOfs/neste
│ └─ #/properties/order
│ ├─ types
│ │ └─ 0: object
│ └─ primaryType: object
│ ├─ primaryType: object
│ └─ children
│ └─ 0
│ └─ #/properties/order/additionalProperties
│ ├─ types
│ │ └─ 0: string
│ ├─ primaryType: string
│ └─ enum
│ ├─ 0: ASC
│ └─ 1: DESC
└─ 7
└─ #/properties/nextToken
├─ types
Expand Down Expand Up @@ -1246,6 +1310,61 @@ exports[`SchemaTree output should generate valid tree for formats-schema.json 1`
"
`;

exports[`SchemaTree output should generate valid tree for objects/additional-empty.json 1`] = `
"└─ #
├─ types
│ └─ 0: object
├─ primaryType: object
└─ children
└─ 0
└─ #/additionalProperties
"
`;

exports[`SchemaTree output should generate valid tree for objects/additional-false.json 1`] = `
"└─ #
├─ types
│ └─ 0: object
├─ primaryType: object
└─ children
└─ 0
└─ #/additionalProperties
└─ value: false
"
`;

exports[`SchemaTree output should generate valid tree for objects/additional-schema.json 1`] = `
"└─ #
├─ types
│ └─ 0: object
├─ primaryType: object
└─ children
└─ 0
└─ #/additionalProperties
├─ types
│ └─ 0: object
├─ primaryType: object
└─ children
└─ 0
└─ #/additionalProperties/properties/baz
├─ types
│ └─ 0: number
└─ primaryType: number
"
`;

exports[`SchemaTree output should generate valid tree for objects/additional-true.json 1`] = `
"└─ #
├─ types
│ └─ 0: object
├─ primaryType: object
└─ children
└─ 0
└─ #/additionalProperties
└─ value: true
"
`;

exports[`SchemaTree output should generate valid tree for references/base.json 1`] = `
"└─ #
├─ types
Expand Down
28 changes: 28 additions & 0 deletions src/__tests__/tree.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -907,6 +907,34 @@ describe('SchemaTree', () => {
tree.root.children[0].children[1].annotations.description,
).toEqual('_Everyone_ ~hates~ loves caves');
});

it('should render true/false schemas', () => {
const schema = {
type: 'object',
properties: {
bear: true,
cave: false,
},
};

const tree = new SchemaTree(schema);
tree.populate();

expect(printTree(schema)).toMatchInlineSnapshot(`
"└─ #
├─ types
│ └─ 0: object
├─ primaryType: object
└─ children
├─ 0
│ └─ #/properties/bear
│ └─ value: true
└─ 1
└─ #/properties/cave
└─ value: false
"
`);
});
});

describe('position', () => {
Expand Down
29 changes: 19 additions & 10 deletions src/__tests__/utils/printTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { pathToPointer } from '@stoplight/json';
import type { Dictionary } from '@stoplight/types';
import * as treeify from 'treeify';

import { isMirroredNode, isReferenceNode, isRegularNode } from '../../guards';
import { isBooleanishNode, isMirroredNode, isReferenceNode, isRegularNode, isRootNode } from '../../guards';
import type { MirroredSchemaNode, ReferenceNode, RegularNode, SchemaNode } from '../../nodes';
import type { BooleanishNode } from '../../nodes/BooleanishNode';
import type { SchemaTreeOptions } from '../../tree';
import { SchemaTree } from '../../tree';
import type { SchemaFragment } from '../../types';
Expand Down Expand Up @@ -41,22 +42,30 @@ function printReferenceNode(node: ReferenceNode) {
};
}

function printBooleanishNode(node: BooleanishNode) {
return {
value: node.fragment,
};
}

function printMirrorNode(node: MirroredSchemaNode): any {
return {
mirrors: pathToPointer(node.mirroredNode.path as string[]),
};
}

function printNode(node: SchemaNode) {
return isMirroredNode(node)
? printMirrorNode(node)
: isRegularNode(node)
? printRegularNode(node)
: isReferenceNode(node)
? printReferenceNode(node)
: {
kind: 'unknown node',
};
if (isMirroredNode(node)) {
return printMirrorNode(node);
} else if (isRegularNode(node)) {
return printRegularNode(node);
} else if (isReferenceNode(node)) {
return printReferenceNode(node);
} else if (isBooleanishNode(node)) {
return printBooleanishNode(node);
} else if (isRootNode(node)) {
return {};
}
}

function prepareTree(node: SchemaNode) {
Expand Down
4 changes: 2 additions & 2 deletions src/accessors/getValidations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ const VALIDATION_TYPES: Partial<Dictionary<(keyof SchemaFragment)[], SchemaNodeK
get integer() {
return this.number;
},
object: ['additionalProperties', 'minProperties', 'maxProperties'],
array: ['additionalItems', 'minItems', 'maxItems', 'uniqueItems'],
object: ['minProperties', 'maxProperties'],
array: ['minItems', 'maxItems', 'uniqueItems'],
};

function getTypeValidations(types: SchemaNodeKind[]): (keyof SchemaFragment)[] | null {
Expand Down
5 changes: 5 additions & 0 deletions src/guards/nodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
RootNode,
SchemaNode,
} from '../nodes';
import type { BooleanishNode } from '../nodes/BooleanishNode';

export function isSchemaNode(node: unknown): node is SchemaNode {
const name = Object.getPrototypeOf(node).constructor.name;
Expand Down Expand Up @@ -34,3 +35,7 @@ export function isMirroredNode(node: SchemaNode): node is MirroredSchemaNode {
export function isReferenceNode(node: SchemaNode): node is ReferenceNode {
return 'external' in node && 'value' in node;
}

export function isBooleanishNode(node: SchemaNode): node is BooleanishNode {
return typeof node.fragment === 'boolean';
}
3 changes: 1 addition & 2 deletions src/nodes/BaseNode.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { SchemaFragment } from '../types';
import type { MirroredRegularNode } from './mirrored';
import type { RegularNode } from './RegularNode';
import type { RootNode } from './RootNode';
Expand Down Expand Up @@ -35,7 +34,7 @@ export abstract class BaseNode {
return this.pos === this.parentChildren.length - 1;
}

protected constructor(public readonly fragment: SchemaFragment) {
protected constructor() {
this.id = String(SEED++);
this.subpath = [];
}
Expand Down
7 changes: 7 additions & 0 deletions src/nodes/BooleanishNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { BaseNode } from './BaseNode';

export class BooleanishNode extends BaseNode {
constructor(public readonly fragment: boolean) {
super();
}
}
4 changes: 2 additions & 2 deletions src/nodes/ReferenceNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { BaseNode } from './BaseNode';
export class ReferenceNode extends BaseNode {
public readonly value: string | null;

constructor(fragment: SchemaFragment, public readonly error: string | null) {
super(fragment);
constructor(public readonly fragment: SchemaFragment, public readonly error: string | null) {
super();

this.value = unwrapStringOrNull(fragment.$ref);
}
Expand Down
5 changes: 3 additions & 2 deletions src/nodes/RegularNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { isDeprecated } from '../accessors/isDeprecated';
import { unwrapArrayOrNull, unwrapStringOrNull } from '../accessors/unwrap';
import type { SchemaFragment } from '../types';
import { BaseNode } from './BaseNode';
import type { BooleanishNode } from './BooleanishNode';
import type { ReferenceNode } from './ReferenceNode';
import { MirroredSchemaNode, SchemaAnnotations, SchemaCombinerName, SchemaNodeKind } from './types';

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

public children: (RegularNode | ReferenceNode | MirroredSchemaNode)[] | null | undefined;
public children: (RegularNode | BooleanishNode | ReferenceNode | MirroredSchemaNode)[] | null | undefined;

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

constructor(public readonly fragment: SchemaFragment, context?: { originalFragment?: SchemaFragment }) {
super(fragment);
super();

this.$id = unwrapStringOrNull('id' in fragment ? fragment.id : fragment.$id);
this.types = getTypes(fragment);
Expand Down
Loading
Loading