From ee868aa7eafb58ee7d973806a0f5fe45dac6705d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Gorej?= Date: Mon, 8 May 2023 15:13:23 +0200 Subject: [PATCH] feat(json-schema-2020-12): add support for dependentRequired keyword (#8633) Refs #8513 --- .../components/JSONSchema/JSONSchema.jsx | 332 +++++++++--------- .../DependentRequired/DependentRequired.jsx | 35 ++ .../_dependent-required.scss | 12 + .../keywords/Properties/Properties.jsx | 36 +- .../keywords/Properties/_properties.scss | 2 +- .../components/keywords/_all.scss | 7 + src/core/plugins/json-schema-2020-12/fn.js | 15 + src/core/plugins/json-schema-2020-12/hoc.jsx | 4 + src/core/plugins/json-schema-2020-12/index.js | 2 + .../plugins/oas31/wrap-components/models.jsx | 4 + 10 files changed, 276 insertions(+), 173 deletions(-) create mode 100644 src/core/plugins/json-schema-2020-12/components/keywords/DependentRequired/DependentRequired.jsx create mode 100644 src/core/plugins/json-schema-2020-12/components/keywords/DependentRequired/_dependent-required.scss diff --git a/src/core/plugins/json-schema-2020-12/components/JSONSchema/JSONSchema.jsx b/src/core/plugins/json-schema-2020-12/components/JSONSchema/JSONSchema.jsx index ca7bef6c..ff845387 100644 --- a/src/core/plugins/json-schema-2020-12/components/JSONSchema/JSONSchema.jsx +++ b/src/core/plugins/json-schema-2020-12/components/JSONSchema/JSONSchema.jsx @@ -21,181 +21,193 @@ import { JSONSchemaCyclesContext, } from "../../context" -const JSONSchema = forwardRef(({ schema, name, onExpand }, ref) => { - const fn = useFn() - const isExpandedDeeply = useIsExpandedDeeply() - const [expanded, setExpanded] = useState(isExpandedDeeply) - const [expandedDeeply, setExpandedDeeply] = useState(isExpandedDeeply) - const [level, nextLevel] = useLevel() - const isEmbedded = useIsEmbedded() - const isExpandable = fn.isExpandable(schema) - const isCircular = useIsCircular(schema) - const renderedSchemas = useRenderedSchemas(schema) - const constraints = fn.stringifyConstraints(schema) - const Accordion = useComponent("Accordion") - const Keyword$schema = useComponent("Keyword$schema") - const Keyword$vocabulary = useComponent("Keyword$vocabulary") - const Keyword$id = useComponent("Keyword$id") - const Keyword$anchor = useComponent("Keyword$anchor") - const Keyword$dynamicAnchor = useComponent("Keyword$dynamicAnchor") - const Keyword$ref = useComponent("Keyword$ref") - const Keyword$dynamicRef = useComponent("Keyword$dynamicRef") - const Keyword$defs = useComponent("Keyword$defs") - const Keyword$comment = useComponent("Keyword$comment") - const KeywordAllOf = useComponent("KeywordAllOf") - const KeywordAnyOf = useComponent("KeywordAnyOf") - const KeywordOneOf = useComponent("KeywordOneOf") - const KeywordNot = useComponent("KeywordNot") - const KeywordIf = useComponent("KeywordIf") - const KeywordThen = useComponent("KeywordThen") - const KeywordElse = useComponent("KeywordElse") - const KeywordDependentSchemas = useComponent("KeywordDependentSchemas") - const KeywordPrefixItems = useComponent("KeywordPrefixItems") - const KeywordItems = useComponent("KeywordItems") - const KeywordContains = useComponent("KeywordContains") - const KeywordProperties = useComponent("KeywordProperties") - const KeywordPatternProperties = useComponent("KeywordPatternProperties") - const KeywordAdditionalProperties = useComponent( - "KeywordAdditionalProperties" - ) - const KeywordPropertyNames = useComponent("KeywordPropertyNames") - const KeywordUnevaluatedItems = useComponent("KeywordUnevaluatedItems") - const KeywordUnevaluatedProperties = useComponent( - "KeywordUnevaluatedProperties" - ) - const KeywordType = useComponent("KeywordType") - const KeywordEnum = useComponent("KeywordEnum") - const KeywordConst = useComponent("KeywordConst") - const KeywordConstraint = useComponent("KeywordConstraint") - const KeywordFormat = useComponent("KeywordFormat") - const KeywordTitle = useComponent("KeywordTitle") - const KeywordDescription = useComponent("KeywordDescription") - const ExpandDeepButton = useComponent("ExpandDeepButton") +const JSONSchema = forwardRef( + ({ schema, name, dependentRequired, onExpand }, ref) => { + const fn = useFn() + const isExpandedDeeply = useIsExpandedDeeply() + const [expanded, setExpanded] = useState(isExpandedDeeply) + const [expandedDeeply, setExpandedDeeply] = useState(isExpandedDeeply) + const [level, nextLevel] = useLevel() + const isEmbedded = useIsEmbedded() + const isExpandable = fn.isExpandable(schema) || dependentRequired.length > 0 + const isCircular = useIsCircular(schema) + const renderedSchemas = useRenderedSchemas(schema) + const constraints = fn.stringifyConstraints(schema) + const Accordion = useComponent("Accordion") + const Keyword$schema = useComponent("Keyword$schema") + const Keyword$vocabulary = useComponent("Keyword$vocabulary") + const Keyword$id = useComponent("Keyword$id") + const Keyword$anchor = useComponent("Keyword$anchor") + const Keyword$dynamicAnchor = useComponent("Keyword$dynamicAnchor") + const Keyword$ref = useComponent("Keyword$ref") + const Keyword$dynamicRef = useComponent("Keyword$dynamicRef") + const Keyword$defs = useComponent("Keyword$defs") + const Keyword$comment = useComponent("Keyword$comment") + const KeywordAllOf = useComponent("KeywordAllOf") + const KeywordAnyOf = useComponent("KeywordAnyOf") + const KeywordOneOf = useComponent("KeywordOneOf") + const KeywordNot = useComponent("KeywordNot") + const KeywordIf = useComponent("KeywordIf") + const KeywordThen = useComponent("KeywordThen") + const KeywordElse = useComponent("KeywordElse") + const KeywordDependentSchemas = useComponent("KeywordDependentSchemas") + const KeywordPrefixItems = useComponent("KeywordPrefixItems") + const KeywordItems = useComponent("KeywordItems") + const KeywordContains = useComponent("KeywordContains") + const KeywordProperties = useComponent("KeywordProperties") + const KeywordPatternProperties = useComponent("KeywordPatternProperties") + const KeywordAdditionalProperties = useComponent( + "KeywordAdditionalProperties" + ) + const KeywordPropertyNames = useComponent("KeywordPropertyNames") + const KeywordUnevaluatedItems = useComponent("KeywordUnevaluatedItems") + const KeywordUnevaluatedProperties = useComponent( + "KeywordUnevaluatedProperties" + ) + const KeywordType = useComponent("KeywordType") + const KeywordEnum = useComponent("KeywordEnum") + const KeywordConst = useComponent("KeywordConst") + const KeywordConstraint = useComponent("KeywordConstraint") + const KeywordDependentRequired = useComponent("KeywordDependentRequired") + const KeywordFormat = useComponent("KeywordFormat") + const KeywordTitle = useComponent("KeywordTitle") + const KeywordDescription = useComponent("KeywordDescription") + const ExpandDeepButton = useComponent("ExpandDeepButton") - /** - * Effects handlers. - */ - useEffect(() => { - setExpandedDeeply(isExpandedDeeply) - }, [isExpandedDeeply]) + /** + * Effects handlers. + */ + useEffect(() => { + setExpandedDeeply(isExpandedDeeply) + }, [isExpandedDeeply]) - useEffect(() => { - setExpandedDeeply(expandedDeeply) - }, [expandedDeeply]) + useEffect(() => { + setExpandedDeeply(expandedDeeply) + }, [expandedDeeply]) - /** - * Event handlers. - */ - const handleExpansion = useCallback( - (e, expandedNew) => { - setExpanded(expandedNew) - !expandedNew && setExpandedDeeply(false) - onExpand(e, expandedNew, false) - }, - [onExpand] - ) - const handleExpansionDeep = useCallback( - (e, expandedDeepNew) => { - setExpanded(expandedDeepNew) - setExpandedDeeply(expandedDeepNew) - onExpand(e, expandedDeepNew, true) - }, - [onExpand] - ) + /** + * Event handlers. + */ + const handleExpansion = useCallback( + (e, expandedNew) => { + setExpanded(expandedNew) + !expandedNew && setExpandedDeeply(false) + onExpand(e, expandedNew, false) + }, + [onExpand] + ) + const handleExpansionDeep = useCallback( + (e, expandedDeepNew) => { + setExpanded(expandedDeepNew) + setExpandedDeeply(expandedDeepNew) + onExpand(e, expandedDeepNew, true) + }, + [onExpand] + ) - return ( - - - -
-
- {isExpandable && !isCircular ? ( - <> - - - - - - ) : ( - - )} - - - {constraints.length > 0 && - constraints.map((constraint) => ( - - ))} -
-
+ + +
- {expanded && ( - <> - - {!isCircular && isExpandable && ( - <> - - - - - - - - - - - - - - - - - - - )} - - - - - - - - - {!isCircular && isExpandable && ( - - )} - - - - )} -
-
-
-
-
- ) -}) +
+ {isExpandable && !isCircular ? ( + <> + + + + + + ) : ( + + )} + + + {constraints.length > 0 && + constraints.map((constraint) => ( + + ))} +
+
+ {expanded && ( + <> + + {!isCircular && isExpandable && ( + <> + + + + + + + + + + + + + + + + + + + )} + + + + + + + + + + {!isCircular && isExpandable && ( + + )} + + + + )} +
+ + + + + ) + } +) JSONSchema.propTypes = { name: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), schema: propTypes.schema.isRequired, + dependentRequired: PropTypes.arrayOf(PropTypes.string), onExpand: PropTypes.func, } JSONSchema.defaultProps = { name: "", + dependentRequired: [], onExpand: () => {}, } diff --git a/src/core/plugins/json-schema-2020-12/components/keywords/DependentRequired/DependentRequired.jsx b/src/core/plugins/json-schema-2020-12/components/keywords/DependentRequired/DependentRequired.jsx new file mode 100644 index 00000000..dcea3f2d --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/components/keywords/DependentRequired/DependentRequired.jsx @@ -0,0 +1,35 @@ +/** + * @prettier + */ +import React from "react" +import PropTypes from "prop-types" + +import * as propTypes from "../../../prop-types" + +const DependentRequired = ({ dependentRequired }) => { + if (dependentRequired.length === 0) return null + + return ( +
+ + Required when defined + + +
+ ) +} + +DependentRequired.propTypes = { + schema: propTypes.schema.isRequired, + dependentRequired: PropTypes.arrayOf(PropTypes.string).isRequired, +} + +export default DependentRequired diff --git a/src/core/plugins/json-schema-2020-12/components/keywords/DependentRequired/_dependent-required.scss b/src/core/plugins/json-schema-2020-12/components/keywords/DependentRequired/_dependent-required.scss new file mode 100644 index 00000000..3ad27aa4 --- /dev/null +++ b/src/core/plugins/json-schema-2020-12/components/keywords/DependentRequired/_dependent-required.scss @@ -0,0 +1,12 @@ +.json-schema-2020-12-keyword--dependentRequired { + & > ul { + display: inline-block; + padding: 0; + margin: 0; + + li { + display: inline; + list-style-type: none; + } + } +} diff --git a/src/core/plugins/json-schema-2020-12/components/keywords/Properties/Properties.jsx b/src/core/plugins/json-schema-2020-12/components/keywords/Properties/Properties.jsx index 457539c0..678f7a67 100644 --- a/src/core/plugins/json-schema-2020-12/components/keywords/Properties/Properties.jsx +++ b/src/core/plugins/json-schema-2020-12/components/keywords/Properties/Properties.jsx @@ -5,9 +5,10 @@ import React from "react" import classNames from "classnames" import { schema } from "../../../prop-types" -import { useComponent } from "../../../hooks" +import { useFn, useComponent } from "../../../hooks" const Properties = ({ schema }) => { + const fn = useFn() const properties = schema?.properties || {} const required = Array.isArray(schema?.required) ? schema.required : [] const JSONSchema = useComponent("JSONSchema") @@ -22,17 +23,28 @@ const Properties = ({ schema }) => { return (
) diff --git a/src/core/plugins/json-schema-2020-12/components/keywords/Properties/_properties.scss b/src/core/plugins/json-schema-2020-12/components/keywords/Properties/_properties.scss index d6eceb89..0797b94e 100644 --- a/src/core/plugins/json-schema-2020-12/components/keywords/Properties/_properties.scss +++ b/src/core/plugins/json-schema-2020-12/components/keywords/Properties/_properties.scss @@ -11,7 +11,7 @@ list-style-type: none; &--required { - .json-schema-2020-12__title:after { + & > .json-schema-2020-12:first-of-type > .json-schema-2020-12-head .json-schema-2020-12__title:after { content: '*'; color: red; font-weight: bold; diff --git a/src/core/plugins/json-schema-2020-12/components/keywords/_all.scss b/src/core/plugins/json-schema-2020-12/components/keywords/_all.scss index ed0c6b0c..18859905 100644 --- a/src/core/plugins/json-schema-2020-12/components/keywords/_all.scss +++ b/src/core/plugins/json-schema-2020-12/components/keywords/_all.scss @@ -53,6 +53,12 @@ border: 1px dashed #6b6b6b; border-radius: 4px; } + + &--warning { + @extend .json-schema-2020-12-keyword__value--const; + color: red; + border: 1px dashed red; + } } } .json-schema-2020-12-keyword__name--secondary + .json-schema-2020-12-keyword__value--secondary::before { @@ -68,3 +74,4 @@ @import './PatternProperties/pattern-properties'; @import './Enum/enum'; @import './Constraint/constraint'; +@import './DependentRequired/dependent-required'; diff --git a/src/core/plugins/json-schema-2020-12/fn.js b/src/core/plugins/json-schema-2020-12/fn.js index 1cacd3c3..1c23af37 100644 --- a/src/core/plugins/json-schema-2020-12/fn.js +++ b/src/core/plugins/json-schema-2020-12/fn.js @@ -294,3 +294,18 @@ export const stringifyConstraints = (schema) => { return constraints } + +export const getDependentRequired = (propertyName, schema) => { + if (!schema?.dependentRequired) return [] + + return Array.from( + Object.entries(schema.dependentRequired).reduce((acc, [prop, list]) => { + if (!Array.isArray(list)) return acc + if (!list.includes(propertyName)) return acc + + acc.add(prop) + + return acc + }, new Set()) + ) +} diff --git a/src/core/plugins/json-schema-2020-12/hoc.jsx b/src/core/plugins/json-schema-2020-12/hoc.jsx index 3ee0b93e..65af863a 100644 --- a/src/core/plugins/json-schema-2020-12/hoc.jsx +++ b/src/core/plugins/json-schema-2020-12/hoc.jsx @@ -34,6 +34,7 @@ import KeywordType from "./components/keywords/Type/Type" import KeywordEnum from "./components/keywords/Enum/Enum" import KeywordConst from "./components/keywords/Const" import KeywordConstraint from "./components/keywords/Constraint/Constraint" +import KeywordDependentRequired from "./components/keywords/DependentRequired/DependentRequired" import KeywordFormat from "./components/keywords/Format/Format" import KeywordTitle from "./components/keywords/Title/Title" import KeywordDescription from "./components/keywords/Description/Description" @@ -50,6 +51,7 @@ import { isExpandable, stringify, stringifyConstraints, + getDependentRequired, } from "./fn" export const withJSONSchemaContext = (Component, overrides = {}) => { @@ -86,6 +88,7 @@ export const withJSONSchemaContext = (Component, overrides = {}) => { KeywordEnum, KeywordConst, KeywordConstraint, + KeywordDependentRequired, KeywordFormat, KeywordTitle, KeywordDescription, @@ -116,6 +119,7 @@ export const withJSONSchemaContext = (Component, overrides = {}) => { isExpandable, stringify, stringifyConstraints, + getDependentRequired, ...overrides.fn, }, } diff --git a/src/core/plugins/json-schema-2020-12/index.js b/src/core/plugins/json-schema-2020-12/index.js index 1b0658a4..bcb0b0be 100644 --- a/src/core/plugins/json-schema-2020-12/index.js +++ b/src/core/plugins/json-schema-2020-12/index.js @@ -32,6 +32,7 @@ import KeywordType from "./components/keywords/Type/Type" import KeywordEnum from "./components/keywords/Enum/Enum" import KeywordConst from "./components/keywords/Const" import KeywordConstraint from "./components/keywords/Constraint/Constraint" +import KeywordDependentRequired from "./components/keywords/DependentRequired/DependentRequired" import KeywordFormat from "./components/keywords/Format/Format" import KeywordTitle from "./components/keywords/Title/Title" import KeywordDescription from "./components/keywords/Description/Description" @@ -74,6 +75,7 @@ const JSONSchema202012Plugin = () => ({ JSONSchema202012KeywordEnum: KeywordEnum, JSONSchema202012KeywordConst: KeywordConst, JSONSchema202012KeywordConstraint: KeywordConstraint, + JSONSchema202012KeywordDependentRequired: KeywordDependentRequired, JSONSchema202012KeywordFormat: KeywordFormat, JSONSchema202012KeywordTitle: KeywordTitle, JSONSchema202012KeywordDescription: KeywordDescription, diff --git a/src/core/plugins/oas31/wrap-components/models.jsx b/src/core/plugins/oas31/wrap-components/models.jsx index 8c2314e6..9c5ec2dc 100644 --- a/src/core/plugins/oas31/wrap-components/models.jsx +++ b/src/core/plugins/oas31/wrap-components/models.jsx @@ -59,6 +59,9 @@ const ModelsWrapper = createOnlyOAS31ComponentWrapper(({ getSystem }) => { const KeywordEnum = getComponent("JSONSchema202012KeywordEnum") const KeywordConst = getComponent("JSONSchema202012KeywordConst") const KeywordConstraint = getComponent("JSONSchema202012KeywordConstraint") + const KeywordDependentRequired = getComponent( + "JSONSchema202012KeywordDependentRequired" + ) const KeywordFormat = getComponent("JSONSchema202012KeywordFormat") const KeywordTitle = getComponent("JSONSchema202012KeywordTitle") const KeywordDescription = getComponent( @@ -107,6 +110,7 @@ const ModelsWrapper = createOnlyOAS31ComponentWrapper(({ getSystem }) => { KeywordEnum, KeywordConst, KeywordConstraint, + KeywordDependentRequired, KeywordFormat, KeywordTitle, KeywordDescription,