feat(json-schema-2020-12): add support for validation keywords for numbers (#8624)

Includes following keywords:
  - multipleOf
  - minimum
  - maximum
  - inclusiveMinimum
  - inclusiveMaximum

Refs #8513
This commit is contained in:
Vladimír Gorej
2023-05-08 11:00:01 +02:00
committed by GitHub
parent 3b940d0d76
commit 1a29662977
10 changed files with 108 additions and 18 deletions

View File

@@ -31,6 +31,7 @@ const JSONSchema = forwardRef(({ schema, name, onExpand }, ref) => {
const isExpandable = fn.isExpandable(schema) const isExpandable = fn.isExpandable(schema)
const isCircular = useIsCircular(schema) const isCircular = useIsCircular(schema)
const renderedSchemas = useRenderedSchemas(schema) const renderedSchemas = useRenderedSchemas(schema)
const constraints = fn.stringifyConstraints(schema)
const Accordion = useComponent("Accordion") const Accordion = useComponent("Accordion")
const Keyword$schema = useComponent("Keyword$schema") const Keyword$schema = useComponent("Keyword$schema")
const Keyword$vocabulary = useComponent("Keyword$vocabulary") const Keyword$vocabulary = useComponent("Keyword$vocabulary")
@@ -65,6 +66,7 @@ const JSONSchema = forwardRef(({ schema, name, onExpand }, ref) => {
const KeywordType = useComponent("KeywordType") const KeywordType = useComponent("KeywordType")
const KeywordEnum = useComponent("KeywordEnum") const KeywordEnum = useComponent("KeywordEnum")
const KeywordConst = useComponent("KeywordConst") const KeywordConst = useComponent("KeywordConst")
const KeywordConstraint = useComponent("KeywordConstraint")
const KeywordFormat = useComponent("KeywordFormat") const KeywordFormat = useComponent("KeywordFormat")
const KeywordTitle = useComponent("KeywordTitle") const KeywordTitle = useComponent("KeywordTitle")
const KeywordDescription = useComponent("KeywordDescription") const KeywordDescription = useComponent("KeywordDescription")
@@ -129,6 +131,10 @@ const JSONSchema = forwardRef(({ schema, name, onExpand }, ref) => {
)} )}
<KeywordType schema={schema} isCircular={isCircular} /> <KeywordType schema={schema} isCircular={isCircular} />
<KeywordFormat schema={schema} /> <KeywordFormat schema={schema} />
{constraints.length > 0 &&
constraints.map((constraint) => (
<KeywordConstraint key={constraint} constraint={constraint} />
))}
</div> </div>
<div <div
className={classNames("json-schema-2020-12-body", { className={classNames("json-schema-2020-12-body", {

View File

@@ -25,23 +25,6 @@
display: none; display: none;
} }
} }
&__limit {
@include text_code();
margin-left: 10px;
line-height: 1.5;
padding: 1px;
color: white;
background-color: #805AD5;
border-radius: 4px;
}
//&-note {
// @include text_headline($section-models-model-title-font-color);
// padding: 10px 0 0 20px;
// font-size: 11px;
// color: #6b6b6b;
//}
} }

View File

@@ -0,0 +1,19 @@
/**
* @prettier
*/
import React from "react"
import PropTypes from "prop-types"
/**
* This component represents various constraint keywords
* from JSON Schema 2020-12 validation vocabulary.
*/
const Constraint = ({ constraint }) => (
<span className="json-schema-2020-12__constraint">{constraint}</span>
)
Constraint.propTypes = {
constraint: PropTypes.string.isRequired,
}
export default React.memo(Constraint)

View File

@@ -0,0 +1,9 @@
.json-schema-2020-12__constraint {
@include text_code();
margin-left: 10px;
line-height: 1.5;
padding: 1px 3px;
color: white;
background-color: #805AD5;
border-radius: 4px;
}

View File

@@ -2,7 +2,7 @@
@include text_code(); @include text_code();
margin-left: 10px; margin-left: 10px;
line-height: 1.5; line-height: 1.5;
padding: 1px; padding: 1px 3px;
color: white; color: white;
background-color: #D69E2E; background-color: #D69E2E;
border-radius: 4px; border-radius: 4px;

View File

@@ -67,3 +67,4 @@
@import './Properties/properties'; @import './Properties/properties';
@import './PatternProperties/pattern-properties'; @import './PatternProperties/pattern-properties';
@import './Enum/enum'; @import './Enum/enum';
@import './Constraint/constraint';

View File

@@ -179,3 +179,67 @@ export const stringify = (value) => {
return JSON.stringify(value) return JSON.stringify(value)
} }
const stringifyConstraintMultipleOf = (schema) => {
if (typeof schema?.multipleOf !== "number") return null
if (schema.multipleOf <= 0) return null
if (schema.multipleOf === 1) return null
const { multipleOf } = schema
if (Number.isInteger(multipleOf)) {
return `multiple of ${multipleOf}`
}
const decimalPlaces = multipleOf.toString().split(".")[1].length
const factor = 10 ** decimalPlaces
const numerator = multipleOf * factor
const denominator = factor
return `multiple of ${numerator}/${denominator}`
}
const stringifyConstraintNumberRange = (schema) => {
const minimum = schema?.minimum
const maximum = schema?.maximum
const exclusiveMinimum = schema?.exclusiveMinimum
const exclusiveMaximum = schema?.exclusiveMaximum
const hasMinimum = typeof minimum === "number"
const hasMaximum = typeof maximum === "number"
const hasExclusiveMinimum = typeof exclusiveMinimum === "number"
const hasExclusiveMaximum = typeof exclusiveMaximum === "number"
const isMinExclusive = hasExclusiveMinimum && minimum < exclusiveMinimum
const isMaxExclusive = hasExclusiveMaximum && maximum > exclusiveMaximum
if (hasMinimum && hasMaximum) {
const minSymbol = isMinExclusive ? "(" : "["
const maxSymbol = isMaxExclusive ? ")" : "]"
const minValue = isMinExclusive ? exclusiveMinimum : minimum
const maxValue = isMaxExclusive ? exclusiveMaximum : maximum
return `${minSymbol}${minValue}, ${maxValue}${maxSymbol}`
}
if (hasMinimum) {
const minSymbol = isMinExclusive ? ">" : "≥"
const minValue = isMinExclusive ? exclusiveMinimum : minimum
return `${minSymbol} ${minValue}`
}
if (hasMaximum) {
const maxSymbol = isMaxExclusive ? "<" : "≤"
const maxValue = isMaxExclusive ? exclusiveMaximum : maximum
return `${maxSymbol} ${maxValue}`
}
return null
}
export const stringifyConstraints = (schema) => {
const constraints = []
// Validation Keywords for Numeric Instances (number and integer)
const constraintMultipleOf = stringifyConstraintMultipleOf(schema)
if (constraintMultipleOf !== null) constraints.push(constraintMultipleOf)
const constraintNumberRange = stringifyConstraintNumberRange(schema)
if (constraintNumberRange !== null) constraints.push(constraintNumberRange)
return constraints
}

View File

@@ -33,6 +33,7 @@ import KeywordUnevaluatedProperties from "./components/keywords/UnevaluatedPrope
import KeywordType from "./components/keywords/Type/Type" import KeywordType from "./components/keywords/Type/Type"
import KeywordEnum from "./components/keywords/Enum/Enum" import KeywordEnum from "./components/keywords/Enum/Enum"
import KeywordConst from "./components/keywords/Const" import KeywordConst from "./components/keywords/Const"
import KeywordConstraint from "./components/keywords/Constraint/Constraint"
import KeywordFormat from "./components/keywords/Format/Format" import KeywordFormat from "./components/keywords/Format/Format"
import KeywordTitle from "./components/keywords/Title/Title" import KeywordTitle from "./components/keywords/Title/Title"
import KeywordDescription from "./components/keywords/Description/Description" import KeywordDescription from "./components/keywords/Description/Description"
@@ -48,6 +49,7 @@ import {
hasKeyword, hasKeyword,
isExpandable, isExpandable,
stringify, stringify,
stringifyConstraints,
} from "./fn" } from "./fn"
export const withJSONSchemaContext = (Component, overrides = {}) => { export const withJSONSchemaContext = (Component, overrides = {}) => {
@@ -83,6 +85,7 @@ export const withJSONSchemaContext = (Component, overrides = {}) => {
KeywordType, KeywordType,
KeywordEnum, KeywordEnum,
KeywordConst, KeywordConst,
KeywordConstraint,
KeywordFormat, KeywordFormat,
KeywordTitle, KeywordTitle,
KeywordDescription, KeywordDescription,
@@ -112,6 +115,7 @@ export const withJSONSchemaContext = (Component, overrides = {}) => {
hasKeyword, hasKeyword,
isExpandable, isExpandable,
stringify, stringify,
stringifyConstraints,
...overrides.fn, ...overrides.fn,
}, },
} }

View File

@@ -31,6 +31,7 @@ import KeywordUnevaluatedProperties from "./components/keywords/UnevaluatedPrope
import KeywordType from "./components/keywords/Type/Type" import KeywordType from "./components/keywords/Type/Type"
import KeywordEnum from "./components/keywords/Enum/Enum" import KeywordEnum from "./components/keywords/Enum/Enum"
import KeywordConst from "./components/keywords/Const" import KeywordConst from "./components/keywords/Const"
import KeywordConstraint from "./components/keywords/Constraint/Constraint"
import KeywordFormat from "./components/keywords/Format/Format" import KeywordFormat from "./components/keywords/Format/Format"
import KeywordTitle from "./components/keywords/Title/Title" import KeywordTitle from "./components/keywords/Title/Title"
import KeywordDescription from "./components/keywords/Description/Description" import KeywordDescription from "./components/keywords/Description/Description"
@@ -72,6 +73,7 @@ const JSONSchema202012Plugin = () => ({
JSONSchema202012KeywordType: KeywordType, JSONSchema202012KeywordType: KeywordType,
JSONSchema202012KeywordEnum: KeywordEnum, JSONSchema202012KeywordEnum: KeywordEnum,
JSONSchema202012KeywordConst: KeywordConst, JSONSchema202012KeywordConst: KeywordConst,
JSONSchema202012KeywordConstraint: KeywordConstraint,
JSONSchema202012KeywordFormat: KeywordFormat, JSONSchema202012KeywordFormat: KeywordFormat,
JSONSchema202012KeywordTitle: KeywordTitle, JSONSchema202012KeywordTitle: KeywordTitle,
JSONSchema202012KeywordDescription: KeywordDescription, JSONSchema202012KeywordDescription: KeywordDescription,

View File

@@ -58,6 +58,7 @@ const ModelsWrapper = createOnlyOAS31ComponentWrapper(({ getSystem }) => {
const KeywordType = getComponent("JSONSchema202012KeywordType") const KeywordType = getComponent("JSONSchema202012KeywordType")
const KeywordEnum = getComponent("JSONSchema202012KeywordEnum") const KeywordEnum = getComponent("JSONSchema202012KeywordEnum")
const KeywordConst = getComponent("JSONSchema202012KeywordConst") const KeywordConst = getComponent("JSONSchema202012KeywordConst")
const KeywordConstraint = getComponent("JSONSchema202012KeywordConstraint")
const KeywordFormat = getComponent("JSONSchema202012KeywordFormat") const KeywordFormat = getComponent("JSONSchema202012KeywordFormat")
const KeywordTitle = getComponent("JSONSchema202012KeywordTitle") const KeywordTitle = getComponent("JSONSchema202012KeywordTitle")
const KeywordDescription = getComponent( const KeywordDescription = getComponent(
@@ -105,6 +106,7 @@ const ModelsWrapper = createOnlyOAS31ComponentWrapper(({ getSystem }) => {
KeywordType, KeywordType,
KeywordEnum, KeywordEnum,
KeywordConst, KeywordConst,
KeywordConstraint,
KeywordFormat, KeywordFormat,
KeywordTitle, KeywordTitle,
KeywordDescription, KeywordDescription,