From 06cc3e4346228ee3e48047bb3fb447ed0eed011b Mon Sep 17 00:00:00 2001
From: Joe Bottigliero <694253+jbottigliero@users.noreply.github.com>
Date: Wed, 9 Oct 2024 19:14:16 -0500
Subject: [PATCH] feat: adds "Input Schema" validation
---
public/schemas/input_schema_schema.json | 246 ++++++++++++++++++++++++
src/components/Editor.tsx | 30 ++-
src/pages/index.tsx | 54 +++++-
src/stores/editor.ts | 9 +
4 files changed, 325 insertions(+), 14 deletions(-)
create mode 100644 public/schemas/input_schema_schema.json
diff --git a/public/schemas/input_schema_schema.json b/public/schemas/input_schema_schema.json
new file mode 100644
index 0000000..4e2623a
--- /dev/null
+++ b/public/schemas/input_schema_schema.json
@@ -0,0 +1,246 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$id": "http://json-schema.org/draft-07/schema#",
+ "title": "Core schema meta-schema",
+ "definitions": {
+ "schemaArray": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "$ref": "#"
+ }
+ },
+ "nonNegativeInteger": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "nonNegativeIntegerDefault0": {
+ "allOf": [
+ {
+ "$ref": "#/definitions/nonNegativeInteger"
+ },
+ {
+ "default": 0
+ }
+ ]
+ },
+ "simpleTypes": {
+ "enum": [
+ "array",
+ "boolean",
+ "integer",
+ "null",
+ "number",
+ "object",
+ "string"
+ ]
+ },
+ "stringArray": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "uniqueItems": true,
+ "default": []
+ }
+ },
+ "type": ["object", "boolean"],
+ "properties": {
+ "$id": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "$schema": {
+ "type": "string",
+ "format": "uri"
+ },
+ "$ref": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "$comment": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "default": true,
+ "readOnly": {
+ "type": "boolean",
+ "default": false
+ },
+ "writeOnly": {
+ "type": "boolean",
+ "default": false
+ },
+ "examples": {
+ "type": "array",
+ "items": true
+ },
+ "multipleOf": {
+ "type": "number",
+ "exclusiveMinimum": 0
+ },
+ "maximum": {
+ "type": "number"
+ },
+ "exclusiveMaximum": {
+ "type": "number"
+ },
+ "minimum": {
+ "type": "number"
+ },
+ "exclusiveMinimum": {
+ "type": "number"
+ },
+ "maxLength": {
+ "$ref": "#/definitions/nonNegativeInteger"
+ },
+ "minLength": {
+ "$ref": "#/definitions/nonNegativeIntegerDefault0"
+ },
+ "pattern": {
+ "type": "string",
+ "format": "regex"
+ },
+ "additionalItems": {
+ "$ref": "#"
+ },
+ "items": {
+ "anyOf": [
+ {
+ "$ref": "#"
+ },
+ {
+ "$ref": "#/definitions/schemaArray"
+ }
+ ],
+ "default": true
+ },
+ "maxItems": {
+ "$ref": "#/definitions/nonNegativeInteger"
+ },
+ "minItems": {
+ "$ref": "#/definitions/nonNegativeIntegerDefault0"
+ },
+ "uniqueItems": {
+ "type": "boolean",
+ "default": false
+ },
+ "contains": {
+ "$ref": "#"
+ },
+ "maxProperties": {
+ "$ref": "#/definitions/nonNegativeInteger"
+ },
+ "minProperties": {
+ "$ref": "#/definitions/nonNegativeIntegerDefault0"
+ },
+ "required": {
+ "$ref": "#/definitions/stringArray"
+ },
+ "additionalProperties": {
+ "$ref": "#"
+ },
+ "definitions": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#"
+ },
+ "default": {}
+ },
+ "properties": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#"
+ },
+ "default": {}
+ },
+ "patternProperties": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#"
+ },
+ "propertyNames": {
+ "format": "regex"
+ },
+ "default": {}
+ },
+ "dependencies": {
+ "type": "object",
+ "additionalProperties": {
+ "anyOf": [
+ {
+ "$ref": "#"
+ },
+ {
+ "$ref": "#/definitions/stringArray"
+ }
+ ]
+ }
+ },
+ "propertyNames": {
+ "$ref": "#"
+ },
+ "const": true,
+ "enum": {
+ "type": "array",
+ "items": true,
+ "minItems": 1,
+ "uniqueItems": true
+ },
+ "type": {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/simpleTypes"
+ },
+ {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/simpleTypes"
+ },
+ "minItems": 1,
+ "uniqueItems": true
+ }
+ ]
+ },
+ "format": {
+ "type": "string"
+ },
+ "contentMediaType": {
+ "type": "string"
+ },
+ "contentEncoding": {
+ "type": "string"
+ },
+ "if": {
+ "$ref": "#"
+ },
+ "then": {
+ "$ref": "#"
+ },
+ "else": {
+ "$ref": "#"
+ },
+ "allOf": {
+ "$ref": "#/definitions/schemaArray"
+ },
+ "anyOf": {
+ "$ref": "#/definitions/schemaArray"
+ },
+ "oneOf": {
+ "$ref": "#/definitions/schemaArray"
+ },
+ "not": {
+ "$ref": "#"
+ },
+ "propertyOrder": {
+ "$ref": "#/definitions/stringArray"
+ }
+ },
+ "default": true,
+ "additionalProperties": false
+}
diff --git a/src/components/Editor.tsx b/src/components/Editor.tsx
index 7bc06bf..76bca29 100644
--- a/src/components/Editor.tsx
+++ b/src/components/Editor.tsx
@@ -7,16 +7,30 @@ import MonacoEditor, {
import { ValidateButton } from "./Validate";
import { Box } from "@chakra-ui/react";
+export const MODES = {
+ DEFINITION: "DEFINITION",
+ INPUT_SCHEMA: "INPUT_SCHEMA",
+} as const;
+
+export type SupportedModes = keyof typeof MODES;
+
+function isDefinitionMode(settings?: InteralSettings) {
+ return settings?.mode === MODES.DEFINITION || !settings?.mode;
+}
+
type InteralSettings = {
- enableExperimentalValidation: boolean;
+ mode?: SupportedModes;
+ enableExperimentalValidation?: boolean;
};
function configureEditor(
monaco: Monaco,
settings: InteralSettings = {
+ mode: MODES.DEFINITION,
enableExperimentalValidation: true,
},
) {
+ console.log("Configuring editor with settings", settings);
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
enableSchemaRequest: true,
validate: true,
@@ -26,7 +40,11 @@ function configureEditor(
schemas: [
{
uri: `${window.location.origin}${process.env.NEXT_PUBLIC_BASE_PATH}/schemas/flow_definition_schema.json`,
- fileMatch: ["*"],
+ fileMatch: ["definition.json"],
+ },
+ {
+ uri: `${window.location.origin}${process.env.NEXT_PUBLIC_BASE_PATH}/schemas/input_schema_schema.json`,
+ fileMatch: ["input-schema.json"],
},
],
});
@@ -46,9 +64,11 @@ export default function Editor(
}}
{...props}
/>
-
-
-
+ {isDefinitionMode(props.settings) && (
+
+
+
+ )}
>
);
diff --git a/src/pages/index.tsx b/src/pages/index.tsx
index add8603..ff72a68 100644
--- a/src/pages/index.tsx
+++ b/src/pages/index.tsx
@@ -24,7 +24,7 @@ import {
IconButton,
} from "@chakra-ui/react";
import { CloseIcon, HamburgerIcon } from "@chakra-ui/icons";
-import Editor from "../components/Editor";
+import Editor, { MODES, SupportedModes } from "../components/Editor";
import Diagram from "../components/Diagram/Diagram";
import {
compressToEncodedURIComponent,
@@ -93,6 +93,14 @@ export default function Home() {
const [invalidMarkers, setValidity] = useState([]);
const [showPanel, setShowPanel] = useState(false);
+ const [mode, setMode] = useState(MODES.DEFINITION);
+
+ const isDefinitionMode = mode === MODES.DEFINITION;
+
+ const editorContents = isDefinitionMode
+ ? editorStore.definition
+ : editorStore.schmea;
+
useEffect(() => {
if (bootstrapped.current) return;
bootstrapped.current = true;
@@ -115,11 +123,11 @@ export default function Home() {
}, [editorStore]);
useEffect(() => {
- if (definition) {
+ if (isDefinitionMode && definition) {
const v = compressToEncodedURIComponent(JSON.stringify(definition));
router.push(`/?d=${v}`);
}
- }, [definition, router]);
+ }, [isDefinitionMode, definition, router]);
function handleEditorValidate(markers: any[]) {
const errors = markers.filter(
@@ -180,18 +188,44 @@ export default function Home() {
-
+
{showPanel && }
+ {
+ setMode(index === 0 ? MODES.DEFINITION : MODES.INPUT_SCHEMA);
+ }}
+ >
+
+ Definiton
+ Input Schema
+
+
{
+ if (isDefinitionMode) {
+ editorStore.replaceDefinitionFromString(value);
+ } else {
+ editorStore.replaceSchemaFromString(value);
+ }
+ }}
onValidate={handleEditorValidate}
theme="vs-dark"
- settings={{ enableExperimentalValidation: true }}
+ settings={{ enableExperimentalValidation: true, mode }}
+ path={
+ mode === MODES.DEFINITION
+ ? "definition.json"
+ : "input-schema.json"
+ }
/>
- Diagram
+ Definiton Diagram
+ {/* Input Schema UI */}
Documentation
@@ -238,6 +273,7 @@ export default function Home() {
)}
+ {/* */}
| undefined;
definition: FlowDefinition | undefined;
replace: (def?: FlowDefinition) => void;
+ replaceSchemaFromString: (s?: string) => void;
replaceDefinitionFromString: (s?: string) => void;
/**
* Attempt to restore the definition from local storage.
@@ -26,7 +28,14 @@ type EditorState = {
const DEFINITION_LOCAL_STORAGE_KEY = "definition";
export const useEditorStore = create((set, get) => ({
+ schmea: undefined,
definition: undefined,
+ replaceSchemaFromString(s?: string) {
+ try {
+ const v = s ? JSON.parse(s) : undefined;
+ set({ schmea: v });
+ } catch {}
+ },
replaceDefinitionFromString(s?: string) {
try {
const v = s ? JSON.parse(s) : undefined;