diff --git a/controller/src/extension.ts b/controller/src/extension.ts index fcd27de..c648131 100644 --- a/controller/src/extension.ts +++ b/controller/src/extension.ts @@ -350,7 +350,7 @@ export function activate(context: vscode.ExtensionContext) { "**/src/vision/sparql/*.sparql" ); let viewpointsFolderWatcher = vscode.workspace.createFileSystemWatcher( - "**/src/vision/viewpoints/*.json" + "**/src/vision/viewpoints/**/*.json" ); let commandsFolderWatcher = vscode.workspace.createFileSystemWatcher( "**/src/vision/commands/*.json" diff --git a/controller/src/interfaces/ICommandSchema.ts b/controller/src/interfaces/ICommandSchema.ts new file mode 100644 index 0000000..650bef1 --- /dev/null +++ b/controller/src/interfaces/ICommandSchema.ts @@ -0,0 +1,24 @@ +/** + * Defines the structure of the JSON object that is received from the JSON files in the commands directory. + * + * @field name - The name of the command + * @field id - The id of the command + * @field command - The command object + * + */ +export default interface ICommandSchema { + name: string; + id: string; + command: Command; +} + +/** + * Defines the structure of the command + * + * @field type - CRUD command + * + */ +interface Command { + type: string; +} + diff --git a/controller/src/interfaces/IPagesSchema.ts b/controller/src/interfaces/IPagesSchema.ts new file mode 100644 index 0000000..3a67082 --- /dev/null +++ b/controller/src/interfaces/IPagesSchema.ts @@ -0,0 +1,31 @@ +/** + * Defines the structure of the JSON object that is received from the pages.json file. + * + * @field title - The title of the page item + * @field type - The type of the page item. Home = Home Page (there can only be one home page), Group = collection of pages, Diagram = Diagram Page, Tree = Tree Page, and Table = Table Page + * @field path - The path to the configuration of the page item + * @field iconUrl - The url to the icon that gets displayed in the home page. This is typically the url to a SVG published to the web + * @field children - The children of the group items + * + */ +export default interface IPagesSchema { + title: string; + type: string; + path?: string; + iconUrl?: string; + children?: Children[]; +} + +/** + * Defines the structure of the child pages + * + * @field title - The title of the child page item + * @field type - The type of the child page item. Home = Home Page (there can only be one home page), Group = collection of pages, Diagram = Diagram Page, Tree = Tree Page, and Table = Table Page + * @field path - The path to the configuration of the child page item + * + */ +interface Children { + title: string; + type: string; + path: string; +} diff --git a/controller/src/interfaces/ISparqlConfigSchema.ts b/controller/src/interfaces/ISparqlConfigSchema.ts new file mode 100644 index 0000000..6095878 --- /dev/null +++ b/controller/src/interfaces/ISparqlConfigSchema.ts @@ -0,0 +1,18 @@ +/** + * Defines the structure of the JSON object that is received from the sparqlConfig.json file. + * + * @remarks + * This interface relates to {@link https://jena.apache.org/documentation/fuseki2/fuseki-server-info.html | Fuseki endpoints}. + * + * @field queryEndpoint - The query endpoint + * @field updateAssertionEndpoint - The update endpoint for assertions + * @field updateInferenceEndpoint - The update endpoint for inferences + * @field pingEndpoint - The ping endpoint + * + */ +export default interface ISparqlConfigSchema { + queryEndpoint: string; + updateAssertionEndpoint: string; + updateInferenceEndpoint: string; + pingEndpoint: string; +} diff --git a/controller/src/schemas/commands/commandSchema.ts b/controller/src/schemas/commands/commandSchema.ts new file mode 100644 index 0000000..9a06f8a --- /dev/null +++ b/controller/src/schemas/commands/commandSchema.ts @@ -0,0 +1,38 @@ +import ICommandSchema from "../../interfaces/ICommandSchema"; +import { JSONSchemaType } from "ajv"; + +/** + * This JSON schema is used with AJV to validate JSON files stored in the commands directory that is stored within OML models. + * + * @remarks + * This constant will help validate JSON data using {@link https://ajv.js.org | AJV}. + * + * The data within the constant was generated with {@link https://www.jsonschema.net | JSON Schema}. + * + */ + +export const commandSchema: JSONSchemaType = { + $schema: "http://json-schema.org/draft-07/schema#", + type: "array", + items: { + type: "object", + properties: { + name: { + type: "string", + }, + id: { + type: "string", + }, + command: { + type: "object", + properties: { + type: { + type: "string", + }, + }, + required: ["type"], + }, + }, + required: ["name", "id", "command"], + }, +}; diff --git a/controller/src/schemas/config/sparqlConfigSchema.ts b/controller/src/schemas/config/sparqlConfigSchema.ts new file mode 100644 index 0000000..cc3d792 --- /dev/null +++ b/controller/src/schemas/config/sparqlConfigSchema.ts @@ -0,0 +1,40 @@ +import ISparqlConfigSchema from '../../interfaces/ISparqlConfigSchema'; +import { JSONSchemaType } from "ajv"; + +/** + * This JSON schema is used with AJV to validate sparqlConfig.json data that is stored within OML models. + * + * @remarks + * This constant will help validate JSON data using {@link https://ajv.js.org | AJV}. + * + * The data within the constant was generated with {@link https://www.jsonschema.net | JSON Schema}. + * + */ + +export const sparqlConfigSchema: JSONSchemaType = { + type: 'object', + properties: { + queryEndpoint: { + type: 'string', + title: 'Query Endpoint', + default: 'http://example.com/sparql', + examples: ['http://example.com/sparql'] + }, + updateAssertionEndpoint: { + type: 'string', + title: 'Update Assertion Endpoint', + default: 'http://example.com/update/assertion' + }, + updateInferenceEndpoint: { + type: 'string', + title: 'Update Inference Endpoint', + default: 'http://example.com/update/inference' + }, + pingEndpoint: { + type: 'string', + title: 'Ping Endpoint', + default: 'http://example.com/ping' + } + }, + required: ['queryEndpoint', 'updateAssertionEndpoint', 'updateInferenceEndpoint', 'pingEndpoint'] +}; diff --git a/controller/src/schemas/validator.ts b/controller/src/schemas/validator.ts new file mode 100644 index 0000000..b2cf482 --- /dev/null +++ b/controller/src/schemas/validator.ts @@ -0,0 +1,34 @@ +import Ajv, { JSONSchemaType } from "ajv"; +import ISparqlConfigSchema from "../interfaces/ISparqlConfigSchema"; +import ICommandSchema from "../interfaces/ICommandSchema"; +import IPagesSchema from "../interfaces/IPagesSchema"; + +/** + * This schema validator uses AJV to validate JSON data based on a schema interface. + * + * @remarks + * This function will help validate JSON data using {@link https://ajv.js.org | AJV}. + * + * To learn more about Typescript interfaces refer to the official {@link https://www.typescriptlang.org/docs/handbook/2/objects.html | docs}. + * + * @param schema: The JSON schema that will be used to validate the data + * @param data: The JSON data that will be validated against a schema + * + */ +export const validateSchema = ( + schema: JSONSchemaType, + data: Object +) => { + const ajv = new Ajv(); + + // Validates the provided schema + const validate = ajv.compile(schema); + + // Log errors + if (!validate(data)) { + console.error(validate.errors); + } + + // Return true if schema is valid and false if invalid + return validate(data); +}; diff --git a/controller/src/schemas/viewpoints/pagesSchema.ts b/controller/src/schemas/viewpoints/pagesSchema.ts new file mode 100644 index 0000000..e02f8e2 --- /dev/null +++ b/controller/src/schemas/viewpoints/pagesSchema.ts @@ -0,0 +1,60 @@ +import IPagesSchema from "../../interfaces/IPagesSchema"; +import { JSONSchemaType } from "ajv"; + +/** + * This JSON schema is used with AJV to validate pages.json data that is stored within OML models. + * + * @remarks + * This constant will help validate JSON data using {@link https://ajv.js.org | AJV}. + * + * This schema has a recursive schema which you can read more {@link https://ajv.js.org/guide/combining-schemas.html | here}. + * + * This schema has nullable properties which you can read more {@link https://ajv.js.org/guide/typescript.html#utility-types-for-schemas | here}. + * + * The data within the constant was generated with {@link https://www.jsonschema.net | JSON Schema}. + * + */ + +export const pagesSchema: JSONSchemaType = { + $schema: "http://json-schema.org/draft-07/schema#", + type: "array", + items: { + type: "object", + properties: { + title: { + type: "string", + }, + type: { + type: "string", + }, + path: { + type: "string", + nullable: true, + }, + iconUrl: { + type: "string", + nullable: true, + }, + children: { + type: "array", + nullable: true, + items: { + type: "object", + properties: { + title: { + type: "string", + }, + type: { + type: "string", + }, + path: { + type: "string", + }, + }, + required: ["title", "type", "path"], + }, + }, + }, + required: ["title", "type"], + }, +}; diff --git a/controller/src/utilities/loaders/loadCommandFiles.ts b/controller/src/utilities/loaders/loadCommandFiles.ts index 9e4b9c2..5e7d42d 100644 --- a/controller/src/utilities/loaders/loadCommandFiles.ts +++ b/controller/src/utilities/loaders/loadCommandFiles.ts @@ -1,6 +1,8 @@ import { workspace, Uri, commands, window, FileType } from "vscode"; import { TablePanel } from "../../panels/TablePanel"; import { PropertyPanelProvider } from "../../panels/PropertyPanelProvider"; +import { validateSchema } from "../../schemas/validator"; +import { commandSchema } from "../../schemas/commands/commandSchema"; // TODO: handle multiple workspaces (currently assumes model is in the 1st) /** @@ -35,7 +37,14 @@ export const loadCommandFiles = async ( const fileUri = Uri.joinPath(commandFolderUri, file); const buffer = await workspace.fs.readFile(fileUri); const content = JSON.parse(buffer.toString()); - + // Validate if the content matches the JSON Command Schema + const validate = validateSchema(commandSchema, content); + if (files.length > 0 && validate) { + commands.executeCommand("setContext", "vision:hasCommand", true); + window.showInformationMessage(`${file} loaded successfully.`); + } else { + window.showErrorMessage(`Invalid or missing ${file}.`); + } try { TablePanel.updateCommands(); PropertyPanelProvider.updateCommands(); @@ -46,12 +55,6 @@ export const loadCommandFiles = async ( } } } - if (files.length > 0) { - commands.executeCommand("setContext", "vision:hasCommand", true); - window.showInformationMessage("Command files loaded successfully."); - } else { - window.showWarningMessage("Command files not found."); - } } catch (err) { if ( err instanceof Error && diff --git a/controller/src/utilities/loaders/loadSparqlConfigFiles.ts b/controller/src/utilities/loaders/loadSparqlConfigFiles.ts index 31f8fe3..a397a6c 100644 --- a/controller/src/utilities/loaders/loadSparqlConfigFiles.ts +++ b/controller/src/utilities/loaders/loadSparqlConfigFiles.ts @@ -1,69 +1,74 @@ import { workspace, Uri, commands, window, FileType } from "vscode"; import { TreeDataProvider } from "../../sidebar/TreeDataProvider"; +import { validateSchema } from "../../schemas/validator"; +import { sparqlConfigSchema } from "../../schemas/config/sparqlConfigSchema"; export let globalQueryEndpoint: string = ""; export let globalPingEndpoint: string = ""; export let globalUpdateAssertionEndpoint: string = ""; export let globalUpdateInferenceEndpoint: string = ""; +/** + * Loads JSON files that are stored in the config folder of the model. + * + * @remarks + * This method uses the workspace class from the {@link https://code.visualstudio.com/api/references/vscode-api | VSCode API}. + * + * + */ export async function loadSparqlConfigFiles() { - commands.executeCommand("setContext", "vision:hasSparqlConfig", false); - TreeDataProvider.getInstance().updateHasSparqlConfig(false); - - const workspaceFolders = workspace.workspaceFolders; - if (workspaceFolders) { - const uri = workspaceFolders[0].uri; - const configFolderUri = Uri.joinPath( - uri, - "src", - "vision", - "config" - ); - - try { - const files = await workspace.fs.readDirectory(configFolderUri); - - for (const [file, type] of files) { - if (file.endsWith(".json") && type === FileType.File) { - const fileUri = Uri.joinPath(configFolderUri, file); - const buffer = await workspace.fs.readFile(fileUri); - try { - const content: Record = JSON.parse(buffer.toString()); - if (file === "sparqlConfig.json") { - globalQueryEndpoint = content.queryEndpoint; - globalPingEndpoint = content.pingEndpoint; - globalUpdateAssertionEndpoint = content.updateAssertionEndpoint; - globalUpdateInferenceEndpoint = content.updateInferenceEndpoint; - TreeDataProvider.getInstance().updateHasSparqlConfig(true); - } - } catch (parseErr) { - throw new Error(`Error parsing ${file}: ${parseErr}`); + commands.executeCommand("setContext", "vision:hasSparqlConfig", false); + TreeDataProvider.getInstance().updateHasSparqlConfig(false); + + const workspaceFolders = workspace.workspaceFolders; + if (workspaceFolders) { + const uri = workspaceFolders[0].uri; + const configFolderUri = Uri.joinPath(uri, "src", "vision", "config"); + + try { + const files = await workspace.fs.readDirectory(configFolderUri); + + for (const [file, type] of files) { + if (file.endsWith(".json") && type === FileType.File) { + const fileUri = Uri.joinPath(configFolderUri, file); + const buffer = await workspace.fs.readFile(fileUri); + try { + const content: Record = JSON.parse( + buffer.toString() + ); + // Validate if the content matches the SPARQL Config Schema + const validate = validateSchema(sparqlConfigSchema, content); + if (files.length > 0 && file === "sparqlConfig.json" && validate) { + globalQueryEndpoint = content.queryEndpoint; + globalPingEndpoint = content.pingEndpoint; + globalUpdateAssertionEndpoint = content.updateAssertionEndpoint; + globalUpdateInferenceEndpoint = content.updateInferenceEndpoint; + commands.executeCommand( + "setContext", + "vision:hasSparqlConfig", + true + ); + window.showInformationMessage( + `${file} loaded successfully.` + ); + TreeDataProvider.getInstance().updateHasSparqlConfig(true); + } else { + window.showErrorMessage(`Invalid or missing ${file}.`); } + } catch (parseErr) { + throw new Error(`Error parsing ${file}: ${parseErr}`); } } - if (files.length > 0) { - commands.executeCommand( - "setContext", - "vision:hasSparqlConfig", - true - ); - window.showInformationMessage( - "Sparql Config files loaded successfully." - ); - } else { - window.showWarningMessage("Sparql Config files not found."); - } - } catch (err) { - if ( - err instanceof Error && - err.message.startsWith("Error parsing Sparql Config file") - ) - window.showErrorMessage(err.message); - else { - window.showErrorMessage( - `Error reading Sparql Config files: ${err}` - ); - } + } + } catch (err) { + if ( + err instanceof Error && + err.message.startsWith("Error parsing Sparql Config file") + ) + window.showErrorMessage(err.message); + else { + window.showErrorMessage(`Error reading Sparql Config files: ${err}`); } } - } \ No newline at end of file + } +} diff --git a/controller/src/utilities/loaders/loadSparqlFiles.ts b/controller/src/utilities/loaders/loadSparqlFiles.ts index dddfdd1..9479406 100644 --- a/controller/src/utilities/loaders/loadSparqlFiles.ts +++ b/controller/src/utilities/loaders/loadSparqlFiles.ts @@ -1,5 +1,16 @@ import { workspace, Uri, window, FileType } from "vscode"; // TODO: handle multiple workspaces (currently assumes model is in the 1st) + +/** + * Loads SPARQL files that are stored in the sparql folder of the model. + * + * @remarks + * This method uses the workspace class from the {@link https://code.visualstudio.com/api/references/vscode-api | VSCode API}. + * + * @param uri - A universal resource identifier representing either a file on disk or another resource, like untitled resources. + * @param globalSparqlContents - content of the sparql contents object + * + */ export async function loadSparqlFiles(globalSparqlContents: { [file: string]: string; }) { const workspaceFolders = workspace.workspaceFolders; diff --git a/controller/src/utilities/loaders/loadViewpointFiles.ts b/controller/src/utilities/loaders/loadViewpointFiles.ts index 4e8d65b..63b8f1e 100644 --- a/controller/src/utilities/loaders/loadViewpointFiles.ts +++ b/controller/src/utilities/loaders/loadViewpointFiles.ts @@ -2,6 +2,8 @@ import { workspace, Uri, commands, window, FileType } from "vscode"; import { TreeDataProvider } from "../../sidebar/TreeDataProvider"; import { TablePanel } from "../../panels/TablePanel"; import { PropertyPanelProvider } from "../../panels/PropertyPanelProvider"; +import { validateSchema } from "../../schemas/validator"; +import { pagesSchema } from "../../schemas/viewpoints/pagesSchema"; // TODO: handle multiple workspaces (currently assumes model is in the 1st) /** @@ -20,6 +22,7 @@ export const loadViewpointFiles = async (viewpointContents: { TreeDataProvider.getInstance().updateHasPageViewpoint(false); const workspaceFolders = workspace.workspaceFolders; + if (workspaceFolders) { const uri = workspaceFolders[0].uri; loadPageFile(uri, viewpointContents); @@ -57,15 +60,25 @@ const loadPageFile = async ( const fileUri = Uri.joinPath(pageFolderUri, file); const buffer = await workspace.fs.readFile(fileUri); const content = JSON.parse(buffer.toString()); - try { - TreeDataProvider.getInstance().updateViewpoints(content); - TreeDataProvider.getInstance().updateHasPageViewpoint(true); - viewpointContents[file] = content; - } catch (parseErr) { - viewpointContents = {}; - throw new Error( - `Error parsing table viewpoint file ${file}: ${parseErr}` - ); + // Validate if the content matches the JSON Command Schema + const validate = validateSchema(pagesSchema, content); + if (files.length > 0 && validate) { + commands.executeCommand("setContext", "vision:hasCommand", true); + console.log(`${file} loaded successfully.`); + window.showInformationMessage(`${file} loaded successfully.`); + try { + TreeDataProvider.getInstance().updateViewpoints(content); + TreeDataProvider.getInstance().updateHasPageViewpoint(true); + viewpointContents[file] = content; + } catch (parseErr) { + viewpointContents = {}; + throw new Error( + `Error parsing table viewpoint file ${file}: ${parseErr}` + ); + } + } else { + console.error(`Invalid or missing ${file}.`); + window.showErrorMessage(`Invalid or missing ${file}.`); } } } @@ -73,9 +86,12 @@ const loadPageFile = async ( if ( err instanceof Error && err.message.startsWith("Error parsing viewpoint file") - ) + ) { + console.error(err.message); window.showErrorMessage(err.message); + } else { + console.error(`Error reading viewpoint files: ${err}`); window.showErrorMessage(`Error reading viewpoint files: ${err}`); } } @@ -115,19 +131,24 @@ const loadTableFiles = async ( } if (files.length > 0) { commands.executeCommand("setContext", "vision:hasPageViewpoint", true); + console.log("Table viewpoint files loaded successfully."); window.showInformationMessage( "Table viewpoint files loaded successfully." ); } else { + console.warn("Table viewpoint files not found."); window.showWarningMessage("Table viewpoint files not found."); } } catch (err) { if ( err instanceof Error && err.message.startsWith("Error parsing table viewpoint file") - ) + ) { + console.error(err.message); window.showErrorMessage(err.message); + } else { + console.error(`Error reading table viewpoint files: ${err}`); window.showErrorMessage(`Error reading table viewpoint files: ${err}`); } } finally { @@ -171,19 +192,24 @@ const loadTreeFiles = async ( } if (files.length > 0) { commands.executeCommand("setContext", "vision:hasPageViewpoint", true); + console.log("Tree viewpoint files loaded successfully."); window.showInformationMessage( "Tree viewpoint files loaded successfully." ); } else { + console.warn("Tree viewpoint files not found."); window.showWarningMessage("Tree viewpoint files not found."); } } catch (err) { if ( err instanceof Error && err.message.startsWith("Error parsing tree viewpoint file") - ) + ) { + console.error(err.message); window.showErrorMessage(err.message); + } else { + console.error(`Error reading tree viewpoint files: ${err}`); window.showErrorMessage(`Error reading tree viewpoint files: ${err}`); } } finally { @@ -227,13 +253,16 @@ const loadDiagramFiles = async ( } if (files.length > 0) { commands.executeCommand("setContext", "vision:hasPageViewpoint", true); + console.log("Diagram viewpoint files loaded successfully."); window.showInformationMessage( "Diagram viewpoint files loaded successfully." ); } else { + console.warn("Diagram viewpoint files not found."); window.showWarningMessage("Diagram viewpoint files not found."); } } catch (err) { + console.error(`Error reading diagram viewpoint files: ${err}`); window.showErrorMessage(`Error reading diagram viewpoint files: ${err}`); } finally { // Send updated global viewpoints to TablePanels & PropertyPanel @@ -276,13 +305,16 @@ const loadPropertyFiles = async ( } if (files.length > 0) { commands.executeCommand("setContext", "vision:hasPageViewpoint", true); + console.log("Property viewpoint files loaded successfully."); window.showInformationMessage( "Property viewpoint files loaded successfully." ); } else { + console.warn("Property viewpoint files not found."); window.showWarningMessage("Property viewpoint files not found."); } } catch (err) { + console.error(`Error reading property viewpoint files: ${err}`); window.showErrorMessage(`Error reading property viewpoint files: ${err}`); } finally { // Send updated global viewpoints to TablePanels & PropertyPanel @@ -325,13 +357,16 @@ const loadWizardFiles = async ( } if (files.length > 0) { commands.executeCommand("setContext", "vision:hasPageViewpoint", true); + console.log("Wizard viewpoint files loaded successfully."); window.showInformationMessage( "Wizard viewpoint files loaded successfully." ); } else { + console.warn("Wizard viewpoint files not found."); window.showWarningMessage("Wizard viewpoint files not found."); } } catch (err) { + console.error(`Error reading wizard viewpoint files: ${err}`); window.showErrorMessage(`Error reading wizard viewpoint files: ${err}`); } finally { // Send updated global viewpoints to TablePanels & PropertyPanel diff --git a/package-lock.json b/package-lock.json index 8758f10..e35d79f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,6 +38,7 @@ "@vscode/test-electron": "^2.3.2", "@vscode/vsce": "^2.26.1", "@vscode/webview-ui-toolkit": "^1.2.2", + "ajv": "^8.13.0", "elkjs": "^0.8.2", "esbuild": "^0.19.5", "fetch-sparql-endpoint": "^3.3.2", @@ -9724,6 +9725,21 @@ "node": ">= 6.0.0" } }, + "node_modules/ajv": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", + "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.4.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -15584,6 +15600,11 @@ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -17095,6 +17116,14 @@ "once": "^1.3.1" } }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, "node_modules/pure-rand": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.3.tgz", @@ -18094,6 +18123,14 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resize-observer-polyfill": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", @@ -19418,6 +19455,14 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/uritemplate": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/uritemplate/-/uritemplate-0.3.4.tgz", diff --git a/package.json b/package.json index d1b329a..b68037e 100644 --- a/package.json +++ b/package.json @@ -292,6 +292,7 @@ "@vscode/test-electron": "^2.3.2", "@vscode/vsce": "^2.26.1", "@vscode/webview-ui-toolkit": "^1.2.2", + "ajv": "^8.13.0", "elkjs": "^0.8.2", "esbuild": "^0.19.5", "fetch-sparql-endpoint": "^3.3.2", diff --git a/yarn.lock b/yarn.lock index 38b8934..bc418fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5065,6 +5065,16 @@ dependencies: "debug" "4" +"ajv@^8.13.0": + "integrity" "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==" + "resolved" "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz" + "version" "8.13.0" + dependencies: + "fast-deep-equal" "^3.1.3" + "json-schema-traverse" "^1.0.0" + "require-from-string" "^2.0.2" + "uri-js" "^4.4.1" + "ansi-escapes@^4.2.1": "integrity" "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==" "resolved" "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz" @@ -7407,6 +7417,11 @@ "resolved" "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz" "version" "2.3.1" +"json-schema-traverse@^1.0.0": + "integrity" "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + "resolved" "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz" + "version" "1.0.0" + "json-stringify-safe@^5.0.1": "integrity" "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" "resolved" "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" @@ -8313,6 +8328,11 @@ "end-of-stream" "^1.1.0" "once" "^1.3.1" +"punycode@^2.1.0": + "integrity" "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" + "resolved" "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz" + "version" "2.3.1" + "pure-rand@^6.0.0": "integrity" "sha512-KddyFewCsO0j3+np81IQ+SweXLDnDQTs5s67BOnrYmYe/yNmUhttQyGsYzy8yUnoljGAQ9sl38YB4vH8ur7Y+w==" "resolved" "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.3.tgz" @@ -8909,6 +8929,11 @@ "resolved" "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" "version" "2.1.1" +"require-from-string@^2.0.2": + "integrity" "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" + "resolved" "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz" + "version" "2.0.2" + "resize-observer-polyfill@^1.5.1": "integrity" "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" "resolved" "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz" @@ -9752,6 +9777,13 @@ "escalade" "^3.1.1" "picocolors" "^1.0.0" +"uri-js@^4.4.1": + "integrity" "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==" + "resolved" "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" + "version" "4.4.1" + dependencies: + "punycode" "^2.1.0" + "uritemplate@0.3.4": "integrity" "sha512-enADBvHfhjrwxFMTVWeIIYz51SZ91uC6o2MR/NQTVljJB6HTZ8eQL3Q7JBj3RxNISA14MOwJaU3vpf5R6dyxHA==" "resolved" "https://registry.npmjs.org/uritemplate/-/uritemplate-0.3.4.tgz"