Skip to content

Commit

Permalink
feat: accept JSON Schema Draft 6 & Draft 7 (#138)
Browse files Browse the repository at this point in the history
  • Loading branch information
P0lip authored Apr 26, 2021
1 parent 5304cb4 commit 6d262d9
Show file tree
Hide file tree
Showing 28 changed files with 146 additions and 127 deletions.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,14 @@
"react-dom": ">=16.8"
},
"dependencies": {
"@types/json-schema": "^7.0.7",
"@stoplight/json": "^3.5.1",
"@stoplight/json-schema-merge-allof": "^0.7.2",
"@stoplight/react-error-boundary": "^1.0.0",
"@stoplight/tree-list": "^5.0.3",
"@stoplight/types": "^12.0.0",
"classnames": "^2.2.6",
"lodash": "^4.17.15",
"lodash": "^4.17.21",
"mobx-react-lite": "^1.4.1",
"pluralize": "^8.0.0"
},
Expand All @@ -60,12 +62,10 @@
"@stoplight/markdown-viewer": "^3.5.5",
"@stoplight/scripts": "^8.2.0",
"@stoplight/storybook-config": "^2.0.5",
"@stoplight/types": "11.0.0",
"@stoplight/ui-kit": "3.0.0-beta.2",
"@types/classnames": "^2.2.9",
"@types/enzyme": "3.10.3",
"@types/jest": "^24.0.18",
"@types/json-schema": "^7.0.3",
"@types/lodash": "^4.14.149",
"@types/node": "^12.7.2",
"@types/pluralize": "^0.0.29",
Expand Down
5 changes: 2 additions & 3 deletions src/components/JsonSchemaViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@ import cn from 'classnames';
import { action } from 'mobx';
import * as React from 'react';

import { JSONSchema4 } from 'json-schema';
import { SchemaTree, SchemaTreeOptions, SchemaTreePopulateHandler, SchemaTreeRefDereferenceFn } from '../tree/tree';
import { GoToRefHandler, RowRenderer, ViewMode } from '../types';
import { GoToRefHandler, JSONSchema, RowRenderer, ViewMode } from '../types';
import { isSchemaViewerEmpty } from '../utils/isSchemaViewerEmpty';
import { SchemaTree as SchemaTreeComponent } from './SchemaTree';

export interface IJsonSchemaViewer {
schema: JSONSchema4;
schema: JSONSchema;
style?: object;
emptyText?: string;
defaultExpandedDepth?: number;
Expand Down
5 changes: 2 additions & 3 deletions src/components/SchemaRow.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { IRowRendererOptions, isParentNode, Tree } from '@stoplight/tree-list';
import cn from 'classnames';
import { JSONSchema4 } from 'json-schema';
import * as React from 'react';

import { getNodeMetadata, getSchemaNodeMetadata } from '../tree/metadata';
import { GoToRefHandler, SchemaKind, SchemaTreeListNode } from '../types';
import { GoToRefHandler, JSONSchema, SchemaKind, SchemaTreeListNode } from '../types';
import { getPrimaryType } from '../utils/getPrimaryType';
import { hasRefItems, isArrayNodeWithItems, isRefNode } from '../utils/guards';
import { Caret, Description, Divider, Format, Property, Validations } from './shared';
Expand All @@ -20,7 +19,7 @@ const ICON_SIZE = 12;
const ICON_DIMENSION = 20;
const ROW_OFFSET = 7;

function getRelevantSchemaForRequireCheck(treeNode: SchemaTreeListNode): JSONSchema4 | JSONSchema4[] | null {
function getRelevantSchemaForRequireCheck(treeNode: SchemaTreeListNode): JSONSchema | JSONSchema[] | null {
const metadata = getNodeMetadata(treeNode);
if (!('schemaNode' in metadata)) return null;
if (isArrayNodeWithItems(metadata.schemaNode)) {
Expand Down
5 changes: 2 additions & 3 deletions src/components/SchemaTree.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { TreeList, TreeListEvents, TreeStore } from '@stoplight/tree-list';
import { JSONSchema4 } from 'json-schema';
import { observer } from 'mobx-react-lite';
import * as React from 'react';

import { GoToRefHandler, RowRenderer } from '../types';
import { GoToRefHandler, JSONSchema, RowRenderer } from '../types';
import { SchemaRow } from './SchemaRow';

export interface ISchemaTree {
treeStore: TreeStore;
schema: JSONSchema4;
schema: JSONSchema;
name?: string;
hideTopBar?: boolean;
expanded?: boolean;
Expand Down
6 changes: 3 additions & 3 deletions src/components/shared/Divider.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { Dictionary } from '@stoplight/types';
import * as React from 'react';
import { JSONSchema4CombinerName } from '../../types';
import { JSONSchemaCombinerName } from '../../types';

const DIVIDERS: Dictionary<string, JSONSchema4CombinerName> = {
const DIVIDERS: Dictionary<string, JSONSchemaCombinerName> = {
allOf: 'and',
anyOf: 'and/or',
oneOf: 'or',
};

export const Divider: React.FunctionComponent<{ kind: JSONSchema4CombinerName }> = ({ kind }) => (
export const Divider: React.FunctionComponent<{ kind: JSONSchemaCombinerName }> = ({ kind }) => (
<div className="flex items-center w-full absolute" style={{ top: -9, height: 1 }}>
<div className="text-darken-7 dark:text-lighten-8 uppercase text-xs pr-2 -ml-4">{DIVIDERS[kind]}</div>
<div className="flex-1 bg-darken-5 dark:bg-lighten-5" style={{ height: 1 }} />
Expand Down
11 changes: 5 additions & 6 deletions src/components/shared/Types.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import { Dictionary, Optional } from '@stoplight/types';
import cn from 'classnames';
import { JSONSchema4TypeName } from 'json-schema';
import * as React from 'react';

import { JSONSchema4CombinerName, SchemaKind } from '../../types';
import { JSONSchema, JSONSchemaCombinerName, JSONSchemaTypeName, SchemaKind } from '../../types';

/**
* TYPE
*/
export interface IType {
type: JSONSchema4TypeName | JSONSchema4CombinerName | 'binary' | '$ref';
subtype: Optional<JSONSchema4TypeName | JSONSchema4TypeName[]> | '$ref';
type: JSONSchemaTypeName | JSONSchemaCombinerName | 'binary' | '$ref';
subtype: JSONSchema['type'] | '$ref';
className?: string;
title: Optional<string>;
}
Expand Down Expand Up @@ -61,8 +60,8 @@ Type.displayName = 'JsonSchemaViewer.Type';
*/
interface ITypes {
className?: string;
type: Optional<JSONSchema4TypeName | JSONSchema4TypeName[] | JSONSchema4CombinerName | '$ref'>;
subtype: Optional<JSONSchema4TypeName | JSONSchema4TypeName[] | '$ref'>;
type: Optional<JSONSchemaTypeName | JSONSchemaTypeName[] | JSONSchemaCombinerName | '$ref'>;
subtype: Optional<JSONSchema['type'] | '$ref'>;
title: Optional<string>;
}

Expand Down
4 changes: 2 additions & 2 deletions src/components/shared/Validations.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { safeStringify } from '@stoplight/json';
import { Dictionary } from '@stoplight/types';
import { Popover } from '@stoplight/ui-kit';
import { JSONSchema4 } from 'json-schema';
import * as React from 'react';
import { JSONSchema } from '../../types';
import { ViewModeContext } from '../JsonSchemaViewer';
import { PropertyTypeColors } from './Types';

Expand Down Expand Up @@ -93,7 +93,7 @@ export const Validations: React.FunctionComponent<IValidations> = ({
);
};

export const Format: React.FunctionComponent<{ schema: JSONSchema4 }> = ({ schema }) => {
export const Format: React.FunctionComponent<{ schema: JSONSchema }> = ({ schema }) => {
return (
<div
{...(typeof schema.type === 'string' && schema.type in PropertyTypeColors
Expand Down
4 changes: 2 additions & 2 deletions src/tree/__tests__/utils/printTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { pathToPointer } from '@stoplight/json';
import * as treeify from 'treeify';

import { TreeListNode } from '@stoplight/tree-list';
import { JSONSchema4CombinerName } from '../../../types';
import { JSONSchemaCombinerName } from '../../../types';
import { hasRefItems, isArrayNodeWithItems } from '../../../utils/guards';
import { inferType } from '../../../utils/inferType';
import { getNodeMetadata } from '../../metadata';
Expand All @@ -18,7 +18,7 @@ export function printTree(tree: SchemaTree) {
type PrintableNode = {
[key in string]: {
type?: unknown;
combiner?: JSONSchema4CombinerName;
combiner?: JSONSchemaCombinerName;
enum?: unknown;
required?: unknown;
subtype?: unknown;
Expand Down
5 changes: 2 additions & 3 deletions src/tree/metadata.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { TreeListNode } from '@stoplight/tree-list';
import { JsonPath } from '@stoplight/types';
import { JSONSchema4 } from 'json-schema';
import { SchemaNode, SchemaTreeListNode } from '../types';
import { JSONSchema, SchemaNode, SchemaTreeListNode } from '../types';

export interface ITreeNodeMetaSchema {
path: JsonPath;
schemaNode: SchemaNode;
schema: JSONSchema4;
schema: JSONSchema;
}

export interface ITreeNodeMetaError {
Expand Down
17 changes: 10 additions & 7 deletions src/tree/tree.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { extractPointerFromRef, extractSourceFromRef, pointerToPath } from '@stoplight/json';
import { Tree, TreeListParentNode, TreeState } from '@stoplight/tree-list';
import { JsonPath, Optional } from '@stoplight/types';
import { JSONSchema4 } from 'json-schema';
import { get as _get, isEqual as _isEqual, isObject as _isObject } from 'lodash';
import { ResolvingError } from '../errors';
import { IArrayNode, IObjectNode, SchemaKind, SchemaNode, ViewMode } from '../types';
import { IArrayNode, IObjectNode, JSONSchema, SchemaKind, SchemaNode, ViewMode } from '../types';
import { hasRefItems, isArrayNodeWithItems, isCombinerNode, isRefNode } from '../utils/guards';
import { inferType } from '../utils/inferType';
import { getSchemaNodeMetadata } from './metadata';
Expand All @@ -20,8 +19,8 @@ export type SchemaTreeRefInfo = {
export type SchemaTreeRefDereferenceFn = (
ref: SchemaTreeRefInfo,
propertyPath: JsonPath | null,
schema: JSONSchema4,
) => Optional<JSONSchema4>;
schema: JSONSchema,
) => Optional<JSONSchema>;

export type SchemaTreePopulateHandler = (tree: SchemaTree, node: TreeListParentNode) => void;

Expand All @@ -39,7 +38,7 @@ export { TreeState as SchemaTreeState };
export class SchemaTree extends Tree {
public treeOptions: SchemaTreeOptions;

constructor(public schema: JSONSchema4, public state: TreeState, opts: SchemaTreeOptions) {
constructor(public schema: JSONSchema, public state: TreeState, opts: SchemaTreeOptions) {
super({
expanded: node =>
(!(node.id in state.expanded) && SchemaTree.getLevel(node) <= opts.expandedDepth) ||
Expand All @@ -51,7 +50,11 @@ export class SchemaTree extends Tree {

protected readonly visited = new WeakSet();

protected isViewModeRespected = (fragment: JSONSchema4) => {
protected isViewModeRespected = (fragment: JSONSchema) => {
if (!('writeOnly' in fragment) && !('readOnly' in fragment)) {
return true;
}

return !(
!!fragment.writeOnly !== !!fragment.readOnly &&
((this.treeOptions.viewMode === 'read' && fragment.writeOnly) ||
Expand Down Expand Up @@ -112,7 +115,7 @@ export class SchemaTree extends Tree {
this.treeOptions.onPopulate?.(this, this.root);
}

public populateTreeFragment(parent: TreeListParentNode, schema: JSONSchema4, path: JsonPath, stepIn: boolean) {
public populateTreeFragment(parent: TreeListParentNode, schema: JSONSchema, path: JsonPath, stepIn: boolean) {
const initialLevel = Tree.getLevel(parent);
const artificialRoot = Tree.createArtificialRoot();
populateTree(schema, artificialRoot, initialLevel, path, {
Expand Down
4 changes: 2 additions & 2 deletions src/tree/utils/__tests__/walk.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { JSONSchema4 } from 'json-schema';
import { JSONSchema4CombinerName, SchemaKind } from '../../../types';
import { JSONSchemaCombinerName, SchemaKind } from '../../../types';
import { walk } from '../walk';

describe('Schema Walker', () => {
Expand Down Expand Up @@ -100,7 +100,7 @@ describe('Schema Walker', () => {
});

describe('title', () => {
describe.each<JSONSchema4CombinerName>(['allOf', 'oneOf', 'anyOf'])('when combiner equals %s', combiner => {
describe.each<JSONSchemaCombinerName>(['allOf', 'oneOf', 'anyOf'])('when combiner equals %s', combiner => {
test.each([null, 2, void 0, false, true, 0, {}, []])('should ignore %s invalid title', title => {
const schema = {
[combiner]: [],
Expand Down
5 changes: 2 additions & 3 deletions src/tree/utils/canStepIn.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { JSONSchema4 } from 'json-schema';
import { SchemaKind } from '../../types';
import { JSONSchema, SchemaKind } from '../../types';
import { getCombiners } from '../../utils/getCombiners';
import { getPrimaryType } from '../../utils/getPrimaryType';

export const canStepIn = (fragment: JSONSchema4) => {
export const canStepIn = (fragment: JSONSchema) => {
if (getCombiners(fragment) !== void 0) {
return true;
}
Expand Down
10 changes: 5 additions & 5 deletions src/tree/utils/mergeAllOf.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { pathToPointer, safeStringify } from '@stoplight/json';
import { JsonPath } from '@stoplight/types';
import { JSONSchema4 } from 'json-schema';
import { ResolvingError } from '../../errors';
import { JSONSchema } from '../../types';
import { WalkingOptions } from './populateTree';

const resolveAllOf = require('@stoplight/json-schema-merge-allof');

const store = new WeakMap<WalkingOptions, WeakMap<JSONSchema4, string[]>>();
const store = new WeakMap<WalkingOptions, WeakMap<JSONSchema, string[]>>();

function _mergeAllOf(schema: JSONSchema4, path: JsonPath, opts: WalkingOptions) {
function _mergeAllOf(schema: JSONSchema, path: JsonPath, opts: WalkingOptions) {
return resolveAllOf(schema, {
deep: false,
resolvers: resolveAllOf.stoplightResolvers,
Expand Down Expand Up @@ -38,7 +38,7 @@ function _mergeAllOf(schema: JSONSchema4, path: JsonPath, opts: WalkingOptions)

if (Array.isArray(resolved.allOf)) {
for (const member of resolved.allOf) {
if (typeof member.$ref === 'string' && schemaRefs.includes(member.$ref)) {
if (typeof member !== 'boolean' && typeof member.$ref === 'string' && schemaRefs.includes(member.$ref)) {
throw new ResolvingError('Circular reference detected');
}
}
Expand All @@ -49,7 +49,7 @@ function _mergeAllOf(schema: JSONSchema4, path: JsonPath, opts: WalkingOptions)
});
}

export const mergeAllOf = (schema: JSONSchema4, path: JsonPath, opts: WalkingOptions) => {
export const mergeAllOf = (schema: JSONSchema, path: JsonPath, opts: WalkingOptions) => {
try {
if (!store.has(opts)) {
store.set(opts, new WeakMap());
Expand Down
13 changes: 7 additions & 6 deletions src/tree/utils/mergeOneOrAnyOf.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
import { JsonPath } from '@stoplight/types';
import { JSONSchema4 } from 'json-schema';
import { isObject } from 'lodash';
import { JSONSchema } from '../../types';
import { mergeAllOf } from './mergeAllOf';
import { WalkingOptions } from './populateTree';

export function mergeOneOrAnyOf(
schema: JSONSchema4,
schema: JSONSchema,
combiner: 'oneOf' | 'anyOf',
path: JsonPath,
options: WalkingOptions,
): JSONSchema4[] {
): JSONSchema[] {
const items = schema[combiner];

if (!Array.isArray(items)) return []; // just in case

const merged: JSONSchema4[] = [];
const merged: JSONSchema[] = [];

if (Array.isArray(schema.allOf) && Array.isArray(items)) {
for (const item of items) {
merged.push({
allOf: [...schema.allOf, item],
allOf: [...schema.allOf, item].filter<object>(isObject),
});
}

Expand All @@ -31,7 +32,7 @@ export function mergeOneOrAnyOf(
merged.push(
mergeAllOf(
{
allOf: [prunedSchema, item],
allOf: [prunedSchema, item].filter<object>(isObject),
},
path,
options,
Expand Down
Loading

0 comments on commit 6d262d9

Please sign in to comment.