From 6d07c81e6b615157150c976530a4aad3e23fe1a4 Mon Sep 17 00:00:00 2001 From: "g. nicholas d'andrea" Date: Sat, 27 Jan 2024 23:50:45 -0500 Subject: [PATCH] Cleanup discriminator convention detection ... and a few other matters Primarily: - Move discriminator detection and rendering to own .tsx module - Cleanup naming and types to be more precise Also: - Start using json-schema-typed in web - Fix a broken type import (why were we not alerted?) - Enable TypeScript's strict: true in web - Fix strictness and other mistake in CreateNodesWrapper - Define type for props to reflect use of `[internalIdKey]` symbol - Pass original `props.schema` to @theme-original's , rather than the mistaken partial ...rest --- packages/web/package.json | 1 + packages/web/src/contexts/SchemaContext.tsx | 3 +- .../schemaComposition/DiscriminatorSchema.tsx | 154 ++++++++++++++++++ .../schemaComposition/allOfSchema.tsx | 146 ++--------------- .../components/CreateNodes.tsx | 13 +- packages/web/tsconfig.json | 1 + 6 files changed, 179 insertions(+), 139 deletions(-) create mode 100644 packages/web/src/theme/JSONSchemaViewer/JSONSchemaElements/schemaComposition/DiscriminatorSchema.tsx diff --git a/packages/web/package.json b/packages/web/package.json index 12ad3f94..ee023c4e 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -35,6 +35,7 @@ "@docusaurus/module-type-aliases": "^3.0.1", "@docusaurus/tsconfig": "^3.0.1", "@docusaurus/types": "^3.0.1", + "json-schema-typed": "^8.0.1", "typescript": "~5.2.2" }, "browserslist": { diff --git a/packages/web/src/contexts/SchemaContext.tsx b/packages/web/src/contexts/SchemaContext.tsx index abad8487..9e0996f7 100644 --- a/packages/web/src/contexts/SchemaContext.tsx +++ b/packages/web/src/contexts/SchemaContext.tsx @@ -1,5 +1,6 @@ import { useContext, createContext } from "react"; -import type { SchemaInfo, SchemaIndex } from "@site/src/schemas"; +import type { SchemaInfo } from "@ethdebug/format"; +import type { SchemaIndex } from "@site/src/schemas"; export interface SchemaContextValue { rootSchemaInfo?: SchemaInfo; diff --git a/packages/web/src/theme/JSONSchemaViewer/JSONSchemaElements/schemaComposition/DiscriminatorSchema.tsx b/packages/web/src/theme/JSONSchemaViewer/JSONSchemaElements/schemaComposition/DiscriminatorSchema.tsx new file mode 100644 index 00000000..cc8b1277 --- /dev/null +++ b/packages/web/src/theme/JSONSchemaViewer/JSONSchemaElements/schemaComposition/DiscriminatorSchema.tsx @@ -0,0 +1,154 @@ +import React from 'react'; +import type { JSONSchema } from "json-schema-typed/draft-2020-12" +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +import { + SchemaHierarchyContextProvider, + useSchemaHierarchyContext, +} from "@theme-original/JSONSchemaViewer/contexts" + +import { CreateNodes } from "@theme-original/JSONSchemaViewer/components" + +export interface Discriminator { + propertyName: string; + schemasByConst: { + [value: string]: { + schema: JSONSchema; + index: number; + } + } +} + +export interface DiscriminatorSchemaProps extends Discriminator { +} + +export default function DiscriminatorSchema({ + propertyName, + schemasByConst +}: DiscriminatorSchemaProps): JSX.Element { + const { jsonPointer: currentJsonPointer, level: currentLevel } = + useSchemaHierarchyContext() + + return ( +
+
+ polymorphic discriminator  + The value of the {propertyName} field + determines which sub-schema applies: + + { + Object.entries(schemasByConst) + .map(([value, { schema, index }]) => ( + + + + + + )) + } +
+ ); +} + +export function detectDiscriminator(schema: { + allOf: JSONSchema[] +}): Discriminator | undefined { + const { allOf } = schema; + + const allIfThen = allOf.every( + (clause: JSONSchema): clause is { "if": JSONSchema; then: JSONSchema } => { + if (typeof clause === "boolean") { + return false; + } + + const { title, description, "if": if_, then, ...others } = clause; + + return !!if_ && !!then && Object.keys(others).length === 0; + } + ) + + if (!allIfThen) { + return; + } + + const allIfsHaveSinglePropertyWithConst = allOf.every( + (ifThen: { "if": JSONSchema; then: JSONSchema }): ifThen is { + "if": { + properties: { + [propertyName: string]: { + "const": string + } + } + }; + then: JSONSchema; + } => { + const { "if": if_ } = ifThen; + + if ( + typeof if_ === "boolean" || + !("properties" in if_) || + !if_.properties + ) { + return false; + } + + const ifProperties = if_.properties; + + if (Object.keys(ifProperties).length !== 1) { + return false; + } + + const propertyName = Object.keys(ifProperties)[0]; + const propertySchema = ifProperties[propertyName]; + + return ( + typeof propertySchema === "object" && + "const" in propertySchema && + typeof propertySchema.const === "string" && + !!propertySchema.const + ) ; + } + ); + + if (!allIfsHaveSinglePropertyWithConst) { + return; + } + + const propertyName = Object.keys(allOf[0]["if"].properties)[0]; + + const schemasByConst = allOf + .map(({ "if": if_, then }, index) => { + const value = if_.properties[propertyName]["const"]; + + return { + [value]: { + schema: then, + index + } + }; + }) + .reduce((a, b) => ({ ...a, ...b }), {}); + + + const isUniquelyDiscriminating = + Object.keys(schemasByConst).length === allOf.length; + + if (!isUniquelyDiscriminating) { + return; + } + + return { + propertyName, + schemasByConst + }; +} diff --git a/packages/web/src/theme/JSONSchemaViewer/JSONSchemaElements/schemaComposition/allOfSchema.tsx b/packages/web/src/theme/JSONSchemaViewer/JSONSchemaElements/schemaComposition/allOfSchema.tsx index 625d81f7..fdba389f 100644 --- a/packages/web/src/theme/JSONSchemaViewer/JSONSchemaElements/schemaComposition/allOfSchema.tsx +++ b/packages/web/src/theme/JSONSchemaViewer/JSONSchemaElements/schemaComposition/allOfSchema.tsx @@ -1,147 +1,25 @@ import React from 'react'; +import type { JSONSchema } from "json-schema-typed/draft-2020-12" import AllOfSchema from '@theme-original/JSONSchemaViewer/JSONSchemaElements/schemaComposition/allOfSchema'; -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; -import { CreateNodes } from "@theme-original/JSONSchemaViewer/components" -import { - SchemaHierarchyContextProvider, - useSchemaHierarchyContext, -} from "@theme-original/JSONSchemaViewer/contexts" -import { Collapsible } from "@theme-original/JSONSchemaViewer/components"; +import DiscriminatorSchema, { + detectDiscriminator +} from "./DiscriminatorSchema"; -export default function allOfSchemaWrapper(props) { +export default function allOfSchemaWrapper(props: { + schema: Exclude & { allOf: JSONSchema[]; } +}) { const { schema } = props; - const { jsonPointer: currentJsonPointer, level: currentLevel } = - useSchemaHierarchyContext() const discriminator = detectDiscriminator(schema); - if (!discriminator) { - return ( - <> - - - ); + if (discriminator) { + return } - const { propertyName, schemasByConst } = discriminator; - return ( -
-
- polymorphic discriminator  - The value of the {propertyName} field - determines which sub-schema applies: - - { - Object.entries(schemasByConst) - .map(([value, { schema, index }]) => ( - - - - - - )) - } -
+ <> + + ); } -function detectDiscriminator(schema: { - allOf: any[] -}): { - propertyName: string; - schemasByConst: { - [value: string]: { - schema: object; - index: number; - } - } -} | undefined { - const { allOf } = schema; - - const allIfThen = allOf.every( - (clause: any): clause is { "if": any; then: any } => { - const { title, description, "if": if_, then, ...others } = clause; - - return if_ && then && Object.keys(others).length === 0; - } - ) - - if (!allIfThen) { - return; - } - - const allIfsHaveSinglePropertyWithConst = allOf.every( - (ifThen: { "if": any; then: any }): ifThen is { - "if": { - properties: { - [propertyName: string]: { - "const": string - } - } - }; - then: any; - } => { - const { "if": if_ } = ifThen; - - if (!("properties" in if_)) { - return false; - } - - const ifProperties = if_.properties; - - if (Object.keys(ifProperties).length !== 1) { - return false; - } - - const propertyName = Object.keys(ifProperties)[0]; - - const { "const": const_ } = ifProperties[propertyName]; - - return typeof const_ === "string" && !!const_; - } - ); - - if (!allIfsHaveSinglePropertyWithConst) { - return; - } - - const propertyName = Object.keys(allOf[0]["if"].properties)[0]; - - const schemasByConst = allOf - .map(({ "if": if_, then }, index) => { - const value = if_.properties[propertyName]["const"]; - - return { - [value]: { - schema: then, - index - } - }; - }) - .reduce((a, b) => ({ ...a, ...b }), {}); - - - const isUniquelyDiscriminating = - Object.keys(schemasByConst).length === allOf.length; - - if (!isUniquelyDiscriminating) { - return; - } - - return { - propertyName, - schemasByConst - }; -} - diff --git a/packages/web/src/theme/JSONSchemaViewer/components/CreateNodes.tsx b/packages/web/src/theme/JSONSchemaViewer/components/CreateNodes.tsx index 37e19777..9bb9cc16 100644 --- a/packages/web/src/theme/JSONSchemaViewer/components/CreateNodes.tsx +++ b/packages/web/src/theme/JSONSchemaViewer/components/CreateNodes.tsx @@ -1,11 +1,16 @@ import React from 'react'; import CreateTypes from "@theme-original/JSONSchemaViewer/components/CreateTypes"; import CreateNodes from '@theme-original/JSONSchemaViewer/components/CreateNodes'; -import { useJSVOptionsContext, useSchemaHierarchyContext } from "@theme-original/JSONSchemaViewer/contexts"; +import type { JSONSchema } from "json-schema-typed/draft-2020-12"; +import { useSchemaHierarchyContext } from "@theme-original/JSONSchemaViewer/contexts"; import { useSchemaContext, internalIdKey } from "@site/src/contexts/SchemaContext"; import Link from "@docusaurus/Link"; -export default function CreateNodesWrapper(props) { +export default function CreateNodesWrapper(props: { + schema: Exclude & { + [internalIdKey]: string + } +}) { const { level } = useSchemaHierarchyContext(); const { schemaIndex } = useSchemaContext(); @@ -25,7 +30,7 @@ export default function CreateNodesWrapper(props) { ? id.slice("schema:".length) : id } schema` - } = schemaIndex[id]; + } = schemaIndex[id as keyof typeof schemaIndex]; return ( <> @@ -36,7 +41,7 @@ export default function CreateNodesWrapper(props) { return ( <> - + ); } diff --git a/packages/web/tsconfig.json b/packages/web/tsconfig.json index 6f9aaca5..1710cb7d 100644 --- a/packages/web/tsconfig.json +++ b/packages/web/tsconfig.json @@ -3,6 +3,7 @@ "extends": "@docusaurus/tsconfig", "compilerOptions": { "baseUrl": ".", + "strict": true, "resolveJsonModule": true, // Extending "@tsconfig/docusaurus/tsconfig.json".types with "docusaurus-json-schema-plugin" "types": ["node", "@docusaurus/module-type-aliases", "@docusaurus/theme-classic", "docusaurus-json-schema-plugin"]