Skip to content

Commit 335dde7

Browse files
committed
add API argument validation
1 parent 3787aef commit 335dde7

File tree

4 files changed

+199
-16
lines changed

4 files changed

+199
-16
lines changed

src/api/API.ts

Lines changed: 80 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,27 @@ import { ObsidianButtonActionRunner } from '../fields/button/ObsidianButtonActio
3333
import { ButtonMDRC } from '../renderChildren/ButtonMDRC';
3434
import { InlineButtonMDRC } from '../renderChildren/InlineButtonMDRC';
3535
import { SyntaxHighlightingAPI } from './SyntaxHighlightingAPI';
36+
import {
37+
V_BindTargetDeclaration,
38+
V_BindTargetScope,
39+
V_Component,
40+
V_ComponentLike,
41+
V_FilePath,
42+
V_HTMLElement,
43+
V_RenderChildType,
44+
V_Signal,
45+
V_UnvalidatedInputFieldDeclaration,
46+
V_UnvalidatedViewFieldDeclaration,
47+
V_VoidFunction,
48+
} from './APIValidators';
49+
import { z } from 'zod';
50+
51+
export interface ComponentLike {
52+
addChild(child: Component): void;
53+
}
3654

3755
export class API implements IAPI {
38-
public plugin: MetaBindPlugin;
56+
public readonly plugin: MetaBindPlugin;
3957
public readonly inputField: InputFieldAPI;
4058

4159
public readonly inputFieldParser: InputFieldDeclarationParser;
@@ -83,9 +101,16 @@ export class API implements IAPI {
83101
renderType: RenderChildType,
84102
filePath: string,
85103
containerEl: HTMLElement,
86-
component: Component | MarkdownPostProcessorContext,
104+
component: ComponentLike,
87105
scope?: BindTargetScope | undefined,
88106
): InputFieldMDRC | ExcludedMDRC {
107+
V_UnvalidatedInputFieldDeclaration.parse(unvalidatedDeclaration);
108+
V_RenderChildType.parse(renderType);
109+
V_FilePath.parse(filePath);
110+
V_HTMLElement.parse(containerEl);
111+
V_ComponentLike.parse(component);
112+
V_BindTargetScope.optional().parse(scope);
113+
89114
if (this.plugin.isFilePathExcluded(filePath)) {
90115
return this.createExcludedField(containerEl, filePath, component);
91116
}
@@ -114,9 +139,16 @@ export class API implements IAPI {
114139
renderType: RenderChildType,
115140
filePath: string,
116141
containerEl: HTMLElement,
117-
component: Component | MarkdownPostProcessorContext,
142+
component: ComponentLike,
118143
scope?: BindTargetScope | undefined,
119144
): InputFieldMDRC | ExcludedMDRC {
145+
z.string().parse(fullDeclaration);
146+
V_RenderChildType.parse(renderType);
147+
V_FilePath.parse(filePath);
148+
V_HTMLElement.parse(containerEl);
149+
V_ComponentLike.parse(component);
150+
V_BindTargetScope.optional().parse(scope);
151+
120152
if (this.plugin.isFilePathExcluded(filePath)) {
121153
return this.createExcludedField(containerEl, filePath, component);
122154
}
@@ -145,9 +177,16 @@ export class API implements IAPI {
145177
renderType: RenderChildType,
146178
filePath: string,
147179
containerEl: HTMLElement,
148-
component: Component | MarkdownPostProcessorContext,
180+
component: ComponentLike,
149181
scope?: BindTargetScope | undefined,
150182
): ViewFieldMDRC | ExcludedMDRC {
183+
z.string().parse(fullDeclaration);
184+
V_RenderChildType.parse(renderType);
185+
V_FilePath.parse(filePath);
186+
V_HTMLElement.parse(containerEl);
187+
V_ComponentLike.parse(component);
188+
V_BindTargetScope.optional().parse(scope);
189+
151190
if (this.plugin.isFilePathExcluded(filePath)) {
152191
return this.createExcludedField(containerEl, filePath, component);
153192
}
@@ -175,8 +214,14 @@ export class API implements IAPI {
175214
renderType: RenderChildType,
176215
filePath: string,
177216
containerEl: HTMLElement,
178-
component: Component | MarkdownPostProcessorContext,
217+
component: ComponentLike,
179218
): JsViewFieldMDRC | ExcludedMDRC {
219+
z.string().parse(fullDeclaration);
220+
V_RenderChildType.parse(renderType);
221+
V_FilePath.parse(filePath);
222+
V_HTMLElement.parse(containerEl);
223+
V_ComponentLike.parse(component);
224+
180225
if (this.plugin.isFilePathExcluded(filePath)) {
181226
return this.createExcludedField(containerEl, filePath, component);
182227
}
@@ -196,11 +241,7 @@ export class API implements IAPI {
196241
* @param filePath the file path that the field should be in or an empty string if it is not in a file
197242
* @param component component for lifecycle management
198243
*/
199-
public createExcludedField(
200-
containerEl: HTMLElement,
201-
filePath: string,
202-
component: Component | MarkdownPostProcessorContext,
203-
): ExcludedMDRC {
244+
public createExcludedField(containerEl: HTMLElement, filePath: string, component: ComponentLike): ExcludedMDRC {
204245
const excludedField = new ExcludedMDRC(containerEl, RenderChildType.INLINE, this.plugin, filePath, getUUID());
205246
component.addChild(excludedField);
206247

@@ -227,6 +268,12 @@ export class API implements IAPI {
227268
listenToChildren: boolean = false,
228269
onDelete?: () => void,
229270
): () => void {
271+
V_Signal.parse(signal);
272+
V_FilePath.parse(filePath);
273+
z.array(z.string()).parse(metadataPath);
274+
z.boolean().parse(listenToChildren);
275+
V_VoidFunction.optional().parse(onDelete);
276+
230277
const uuid = getUUID();
231278

232279
const subscription = this.plugin.metadataManager.subscribe(
@@ -259,11 +306,18 @@ export class API implements IAPI {
259306
public createTable(
260307
containerEl: HTMLElement,
261308
filePath: string,
262-
component: Component | MarkdownPostProcessorContext,
309+
component: ComponentLike,
263310
bindTarget: BindTargetDeclaration,
264311
tableHead: string[],
265312
columns: (UnvalidatedInputFieldDeclaration | UnvalidatedViewFieldDeclaration)[],
266313
): MetaBindTable {
314+
V_HTMLElement.parse(containerEl);
315+
V_FilePath.parse(filePath);
316+
V_ComponentLike.parse(component);
317+
V_BindTargetDeclaration.parse(bindTarget);
318+
z.array(z.string()).parse(tableHead);
319+
z.array(z.union([V_UnvalidatedInputFieldDeclaration, V_UnvalidatedViewFieldDeclaration])).parse(columns);
320+
267321
const table = new MetaBindTable(
268322
containerEl,
269323
RenderChildType.INLINE,
@@ -280,15 +334,23 @@ export class API implements IAPI {
280334
}
281335

282336
public createBindTarget(fullDeclaration: string, currentFilePath: string): BindTargetDeclaration {
337+
z.string().parse(fullDeclaration);
338+
V_FilePath.parse(currentFilePath);
339+
283340
return this.bindTargetParser.parseAndValidateBindTarget(fullDeclaration, currentFilePath);
284341
}
285342

286343
public createButtonFromString(
287344
fullDeclaration: string,
288345
filePath: string,
289346
containerEl: HTMLElement,
290-
component: Component | MarkdownPostProcessorContext,
347+
component: ComponentLike,
291348
): ButtonMDRC | ExcludedMDRC {
349+
z.string().parse(fullDeclaration);
350+
V_FilePath.parse(filePath);
351+
V_HTMLElement.parse(containerEl);
352+
V_ComponentLike.parse(component);
353+
292354
if (this.plugin.isFilePathExcluded(filePath)) {
293355
return this.createExcludedField(containerEl, filePath, component);
294356
}
@@ -303,8 +365,13 @@ export class API implements IAPI {
303365
fullDeclaration: string,
304366
filePath: string,
305367
containerEl: HTMLElement,
306-
component: Component | MarkdownPostProcessorContext,
368+
component: ComponentLike,
307369
): InlineButtonMDRC | ExcludedMDRC {
370+
z.string().parse(fullDeclaration);
371+
V_FilePath.parse(filePath);
372+
V_HTMLElement.parse(containerEl);
373+
V_ComponentLike.parse(component);
374+
308375
if (this.plugin.isFilePathExcluded(filePath)) {
309376
return this.createExcludedField(containerEl, filePath, component);
310377
}

src/api/APIValidators.ts

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { z } from 'zod';
2+
import { oneOf, schemaForType } from '../utils/ZodUtils';
3+
import { API, ComponentLike } from './API';
4+
import { RenderChildType } from '../config/FieldConfigs';
5+
import {
6+
UnvalidatedFieldArgument,
7+
UnvalidatedInputFieldDeclaration,
8+
} from '../parsers/inputFieldParser/InputFieldDeclaration';
9+
import { ParsingResultNode } from '../parsers/nomParsers/GeneralNomParsers';
10+
import { ParsingPosition, ParsingRange } from '@lemons_dev/parsinom/lib/HelperTypes';
11+
import {
12+
BindTargetDeclaration,
13+
BindTargetStorageType,
14+
UnvalidatedBindTargetDeclaration,
15+
UnvalidatedPropAccess,
16+
} from '../parsers/bindTargetParser/BindTargetDeclaration';
17+
import { PROP_ACCESS_TYPE } from '../utils/prop/PropAccess';
18+
import { ErrorCollection } from '../utils/errors/ErrorCollection';
19+
import { Component } from 'obsidian';
20+
import { BindTargetScope } from '../metadata/BindTargetScope';
21+
import { Signal } from '../utils/Signal';
22+
import { UnvalidatedViewFieldDeclaration } from '../parsers/viewFieldParser/ViewFieldDeclaration';
23+
import { PropPath } from '../utils/prop/PropPath';
24+
25+
export const V_FilePath = schemaForType<string>()(z.string());
26+
27+
export const V_RenderChildType = schemaForType<RenderChildType>()(z.nativeEnum(RenderChildType));
28+
29+
export const V_HTMLElement = schemaForType<HTMLElement>()(z.instanceof(HTMLElement));
30+
export const V_Component = schemaForType<Component>()(z.instanceof(Component));
31+
export const V_BindTargetScope = schemaForType<BindTargetScope>()(z.instanceof(BindTargetScope));
32+
33+
export const V_ParsingPosition = schemaForType<ParsingPosition>()(
34+
z.object({
35+
index: z.number(),
36+
line: z.number(),
37+
column: z.number(),
38+
}),
39+
);
40+
41+
export const V_ParsingRange = schemaForType<ParsingRange>()(
42+
z.object({
43+
from: V_ParsingPosition,
44+
to: V_ParsingPosition,
45+
}),
46+
);
47+
48+
export const V_ParsingResultNode = schemaForType<ParsingResultNode>()(
49+
z.object({
50+
value: z.string(),
51+
position: V_ParsingRange.optional(),
52+
}),
53+
);
54+
55+
export const V_UnvalidatedFieldArgument = schemaForType<UnvalidatedFieldArgument>()(
56+
z.object({
57+
name: V_ParsingResultNode,
58+
value: V_ParsingResultNode.array(),
59+
}),
60+
);
61+
62+
export const V_UnvalidatedPropAccess = schemaForType<UnvalidatedPropAccess>()(
63+
z.object({
64+
type: z.nativeEnum(PROP_ACCESS_TYPE),
65+
prop: V_ParsingResultNode,
66+
}),
67+
);
68+
69+
export const V_UnvalidatedBindTargetDeclaration = schemaForType<UnvalidatedBindTargetDeclaration>()(
70+
z.object({
71+
storageType: V_ParsingResultNode.optional(),
72+
storagePath: V_ParsingResultNode.optional(),
73+
storageProp: V_UnvalidatedPropAccess.array(),
74+
listenToChildren: z.boolean(),
75+
}),
76+
);
77+
78+
export const V_UnvalidatedInputFieldDeclaration = schemaForType<UnvalidatedInputFieldDeclaration>()(
79+
z.object({
80+
fullDeclaration: z.string(),
81+
inputFieldType: V_ParsingResultNode.optional(),
82+
templateName: V_ParsingResultNode.optional(),
83+
bindTarget: V_UnvalidatedBindTargetDeclaration.optional(),
84+
arguments: V_UnvalidatedFieldArgument.array(),
85+
errorCollection: z.instanceof(ErrorCollection),
86+
}),
87+
);
88+
89+
export const V_UnvalidatedViewFieldDeclaration = schemaForType<UnvalidatedViewFieldDeclaration>()(
90+
z.object({
91+
fullDeclaration: z.string(),
92+
templateDeclaration: z.array(z.union([z.string(), V_UnvalidatedBindTargetDeclaration])),
93+
viewFieldType: V_ParsingResultNode.optional(),
94+
arguments: V_UnvalidatedFieldArgument.array(),
95+
writeToBindTarget: V_UnvalidatedBindTargetDeclaration.optional(),
96+
errorCollection: z.instanceof(ErrorCollection),
97+
}),
98+
);
99+
100+
export const V_BindTargetDeclaration = schemaForType<BindTargetDeclaration>()(
101+
z.object({
102+
storageType: z.nativeEnum(BindTargetStorageType),
103+
storagePath: z.string(),
104+
storageProp: z.instanceof(PropPath),
105+
listenToChildren: z.boolean(),
106+
}),
107+
);
108+
109+
export const V_Signal = schemaForType<Signal<unknown>>()(z.instanceof(Signal));
110+
export const V_VoidFunction = schemaForType<() => void>()(z.function().args().returns(z.void()));
111+
112+
export const V_ComponentLike = schemaForType<ComponentLike>()(
113+
z.object({
114+
addChild: z.function().args(z.instanceof(Component)).returns(z.void()),
115+
}),
116+
);

src/api/InputFieldAPI.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ export class InputFieldAPI {
211211
unvalidatedDeclaration: UnvalidatedInputFieldDeclaration,
212212
override: UnvalidatedInputFieldDeclaration,
213213
): UnvalidatedInputFieldDeclaration {
214-
let bindTarget = {} as UnvalidatedBindTargetDeclaration | undefined;
214+
let bindTarget: UnvalidatedBindTargetDeclaration | undefined;
215215

216216
if (unvalidatedDeclaration.bindTarget === undefined) {
217217
bindTarget = override.bindTarget;

src/parsers/nomParsers/GeneralNomParsers.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import { type ParsingRange } from '@lemons_dev/parsinom/lib/HelperTypes';
55
import { type UnvalidatedFieldArgument } from '../inputFieldParser/InputFieldDeclaration';
66

77
export const ident: Parser<string> = P.sequence(
8-
P_UTILS.unicodeLetter(),
9-
P.or(P_UTILS.unicodeAlphanumeric(), P.oneOf('-_')).many(),
8+
P.or(P_UTILS.unicodeLetter(), P.oneOf('_$')),
9+
P.or(P_UTILS.unicodeAlphanumeric(), P.oneOf('-_$')).many(),
1010
)
1111
.map(x => {
1212
return x[0] + x[1].join('');

0 commit comments

Comments
 (0)