From b26c453ffdb97cf90f7eb91d4d827def134f48e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ro=C5=BCek?= Date: Tue, 30 Mar 2021 13:05:51 +0200 Subject: [PATCH] fix: readOnly/writeOnly not respected for >=2 levels --- src/components/JsonSchemaViewer.tsx | 26 ++++++++++++++-- src/components/shared/Property.tsx | 30 +++--------------- src/tree/tree.ts | 47 ++++++++++++++++++++++++----- 3 files changed, 68 insertions(+), 35 deletions(-) diff --git a/src/components/JsonSchemaViewer.tsx b/src/components/JsonSchemaViewer.tsx index 20221497..9df4b5a8 100644 --- a/src/components/JsonSchemaViewer.tsx +++ b/src/components/JsonSchemaViewer.tsx @@ -33,8 +33,22 @@ export interface IJsonSchemaViewer { export const ViewModeContext = React.createContext('standalone'); ViewModeContext.displayName = 'ViewModeContext'; +export const SchemaTreeStoreContext = React.createContext>( + new TreeStore( + new SchemaTree({}, new TreeState(), { + mergeAllOf: true, + expandedDepth: -1, + resolveRef: void 0, + shouldResolveEagerly: false, + onPopulate: void 0, + }), + new TreeState(), + ), +); +SchemaTreeStoreContext.displayName = 'SchemaTreeStoreContext'; + export class JsonSchemaViewerComponent extends React.PureComponent { - protected readonly treeStore: TreeStore; + protected readonly treeStore: TreeStore; protected readonly tree: SchemaTree; protected readonly treeState: TreeState; @@ -124,7 +138,15 @@ export class JsonSchemaViewerComponent extends React.PureComponent - + + + ); diff --git a/src/components/shared/Property.tsx b/src/components/shared/Property.tsx index bd60d090..a48f83bf 100644 --- a/src/components/shared/Property.tsx +++ b/src/components/shared/Property.tsx @@ -1,13 +1,12 @@ import { isLocalRef } from '@stoplight/json'; import { Optional } from '@stoplight/types'; -import { JSONSchema4 } from 'json-schema'; -import { isObject as _isObject, size as _size } from 'lodash'; import * as React from 'react'; import { getSchemaNodeMetadata } from '../../tree/metadata'; -import { GoToRefHandler, IArrayNode, IObjectNode, SchemaKind, SchemaNode, SchemaTreeListNode } from '../../types'; +import { GoToRefHandler, IArrayNode, SchemaKind, SchemaNode, SchemaTreeListNode } from '../../types'; import { getPrimaryType } from '../../utils/getPrimaryType'; import { hasRefItems, isArrayNodeWithItems, isCombinerNode, isRefNode } from '../../utils/guards'; import { inferType } from '../../utils/inferType'; +import { SchemaTreeStoreContext } from '../JsonSchemaViewer'; import { Types } from './Types'; export interface IProperty { @@ -15,14 +14,6 @@ export interface IProperty { onGoToRef?: GoToRefHandler; } -function count(obj: Optional): number | null { - if (_isObject(obj)) { - return _size(obj); - } - - return null; -} - function shouldShowPropertyName(treeNode: SchemaTreeListNode) { if (treeNode.parent === null) return false; try { @@ -77,22 +68,9 @@ export const Property: React.FunctionComponent = ({ node: treeNode, o const type = isRefNode(node) ? '$ref' : isCombinerNode(node) ? node.combiner : node.type; const subtype = isArrayNodeWithItems(node) ? (hasRefItems(node) ? '$ref' : inferType(node.items)) : void 0; const title = getTitle(node); + const treeStore = React.useContext(SchemaTreeStoreContext); - const childrenCount = React.useMemo(() => { - if (type === SchemaKind.Object || (Array.isArray(type) && type.includes(SchemaKind.Object))) { - return count((node as IObjectNode).properties); - } - - if (subtype === SchemaKind.Object) { - return count(((node as IArrayNode).items as IObjectNode).properties); - } - - if (subtype === SchemaKind.Array) { - return count((node as IArrayNode).items as IArrayNode); - } - - return null; - }, [node]); + const childrenCount = React.useMemo(() => treeStore.tree.getChildrenCount(node), [node, treeStore]); const handleGoToRef = React.useCallback(() => { if (onGoToRef && isRefNode(node) && node.$ref !== null) { diff --git a/src/tree/tree.ts b/src/tree/tree.ts index 7aff300e..2330efeb 100644 --- a/src/tree/tree.ts +++ b/src/tree/tree.ts @@ -4,8 +4,9 @@ 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 { ViewMode } from '../types'; -import { hasRefItems, isRefNode } from '../utils/guards'; +import { IArrayNode, IObjectNode, SchemaKind, SchemaNode, ViewMode } from '../types'; +import { hasRefItems, isArrayNodeWithItems, isCombinerNode, isRefNode } from '../utils/guards'; +import { inferType } from '../utils/inferType'; import { getSchemaNodeMetadata } from './metadata'; import { canStepIn } from './utils/canStepIn'; import { createErrorTreeNode } from './utils/createErrorTreeNode'; @@ -50,16 +51,45 @@ export class SchemaTree extends Tree { protected readonly visited = new WeakSet(); + protected isViewModeRespected = (fragment: JSONSchema4) => { + return !( + !!fragment.writeOnly !== !!fragment.readOnly && + ((this.treeOptions.viewMode === 'read' && fragment.writeOnly) || + (this.treeOptions.viewMode === 'write' && fragment.readOnly)) + ); + }; + + public getChildrenCount(node: SchemaNode) { + const type = isRefNode(node) ? '$ref' : isCombinerNode(node) ? node.combiner : node.type; + const subtype = isArrayNodeWithItems(node) ? (hasRefItems(node) ? '$ref' : inferType(node.items)) : void 0; + + let children; + + if (type === SchemaKind.Object || (Array.isArray(type) && type.includes(SchemaKind.Object))) { + children = (node as IObjectNode).properties; + } + + if (subtype === SchemaKind.Object) { + children = ((node as IArrayNode).items as IObjectNode).properties; + } + + if (subtype === SchemaKind.Array) { + children = (node as IArrayNode).items as IArrayNode; + } + + if (typeof children === 'object' && children !== null) { + return Object.values(children).filter(this.isViewModeRespected).length; + } + + return null; + } + public populate() { const expanded = {}; populateTree(this.schema, this.root, 0, [], { mergeAllOf: this.treeOptions.mergeAllOf, onNode: (fragment, node, parentTreeNode, level): boolean => { - if ( - !!fragment.writeOnly !== !!fragment.readOnly && - ((this.treeOptions.viewMode === 'read' && fragment.writeOnly) || - (this.treeOptions.viewMode === 'write' && fragment.readOnly)) - ) { + if (!this.isViewModeRespected(fragment)) { return false; } if ( @@ -88,6 +118,9 @@ export class SchemaTree extends Tree { populateTree(schema, artificialRoot, initialLevel, path, { mergeAllOf: this.treeOptions.mergeAllOf, onNode: (fragment, node, parentTreeNode, level) => { + if (!this.isViewModeRespected(fragment)) { + return false; + } if (level <= this.treeOptions.expandedDepth || level <= initialLevel) return true; return stepIn && level <= initialLevel + 1 && canStepIn(getSchemaNodeMetadata(parentTreeNode).schema); },