From 7b756960ed97bf707c79a924ebb0340e3c1f14fa Mon Sep 17 00:00:00 2001 From: Moritz Jung Date: Thu, 19 Sep 2024 21:56:40 +0200 Subject: [PATCH] add validation to API methods --- bun.lockb | Bin 157212 -> 157540 bytes exampleVault/Test.md | 4 + jsEngine/api/API.ts | 11 ++ jsEngine/api/Internal.ts | 20 ++ jsEngine/api/MarkdownAPI.ts | 68 +++++-- jsEngine/api/MessageAPI.ts | 11 ++ jsEngine/api/PromptAPI.ts | 32 +++- jsEngine/api/QueryAPI.ts | 14 ++ .../api/markdown/AbstractMarkdownElement.ts | 9 +- .../AbstractMarkdownElementContainer.ts | 123 +++++++----- jsEngine/api/markdown/MarkdownBuilder.ts | 5 +- jsEngine/api/markdown/MarkdownString.ts | 17 +- jsEngine/engine/JsExecution.ts | 6 +- jsEngine/main.ts | 3 + jsEngine/utils/Errors.ts | 65 +++++++ jsEngine/utils/Validators.ts | 180 ++++++++++++++++++ package.json | 3 +- 17 files changed, 497 insertions(+), 74 deletions(-) create mode 100644 jsEngine/utils/Errors.ts create mode 100644 jsEngine/utils/Validators.ts diff --git a/bun.lockb b/bun.lockb index 0e630e6cacf1f39b3f10f717004c73f2b82fce1c..2bead42e1b5b8cda3f4d439f35372361ad4e3031 100755 GIT binary patch delta 27108 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 F{~tvkK79ZH diff --git a/exampleVault/Test.md b/exampleVault/Test.md index 3a45a70..3765d6c 100644 --- a/exampleVault/Test.md +++ b/exampleVault/Test.md @@ -227,4 +227,8 @@ el.addEventListener("click", () => { ```js-engine le) +``` + +```js-engine +engine.getPlugin(false) ``` \ No newline at end of file diff --git a/jsEngine/api/API.ts b/jsEngine/api/API.ts index c665a41..9a81849 100644 --- a/jsEngine/api/API.ts +++ b/jsEngine/api/API.ts @@ -8,7 +8,10 @@ import { QueryAPI } from 'jsEngine/api/QueryAPI'; import { ReactiveComponent } from 'jsEngine/api/reactive/ReactiveComponent'; import type { JsFunc } from 'jsEngine/engine/JsExecution'; import type JsEnginePlugin from 'jsEngine/main'; +import type { Validators } from 'jsEngine/utils/Validators'; +import { validateAPIArgs } from 'jsEngine/utils/Validators'; import type { App, Plugin } from 'obsidian'; +import { z } from 'zod'; export class API { /** @@ -20,6 +23,7 @@ export class API { */ readonly plugin: JsEnginePlugin; readonly instanceId: InstanceId; + readonly validators: Validators; /** * API to interact with markdown. */ @@ -46,6 +50,7 @@ export class API { this.app = app; this.plugin = plugin; this.instanceId = instanceId; + this.validators = plugin.validators; this.markdown = new MarkdownAPI(this); this.message = new MessageAPI(this); @@ -65,6 +70,8 @@ export class API { * @param path the vault relative path of the file to import */ public async importJs(path: string): Promise { + validateAPIArgs(z.object({ path: z.string() }), { path }); + let fullPath = this.app.vault.adapter.getResourcePath(path); // we need to remove the query parameters from the path @@ -83,6 +90,8 @@ export class API { * @param pluginId the id of the plugin. */ public getPlugin(pluginId: string): Plugin | undefined { + validateAPIArgs(z.object({ pluginId: z.string() }), { pluginId }); + return this.app.plugins.getPlugin(pluginId) ?? undefined; } @@ -94,6 +103,8 @@ export class API { * @param initialArgs the initial arguments (for the first render) to pass to the function. */ public reactive(fn: JsFunc, ...initialArgs: unknown[]): ReactiveComponent { + validateAPIArgs(z.object({ fn: z.function(), initialArgs: z.array(z.unknown()) }), { fn, initialArgs }); + return new ReactiveComponent(this, fn, initialArgs); } } diff --git a/jsEngine/api/Internal.ts b/jsEngine/api/Internal.ts index 9749747..8690bf8 100644 --- a/jsEngine/api/Internal.ts +++ b/jsEngine/api/Internal.ts @@ -2,8 +2,10 @@ import type { API } from 'jsEngine/api/API'; import type { EngineExecutionParams } from 'jsEngine/engine/Engine'; import type { JsExecution, JsExecutionContext, JsExecutionGlobals, JsExecutionGlobalsConstructionOptions } from 'jsEngine/engine/JsExecution'; import { ResultRenderer } from 'jsEngine/engine/ResultRenderer'; +import { validateAPIArgs } from 'jsEngine/utils/Validators'; import { Component, TFile } from 'obsidian'; import * as Obsidian from 'obsidian'; +import { z } from 'zod'; /** * The internal API provides access to some of js engines internals. @@ -21,6 +23,8 @@ export class InternalAPI { * @param params */ public async execute(params: EngineExecutionParams): Promise { + validateAPIArgs(z.object({ params: this.apiInstance.validators.engineExecutionParams }), { params }); + return await this.apiInstance.plugin.jsEngine.execute(params); } @@ -32,6 +36,11 @@ export class InternalAPI { * @param component */ public createRenderer(container: HTMLElement, sourcePath: string, component: Component): ResultRenderer { + validateAPIArgs( + z.object({ container: this.apiInstance.validators.htmlElement, sourcePath: z.string(), component: this.apiInstance.validators.component }), + { container, sourcePath, component }, + ); + return new ResultRenderer(this.apiInstance.plugin, container, sourcePath, component); } @@ -42,6 +51,8 @@ export class InternalAPI { * @param params */ public async executeFile(path: string, params: Omit): Promise { + validateAPIArgs(z.object({ path: z.string(), params: this.apiInstance.validators.engineExecutionParamsNoCode }), { path, params }); + const file = this.apiInstance.app.vault.getAbstractFileByPath(path); if (!file || !(file instanceof TFile)) { throw new Error(`File ${path} not found.`); @@ -60,6 +71,11 @@ export class InternalAPI { * @param params */ public async executeFileSimple(path: string, params?: Omit): Promise { + validateAPIArgs(z.object({ path: z.string(), params: this.apiInstance.validators.engineExecutionParamsNoCodeAndComponent.optional() }), { + path, + params, + }); + const component = new Component(); component.load(); try { @@ -75,6 +91,8 @@ export class InternalAPI { * @param path */ public async getContextForFile(path: string): Promise { + validateAPIArgs(z.object({ path: z.string() }), { path }); + const file = this.apiInstance.app.vault.getAbstractFileByPath(path); if (!file || !(file instanceof TFile)) { throw new Error(`File ${path} not found.`); @@ -95,6 +113,8 @@ export class InternalAPI { * @param options */ public createExecutionGlobals(options: JsExecutionGlobalsConstructionOptions): JsExecutionGlobals { + validateAPIArgs(z.object({ options: this.apiInstance.validators.jsExecutionGlobalsConstructionOptions }), { options }); + return { app: this.apiInstance.app, engine: options.engine ?? this.apiInstance, diff --git a/jsEngine/api/MarkdownAPI.ts b/jsEngine/api/MarkdownAPI.ts index ae99cf3..96850ac 100644 --- a/jsEngine/api/MarkdownAPI.ts +++ b/jsEngine/api/MarkdownAPI.ts @@ -12,6 +12,8 @@ import { } from 'jsEngine/api/markdown/AbstractMarkdownElementContainer'; import { MarkdownBuilder } from 'jsEngine/api/markdown/MarkdownBuilder'; import { MarkdownString } from 'jsEngine/api/markdown/MarkdownString'; +import { validateAPIArgs } from 'jsEngine/utils/Validators'; +import { z } from 'zod'; /** * The markdown API provides utilities for creating markdown using js. @@ -27,7 +29,7 @@ export class MarkdownAPI { * Creates a markdown builder. */ public createBuilder(): MarkdownBuilder { - return new MarkdownBuilder(); + return new MarkdownBuilder(this.apiInstance); } /** @@ -38,7 +40,9 @@ export class MarkdownAPI { * @param markdown the string to wrap */ public create(markdown: string): MarkdownString { - return new MarkdownString(markdown); + validateAPIArgs(z.object({ markdown: z.string() }), { markdown }); + + return new MarkdownString(this.apiInstance, markdown); } /** @@ -47,7 +51,9 @@ export class MarkdownAPI { * @param text */ public createText(text: string): TextElement { - return new TextElement(text, false, false, false); + validateAPIArgs(z.object({ text: z.string() }), { text }); + + return new TextElement(this.apiInstance, text, false, false, false); } /** @@ -56,7 +62,9 @@ export class MarkdownAPI { * @param text */ public createBoldText(text: string): TextElement { - return new TextElement(text, true, false, false); + validateAPIArgs(z.object({ text: z.string() }), { text }); + + return new TextElement(this.apiInstance, text, true, false, false); } /** @@ -65,7 +73,9 @@ export class MarkdownAPI { * @param text */ public createCursiveText(text: string): TextElement { - return new TextElement(text, false, true, false); + validateAPIArgs(z.object({ text: z.string() }), { text }); + + return new TextElement(this.apiInstance, text, false, true, false); } /** @@ -74,7 +84,9 @@ export class MarkdownAPI { * @param text */ public createUnderlinedText(text: string): TextElement { - return new TextElement(text, false, false, true); + validateAPIArgs(z.object({ text: z.string() }), { text }); + + return new TextElement(this.apiInstance, text, false, false, true); } /** @@ -83,7 +95,9 @@ export class MarkdownAPI { * @param text */ public createCode(text: string): CodeElement { - return new CodeElement(text); + validateAPIArgs(z.object({ text: z.string() }), { text }); + + return new CodeElement(this.apiInstance, text); } /** @@ -92,7 +106,9 @@ export class MarkdownAPI { * @param content */ public createParagraph(content: string): ParagraphElement { - return new ParagraphElement(content); + validateAPIArgs(z.object({ content: z.string() }), { content }); + + return new ParagraphElement(this.apiInstance, content); } /** @@ -102,14 +118,16 @@ export class MarkdownAPI { * @param content the text of the heading */ public createHeading(level: number, content: string): HeadingElement { - return new HeadingElement(level, content); + validateAPIArgs(z.object({ level: z.number(), content: z.string() }), { level, content }); + + return new HeadingElement(this.apiInstance, level, content); } /** * Creates a new markdown block quote element. */ public createBlockQuote(): BlockQuoteElement { - return new BlockQuoteElement(); + return new BlockQuoteElement(this.apiInstance); } /** @@ -120,7 +138,23 @@ export class MarkdownAPI { * @param args the callout args, optional */ public createCallout(title: string, type: string, args: string = ''): CalloutElement { - return new CalloutElement(title, type, args); + validateAPIArgs(z.object({ title: z.string(), type: z.string(), args: z.string() }), { title, type, args }); + + return new CalloutElement(this.apiInstance, title, type, args); + } + + /** + * Creates a new markdown collapsible callout element. + * + * @param title the title of the callout + * @param type the type of the callout + * @param args the callout args, optional + * @param collapsed whether the callout should be collapsed by default, optional + */ + createCollapsibleCallout(title: string, type: string, args: string = '', collapsed: boolean = false): CalloutElement { + validateAPIArgs(z.object({ title: z.string(), type: z.string(), args: z.string(), collapsed: z.boolean() }), { title, type, args, collapsed }); + + return new CalloutElement(this.apiInstance, title, type, args, true, collapsed); } /** @@ -130,7 +164,9 @@ export class MarkdownAPI { * @param content the content of the code block */ public createCodeBlock(language: string, content: string): CodeBlockElement { - return new CodeBlockElement(language, content); + validateAPIArgs(z.object({ language: z.string(), content: z.string() }), { language, content }); + + return new CodeBlockElement(this.apiInstance, language, content); } /** @@ -140,7 +176,9 @@ export class MarkdownAPI { * @param body the table body */ public createTable(header: string[], body: string[][]): TableElement { - return new TableElement(header, body); + validateAPIArgs(z.object({ header: z.array(z.string()), body: z.array(z.array(z.string())) }), { header, body }); + + return new TableElement(this.apiInstance, header, body); } /** @@ -149,6 +187,8 @@ export class MarkdownAPI { * @param ordered whether the list should be ordered or not (use 1. or -) */ createList(ordered: boolean): ListElement { - return new ListElement(ordered); + validateAPIArgs(z.object({ ordered: z.boolean() }), { ordered }); + + return new ListElement(this.apiInstance, ordered); } } diff --git a/jsEngine/api/MessageAPI.ts b/jsEngine/api/MessageAPI.ts index 93ae517..993a05f 100644 --- a/jsEngine/api/MessageAPI.ts +++ b/jsEngine/api/MessageAPI.ts @@ -1,6 +1,8 @@ import type { API } from 'jsEngine/api/API'; import type { MessageManager, MessageType, MessageWrapper } from 'jsEngine/messages/MessageManager'; import { Message } from 'jsEngine/messages/MessageManager'; +import { validateAPIArgs } from 'jsEngine/utils/Validators'; +import { z } from 'zod'; export class MessageAPI { readonly apiInstance: API; @@ -12,10 +14,19 @@ export class MessageAPI { } public createMessage(type: MessageType, title: string, content: string, code: string = ''): MessageWrapper { + validateAPIArgs(z.object({ type: this.apiInstance.validators.messageType, title: z.string(), content: z.string(), code: z.string() }), { + type, + title, + content, + code, + }); + return this.messageManager.addMessage(new Message(type, title, content, code), this.apiInstance.instanceId); } public getMessageById(id: string): MessageWrapper | undefined { + validateAPIArgs(z.object({ id: z.string() }), { id }); + return this.messageManager.messages.get().find(message => message.uuid === id); } diff --git a/jsEngine/api/PromptAPI.ts b/jsEngine/api/PromptAPI.ts index dcb6c83..9005290 100644 --- a/jsEngine/api/PromptAPI.ts +++ b/jsEngine/api/PromptAPI.ts @@ -5,8 +5,14 @@ import { Suggester } from 'jsEngine/api/prompts/Suggester'; import { SvelteModal } from 'jsEngine/api/prompts/SvelteModal'; import type { AnySvelteComponent } from 'jsEngine/utils/SvelteUtils'; import { ButtonStyleType } from 'jsEngine/utils/Util'; +import { validateAPIArgs } from 'jsEngine/utils/Validators'; import { mount } from 'svelte'; +import { z } from 'zod'; +/** + * Basic options for a prompt modal. + * This interface is used as a base for other prompt options. + */ export interface ModalPromptOptions { /** * The title of the modal. @@ -26,11 +32,13 @@ export interface ButtonPromptOptions extends ModalPromptOptions { /** * A list of buttons to display in the modal. */ - buttons: { - label: string; - value: T; - variant?: ButtonStyleType; - }[]; + buttons: ButtonPromptButtonOptions[]; +} + +export interface ButtonPromptButtonOptions { + label: string; + value: T; + variant?: ButtonStyleType; } export interface ConfirmPromptOptions extends ModalPromptOptions { @@ -122,6 +130,8 @@ export class PromptAPI { * ``` */ public button(options: ButtonPromptOptions): Promise { + validateAPIArgs(z.object({ options: this.apiInstance.validators.buttonModalPromptOptions }), { options }); + return new Promise((resolve, reject) => { try { new SvelteModal( @@ -159,6 +169,8 @@ export class PromptAPI { * ``` */ public async confirm(options: ConfirmPromptOptions): Promise { + validateAPIArgs(z.object({ options: this.apiInstance.validators.confirmPromptOptions }), { options }); + return ( (await this.button({ ...options, @@ -193,6 +205,8 @@ export class PromptAPI { * ``` */ public async yesNo(options: YesNoPromptOptions): Promise { + validateAPIArgs(z.object({ options: this.apiInstance.validators.yesNoPromptOptions }), { options }); + return await this.button({ ...options, buttons: [ @@ -232,6 +246,8 @@ export class PromptAPI { * ``` */ public suggester(options: SuggesterPromptOptions): Promise { + validateAPIArgs(z.object({ options: this.apiInstance.validators.suggesterPromptOptions }), { options }); + return new Promise((resolve, reject) => { try { new Suggester(this.apiInstance.app, options, resolve).open(); @@ -257,6 +273,8 @@ export class PromptAPI { * ``` */ public text(options: InputPromptOptions): Promise { + validateAPIArgs(z.object({ options: this.apiInstance.validators.inputPromptOptions }), { options }); + options.initialValue = options.initialValue ?? ''; return new Promise((resolve, reject) => { @@ -299,6 +317,8 @@ export class PromptAPI { * ``` */ public textarea(options: InputPromptOptions): Promise { + validateAPIArgs(z.object({ options: this.apiInstance.validators.inputPromptOptions }), { options }); + options.initialValue = options.initialValue ?? ''; return new Promise((resolve, reject) => { @@ -340,6 +360,8 @@ export class PromptAPI { * ``` */ public number(options: NumberInputPromptOptions): Promise { + validateAPIArgs(z.object({ options: this.apiInstance.validators.numberInputPromptOptions }), { options }); + options.initialValue = options.initialValue ?? 0; return new Promise((resolve, reject) => { diff --git a/jsEngine/api/QueryAPI.ts b/jsEngine/api/QueryAPI.ts index fe420bd..9784fb2 100644 --- a/jsEngine/api/QueryAPI.ts +++ b/jsEngine/api/QueryAPI.ts @@ -1,6 +1,8 @@ import type { API } from 'jsEngine/api/API'; +import { validateAPIArgs } from 'jsEngine/utils/Validators'; import type { CachedMetadata, TFile } from 'obsidian'; import { getAllTags } from 'obsidian'; +import { z } from 'zod'; export class QueryAPI { readonly apiInstance: API; @@ -25,6 +27,8 @@ export class QueryAPI { * ``` */ public files(query: (file: TFile) => T | undefined): T[] { + validateAPIArgs(z.object({ query: z.function().args(this.apiInstance.validators.tFile).returns(z.unknown()) }), { query }); + return this.apiInstance.app.vault .getMarkdownFiles() .map(file => query(file)) @@ -41,6 +45,16 @@ export class QueryAPI { * ``` */ public filesWithMetadata(query: (file: TFile, cache: CachedMetadata | null, tags: string[]) => T | undefined): T[] { + validateAPIArgs( + z.object({ + query: z + .function() + .args(this.apiInstance.validators.tFile, this.apiInstance.validators.cachedMetadata.nullable(), z.string().array()) + .returns(z.unknown()), + }), + { query }, + ); + return this.apiInstance.app.vault .getMarkdownFiles() .map(file => { diff --git a/jsEngine/api/markdown/AbstractMarkdownElement.ts b/jsEngine/api/markdown/AbstractMarkdownElement.ts index 2feec6c..9fd51b5 100644 --- a/jsEngine/api/markdown/AbstractMarkdownElement.ts +++ b/jsEngine/api/markdown/AbstractMarkdownElement.ts @@ -1,3 +1,4 @@ +import type { API } from 'jsEngine/api/API'; import type { MarkdownElementType } from 'jsEngine/api/markdown/MarkdownElementType'; import { MarkdownString } from 'jsEngine/api/markdown/MarkdownString'; @@ -5,6 +6,12 @@ import { MarkdownString } from 'jsEngine/api/markdown/MarkdownString'; * @internal */ export abstract class AbstractMarkdownElement { + readonly apiInstance: API; + + constructor(apiInstance: API) { + this.apiInstance = apiInstance; + } + /** * Converts the element to a string. */ @@ -19,6 +26,6 @@ export abstract class AbstractMarkdownElement { * Converts the element to a {@link MarkdownString}. */ toMarkdown(): MarkdownString { - return new MarkdownString(this.toString()); + return new MarkdownString(this.apiInstance, this.toString()); } } diff --git a/jsEngine/api/markdown/AbstractMarkdownElementContainer.ts b/jsEngine/api/markdown/AbstractMarkdownElementContainer.ts index 90c6db4..f502a79 100644 --- a/jsEngine/api/markdown/AbstractMarkdownElementContainer.ts +++ b/jsEngine/api/markdown/AbstractMarkdownElementContainer.ts @@ -1,6 +1,9 @@ +import type { API } from 'jsEngine/api/API'; import { AbstractMarkdownElement } from 'jsEngine/api/markdown/AbstractMarkdownElement'; import { AbstractMarkdownLiteral } from 'jsEngine/api/markdown/AbstractMarkdownLiteral'; import { MarkdownElementType } from 'jsEngine/api/markdown/MarkdownElementType'; +import { validateAPIArgs } from 'jsEngine/utils/Validators'; +import { z } from 'zod'; /** * @internal @@ -8,8 +11,8 @@ import { MarkdownElementType } from 'jsEngine/api/markdown/MarkdownElementType'; export abstract class AbstractMarkdownElementContainer extends AbstractMarkdownElement { markdownElements: AbstractMarkdownElement[]; - constructor() { - super(); + constructor(apiInstance: API) { + super(apiInstance); this.markdownElements = []; } @@ -30,6 +33,8 @@ export abstract class AbstractMarkdownElementContainer extends AbstractMarkdownE * @throws Error if the element is not allowed in the container. */ addElement(element: AbstractMarkdownElement): void { + validateAPIArgs(z.object({ element: this.apiInstance.validators.abstractMarkdownElement }), { element }); + if (this.allowElement(element)) { this.markdownElements.push(element); } else { @@ -38,79 +43,103 @@ export abstract class AbstractMarkdownElementContainer extends AbstractMarkdownE } addText(text: string): AbstractMarkdownElementContainer { - const element = new TextElement(text, false, false, false); + validateAPIArgs(z.object({ text: z.string() }), { text }); + + const element = new TextElement(this.apiInstance, text, false, false, false); this.addElement(element); return this; } addBoldText(text: string): AbstractMarkdownElementContainer { - const element = new TextElement(text, true, false, false); + validateAPIArgs(z.object({ text: z.string() }), { text }); + + const element = new TextElement(this.apiInstance, text, true, false, false); this.addElement(element); return this; } addCursiveText(text: string): AbstractMarkdownElementContainer { - const element = new TextElement(text, false, true, false); + validateAPIArgs(z.object({ text: z.string() }), { text }); + + const element = new TextElement(this.apiInstance, text, false, true, false); this.addElement(element); return this; } addUnderlinedText(text: string): AbstractMarkdownElementContainer { - const element = new TextElement(text, false, false, true); + validateAPIArgs(z.object({ text: z.string() }), { text }); + + const element = new TextElement(this.apiInstance, text, false, false, true); this.addElement(element); return this; } addCode(text: string): AbstractMarkdownElementContainer { - const element = new CodeElement(text); + validateAPIArgs(z.object({ text: z.string() }), { text }); + + const element = new CodeElement(this.apiInstance, text); this.addElement(element); return this; } createParagraph(content: string): ParagraphElement { - const element = new ParagraphElement(content); + validateAPIArgs(z.object({ content: z.string() }), { content }); + + const element = new ParagraphElement(this.apiInstance, content); this.addElement(element); return element; } createHeading(level: number, content: string): HeadingElement { - const element = new HeadingElement(level, content); + validateAPIArgs(z.object({ level: z.number(), content: z.string() }), { level, content }); + + const element = new HeadingElement(this.apiInstance, level, content); this.addElement(element); return element; } createBlockQuote(): BlockQuoteElement { - const element = new BlockQuoteElement(); + const element = new BlockQuoteElement(this.apiInstance); this.addElement(element); return element; } createCallout(title: string, type: string, args: string = ''): CalloutElement { - const element = new CalloutElement(title, type, args, false, false); + validateAPIArgs(z.object({ title: z.string(), type: z.string(), args: z.string() }), { title, type, args }); + + const element = new CalloutElement(this.apiInstance, title, type, args, false, false); this.addElement(element); return element; } createCollapsibleCallout(title: string, type: string, args: string = '', collapsed: boolean = false): CalloutElement { - const element = new CalloutElement(title, type, args, true, collapsed); + validateAPIArgs(z.object({ title: z.string(), type: z.string(), args: z.string(), collapsed: z.boolean() }), { title, type, args, collapsed }); + + const element = new CalloutElement(this.apiInstance, title, type, args, true, collapsed); this.addElement(element); return element; } createCodeBlock(language: string, content: string): CodeBlockElement { - const element = new CodeBlockElement(language, content); + validateAPIArgs(z.object({ language: z.string(), content: z.string() }), { language, content }); + + const element = new CodeBlockElement(this.apiInstance, language, content); this.addElement(element); return element; } createTable(header: string[], body: string[][]): TableElement { - const element = new TableElement(header, body); + validateAPIArgs(z.object({ header: z.array(z.string()), body: z.array(z.array(z.string())) }), { header, body }); + + const element = new TableElement(this.apiInstance, header, body); this.addElement(element); return element; } createList(ordered: boolean): ListElement { - const element = new ListElement(ordered); + validateAPIArgs(z.object({ ordered: z.boolean() }), { ordered }); + + const element = new ListElement(this.apiInstance, ordered); this.addElement(element); return element; } @@ -129,8 +158,8 @@ export class TextElement extends AbstractMarkdownLiteral { cursive: boolean; underline: boolean; - constructor(content: string, bold: boolean, cursive: boolean, underline: boolean) { - super(); + constructor(apiInstance: API, content: string, bold: boolean, cursive: boolean, underline: boolean) { + super(apiInstance); this.content = content; this.bold = bold; @@ -167,8 +196,8 @@ export class TextElement extends AbstractMarkdownLiteral { export class CodeElement extends AbstractMarkdownLiteral { content: string; - constructor(content: string) { - super(); + constructor(apiInstance: API, content: string) { + super(apiInstance); this.content = content; } @@ -185,8 +214,8 @@ export class TableElement extends AbstractMarkdownLiteral { header: string[]; body: string[][]; - constructor(header: string[], body: string[][]) { - super(); + constructor(apiInstance: API, header: string[], body: string[][]) { + super(apiInstance); this.header = header; this.body = body; @@ -267,8 +296,8 @@ export class TableElement extends AbstractMarkdownLiteral { export class HeadingElement extends AbstractMarkdownElementContainer { level: number; - constructor(level: number, content: string) { - super(); + constructor(apiInstance: API, level: number, content: string) { + super(apiInstance); this.level = level; this.addText(content); @@ -287,8 +316,8 @@ export class HeadingElement extends AbstractMarkdownElementContainer { * Represents a markdown paragraph. */ export class ParagraphElement extends AbstractMarkdownElementContainer { - constructor(content: string) { - super(); + constructor(apiInstance: API, content: string) { + super(apiInstance); this.addText(content); } @@ -308,33 +337,33 @@ export class ParagraphElement extends AbstractMarkdownElementContainer { export class CodeBlockElement extends AbstractMarkdownElementContainer { language: string; - constructor(language: string, content: string) { - super(); + constructor(apiInstance: API, language: string, content: string) { + super(apiInstance); this.language = language; this.addText(content); } - public allowElement(element: AbstractMarkdownElement): boolean { - return element.getType() === MarkdownElementType.LITERAL; - } - public toString(): string { return `\`\`\`${this.language}\n${this.markdownElements.map(x => x.toString()).join('')}\n\`\`\``; } + + public allowElement(element: AbstractMarkdownElement): boolean { + return element.getType() === MarkdownElementType.LITERAL; + } } /** * Represents a markdown block quote. */ export class BlockQuoteElement extends AbstractMarkdownElementContainer { - public allowElement(_: AbstractMarkdownElement): boolean { - return true; - } - public toString(): string { return `> ` + this.markdownElements.map(x => x.toString().replaceAll('\n', '\n> ')).join('\n> \n> '); } + + public allowElement(_: AbstractMarkdownElement): boolean { + return true; + } } /** @@ -347,8 +376,8 @@ export class CalloutElement extends AbstractMarkdownElementContainer { collapsible: boolean; collapsed: boolean; - constructor(title: string, type: string, args: string, collapsible: boolean = false, collapsed: boolean = false) { - super(); + constructor(apiInstance: API, title: string, type: string, args: string, collapsible: boolean = false, collapsed: boolean = false) { + super(apiInstance); this.title = title; this.type = type; @@ -357,10 +386,6 @@ export class CalloutElement extends AbstractMarkdownElementContainer { this.collapsed = collapsed; } - public allowElement(_: AbstractMarkdownElement): boolean { - return true; - } - public toString(): string { return ( `> [!${this.type}|${this.args}]${this.collapseChar()} ${this.title}` + @@ -376,21 +401,21 @@ export class CalloutElement extends AbstractMarkdownElementContainer { return ''; } } + + public allowElement(_: AbstractMarkdownElement): boolean { + return true; + } } export class ListElement extends AbstractMarkdownElementContainer { ordered: boolean; - constructor(ordered: boolean) { - super(); + constructor(apiInstance: API, ordered: boolean) { + super(apiInstance); this.ordered = ordered; } - public allowElement(_: AbstractMarkdownElement): boolean { - return true; - } - private getPrefix(i: number): string { if (this.ordered) { return `${i + 1}. `; @@ -410,4 +435,8 @@ export class ListElement extends AbstractMarkdownElementContainer { }) .join('\n'); } + + public allowElement(_: AbstractMarkdownElement): boolean { + return true; + } } diff --git a/jsEngine/api/markdown/MarkdownBuilder.ts b/jsEngine/api/markdown/MarkdownBuilder.ts index d829c7f..86e26db 100644 --- a/jsEngine/api/markdown/MarkdownBuilder.ts +++ b/jsEngine/api/markdown/MarkdownBuilder.ts @@ -1,3 +1,4 @@ +import type { API } from 'jsEngine/api/API'; import type { AbstractMarkdownElement } from 'jsEngine/api/markdown/AbstractMarkdownElement'; import { AbstractMarkdownElementContainer } from 'jsEngine/api/markdown/AbstractMarkdownElementContainer'; @@ -5,8 +6,8 @@ import { AbstractMarkdownElementContainer } from 'jsEngine/api/markdown/Abstract * Allows for easily building markdown using JavaScript. */ export class MarkdownBuilder extends AbstractMarkdownElementContainer { - constructor() { - super(); + constructor(apiInstance: API) { + super(apiInstance); } public toString(): string { diff --git a/jsEngine/api/markdown/MarkdownString.ts b/jsEngine/api/markdown/MarkdownString.ts index 5adf559..a80e733 100644 --- a/jsEngine/api/markdown/MarkdownString.ts +++ b/jsEngine/api/markdown/MarkdownString.ts @@ -1,13 +1,18 @@ +import type { API } from 'jsEngine/api/API'; +import { validateAPIArgs } from 'jsEngine/utils/Validators'; import type { App, Component } from 'obsidian'; import { MarkdownRenderer } from 'obsidian'; +import { z } from 'zod'; /** * A string that should be rendered as markdown by the plugin. */ export class MarkdownString { readonly content: string; + readonly apiInstance: API; - constructor(content: string) { + constructor(apiInstance: API, content: string) { + this.apiInstance = apiInstance; this.content = content; } @@ -15,6 +20,16 @@ export class MarkdownString { * @internal */ async render(app: App, element: HTMLElement, sourcePath: string, component: Component): Promise { + validateAPIArgs( + z.object({ + app: z.object({}), + element: this.apiInstance.validators.htmlElement, + sourcePath: z.string(), + component: this.apiInstance.validators.component, + }), + { app, element, sourcePath, component }, + ); + await MarkdownRenderer.render(app, this.content, element, sourcePath, component); } } diff --git a/jsEngine/engine/JsExecution.ts b/jsEngine/engine/JsExecution.ts index 97f0515..5e34609 100644 --- a/jsEngine/engine/JsExecution.ts +++ b/jsEngine/engine/JsExecution.ts @@ -19,15 +19,15 @@ export interface JsExecutionContext { /** * The file that the execution was triggered from. */ - file: TFile | undefined; + file?: TFile | undefined; /** * The metadata of the file that the execution was triggered from. */ - metadata: CachedMetadata | undefined; + metadata?: CachedMetadata | undefined; /** * Currently unused. */ - block: Block | undefined; + block?: Block | undefined; } export interface Block { diff --git a/jsEngine/main.ts b/jsEngine/main.ts index 80161b9..c36c191 100644 --- a/jsEngine/main.ts +++ b/jsEngine/main.ts @@ -7,6 +7,7 @@ import { JsMDRC } from 'jsEngine/JsMDRC'; import { MessageManager } from 'jsEngine/messages/MessageManager'; import type { JsEnginePluginSettings } from 'jsEngine/Settings'; import { JS_ENGINE_DEFAULT_SETTINGS } from 'jsEngine/Settings'; +import { Validators } from 'jsEngine/utils/Validators'; import type { App, PluginManifest } from 'obsidian'; import { Plugin } from 'obsidian'; @@ -15,10 +16,12 @@ export default class JsEnginePlugin extends Plugin { messageManager: MessageManager; jsEngine: Engine; api: API; + validators: Validators; constructor(app: App, manifest: PluginManifest) { super(app, manifest); + this.validators = new Validators(); this.messageManager = new MessageManager(this.app, this); this.jsEngine = new Engine(this.app, this); this.api = new API(this.app, this, InstanceId.create(InstanceType.PLUGIN)); diff --git a/jsEngine/utils/Errors.ts b/jsEngine/utils/Errors.ts new file mode 100644 index 0000000..908c47c --- /dev/null +++ b/jsEngine/utils/Errors.ts @@ -0,0 +1,65 @@ +export enum ErrorLevel { + CRITICAL = 'CRITICAL', + ERROR = 'ERROR', + WARNING = 'WARNING', +} + +export enum ErrorType { + INTERNAL = 'MB_INTERNAL', + + OTHER = 'OTHER', +} + +interface JSEngineErrorParams { + errorLevel: ErrorLevel; + effect: string; + cause: string | Error; + tip?: string; + docs?: string[]; + context?: Record; + positionContext?: string; +} + +export abstract class JSEngineError extends Error { + abstract getErrorType(): ErrorType; + + errorLevel: ErrorLevel; + effect: string; + cause: string | Error; + tip?: string; + docs?: string[]; + context?: Record; + positionContext?: string; + + constructor(params: JSEngineErrorParams) { + super(''); + + this.errorLevel = params.errorLevel; + this.effect = params.effect; + this.cause = params.cause; + this.tip = params.tip; + this.docs = params.docs; + this.context = params.context; + this.positionContext = params.positionContext; + + this.updateMessage(); + } + + protected updateMessage(): void { + if (this.cause instanceof Error) { + this.message = `[${this.getErrorType()}] "${this.effect}" caused by error "${this.cause.message}"`; + } else { + this.message = `[${this.getErrorType()}] "${this.effect}" caused by "${this.cause}"`; + } + } + + public log(): void { + console.log(this.message, this.stack, this.context); + } +} + +export class JSEngineInternalError extends JSEngineError { + public getErrorType(): ErrorType { + return ErrorType.INTERNAL; + } +} diff --git a/jsEngine/utils/Validators.ts b/jsEngine/utils/Validators.ts new file mode 100644 index 0000000..a9b664b --- /dev/null +++ b/jsEngine/utils/Validators.ts @@ -0,0 +1,180 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import { API } from 'jsEngine/api/API'; +import { AbstractMarkdownElement } from 'jsEngine/api/markdown/AbstractMarkdownElement'; +import type { + ButtonPromptButtonOptions, + ButtonPromptOptions, + ConfirmPromptOptions, + InputPromptOptions, + NumberInputPromptOptions, + SuggesterOption, + SuggesterPromptOptions, + YesNoPromptOptions, +} from 'jsEngine/api/PromptAPI'; +import type { EngineExecutionParams } from 'jsEngine/engine/Engine'; +import type { Block, JsExecutionContext, JsExecutionGlobalsConstructionOptions } from 'jsEngine/engine/JsExecution'; +import { MessageType } from 'jsEngine/messages/MessageManager'; +import { ErrorLevel, JSEngineInternalError } from 'jsEngine/utils/Errors'; +import { ButtonStyleType } from 'jsEngine/utils/Util'; +import type { CachedMetadata } from 'obsidian'; +import { Component, TFile } from 'obsidian'; +import { z } from 'zod'; + +export function schemaForType(): >(arg: S) => S { + return function >(arg: S): S { + return arg; + }; +} + +export function validateAPIArgs(validator: z.ZodType, args: T): void { + const result = validator.safeParse(args); + + if (!result.success) { + throw new JSEngineInternalError({ + errorLevel: ErrorLevel.CRITICAL, + effect: 'Failed to run function due to invalid arguments. Check that the arguments that you are passing to the function match the type definition of the function.', + cause: result.error, + }); + } +} + +export class Validators { + htmlElement: z.ZodType; + voidFunction: z.ZodType<() => void, any, any>; + component: z.ZodType; + tFile: z.ZodType; + cachedMetadata: z.ZodType; + block: z.ZodType; + jsExecutionContext: z.ZodType; + engineExecutionParams: z.ZodType; + engineExecutionParamsNoCode: z.ZodType, any, any>; + engineExecutionParamsNoCodeAndComponent: z.ZodType, any, any>; + jsExecutionGlobalsConstructionOptions: z.ZodType; + abstractMarkdownElement: z.ZodType; + messageType: z.ZodType; + buttonStyleType: z.ZodType; + buttonPromptButtonOptions: z.ZodType, any, any>; + buttonModalPromptOptions: z.ZodType, any, any>; + confirmPromptOptions: z.ZodType; + yesNoPromptOptions: z.ZodType; + suggesterOption: z.ZodType, any, any>; + suggesterPromptOptions: z.ZodType, any, any>; + inputPromptOptions: z.ZodType; + numberInputPromptOptions: z.ZodType; + + constructor() { + this.htmlElement = schemaForType()(z.instanceof(HTMLElement)); + this.voidFunction = schemaForType<() => void>()(z.function().args().returns(z.void())); + this.component = schemaForType()(z.instanceof(Component)); + this.tFile = schemaForType()(z.instanceof(TFile)); + this.cachedMetadata = schemaForType()(z.record(z.unknown())) as z.ZodType; + this.block = schemaForType()( + z.object({ + from: z.number(), + to: z.number(), + }), + ); + this.jsExecutionContext = schemaForType()( + z.object({ + file: this.tFile.optional(), + metadata: this.cachedMetadata.optional(), + block: this.block.optional(), + }), + ); + this.engineExecutionParams = schemaForType()( + z.object({ + code: z.string(), + component: this.component, + container: this.htmlElement.optional(), + context: this.jsExecutionContext.optional(), + contextOverrides: z.record(z.unknown()).optional(), + }), + ); + this.engineExecutionParamsNoCode = schemaForType>()( + z.object({ + component: this.component, + container: this.htmlElement.optional(), + context: this.jsExecutionContext.optional(), + contextOverrides: z.record(z.unknown()).optional(), + }), + ); + this.engineExecutionParamsNoCodeAndComponent = schemaForType>()( + z.object({ + container: this.htmlElement.optional(), + context: this.jsExecutionContext.optional(), + contextOverrides: z.record(z.unknown()).optional(), + }), + ); + this.jsExecutionGlobalsConstructionOptions = schemaForType()( + z.object({ + engine: z.instanceof(API).optional(), + component: this.component, + context: z.record(z.unknown()), + container: this.htmlElement.optional(), + }), + ); + this.abstractMarkdownElement = schemaForType()(z.instanceof(AbstractMarkdownElement)); + this.messageType = schemaForType()(z.nativeEnum(MessageType)); + this.buttonStyleType = schemaForType()(z.nativeEnum(ButtonStyleType)); + this.buttonPromptButtonOptions = schemaForType>()( + z.object({ + label: z.string(), + value: z.unknown(), + variant: this.buttonStyleType.optional(), + }) as z.ZodType, any, any>, + ); + this.buttonModalPromptOptions = schemaForType>()( + z.object({ + title: z.string(), + classes: z.string().array().optional(), + content: z.string().optional(), + buttons: this.buttonPromptButtonOptions.array(), + }), + ); + this.confirmPromptOptions = schemaForType()( + z.object({ + title: z.string(), + classes: z.string().array().optional(), + content: z.string().optional(), + }), + ); + this.yesNoPromptOptions = schemaForType()( + z.object({ + title: z.string(), + classes: z.string().array().optional(), + content: z.string().optional(), + }), + ); + this.suggesterOption = schemaForType>()( + z.object({ + value: z.unknown(), + label: z.string(), + }) as z.ZodType, any, any>, + ); + this.suggesterPromptOptions = schemaForType>()( + z.object({ + placeholder: z.string().optional(), + options: this.suggesterOption.array(), + }), + ); + this.inputPromptOptions = schemaForType()( + z.object({ + title: z.string(), + classes: z.string().array().optional(), + content: z.string().optional(), + placeholder: z.string().optional(), + initialValue: z.string().optional(), + }), + ); + this.numberInputPromptOptions = schemaForType()( + z.object({ + title: z.string(), + classes: z.string().array().optional(), + content: z.string().optional(), + placeholder: z.string().optional(), + initialValue: z.number().optional(), + }), + ); + } +} diff --git a/package.json b/package.json index a559f37..5645ff7 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,8 @@ "dependencies": { "@codemirror/legacy-modes": "^6.4.1", "@lemons_dev/parsinom": "^0.0.12", - "itertools-ts": "^1.27.1" + "itertools-ts": "^1.27.1", + "zod": "^3.23.8" }, "trustedDependencies": [ "svelte-preprocess"