Skip to content

Commit 8db593d

Browse files
committed
plugin: refactor query parser internals
1 parent 07653e4 commit 8db593d

File tree

1 file changed

+73
-58
lines changed

1 file changed

+73
-58
lines changed

plugin/src/query/parser.ts

Lines changed: 73 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,12 @@ import { GroupVariant, type Query, ShowMetadataVariant, SortingVariant } from "@
66

77
type ErrorTree = string | { msg: string; children: ErrorTree[] };
88

9-
function formatErrorTree(tree: ErrorTree, indent = ""): string {
10-
if (typeof tree === "string") {
11-
return `${indent}${tree}`;
12-
}
13-
const lines = [`${indent}${tree.msg}`];
14-
for (const child of tree.children) {
15-
lines.push(formatErrorTree(child, `${indent} `));
16-
}
17-
return lines.join("\n");
18-
}
19-
209
export class ParsingError extends Error {
2110
messages: ErrorTree[];
2211
inner: unknown | undefined;
2312

2413
constructor(msgs: ErrorTree[], inner: unknown | undefined = undefined) {
25-
super(msgs.map((tree) => formatErrorTree(tree)).join("\n"));
14+
super(msgs.map((tree) => ParsingError.formatErrorTree(tree)).join("\n"));
2615
this.inner = inner;
2716
this.messages = msgs;
2817
}
@@ -34,6 +23,17 @@ export class ParsingError extends Error {
3423

3524
return super.toString();
3625
}
26+
27+
private static formatErrorTree(tree: ErrorTree, indent = ""): string {
28+
if (typeof tree === "string") {
29+
return `${indent}${tree}`;
30+
}
31+
const lines = [`${indent}${tree.msg}`];
32+
for (const child of tree.children) {
33+
lines.push(ParsingError.formatErrorTree(child, `${indent} `));
34+
}
35+
return lines.join("\n");
36+
}
3737
}
3838

3939
export type QueryWarning = string;
@@ -120,66 +120,86 @@ const groupBySchema = lookupToEnum({
120120
labels: GroupVariant.Label,
121121
});
122122

123-
const viewSchema = z
124-
.object({
125-
noTasksMessage: z.string().optional(),
126-
})
127-
.optional()
128-
.default({});
123+
const viewSchema = z.object({
124+
noTasksMessage: z.string().optional(),
125+
});
129126

130-
const defaults = {
127+
const defaultQuery = (): Omit<Query, "filter"> => ({
131128
name: "",
132129
autorefresh: 0,
133130
sorting: [SortingVariant.Order],
134-
show: [
131+
show: new Set([
135132
ShowMetadataVariant.Due,
136133
ShowMetadataVariant.Description,
137134
ShowMetadataVariant.Labels,
138135
ShowMetadataVariant.Project,
139136
ShowMetadataVariant.Deadline,
140-
],
137+
]),
141138
groupBy: GroupVariant.None,
142139
view: {},
143-
};
140+
});
144141

145142
const querySchema = z.object({
146-
name: z.string().optional().default(""),
143+
name: z.string().optional(),
147144
filter: z.string(),
148-
autorefresh: z.number().nonnegative().optional().default(0),
149-
sorting: z
150-
.array(sortingSchema)
151-
.optional()
152-
.transform((val) => val ?? defaults.sorting),
145+
autorefresh: z.number().nonnegative().optional(),
146+
sorting: z.array(sortingSchema).optional(),
153147
show: z
154-
.union([z.array(showSchema), z.literal("none").transform(() => [])])
155-
.optional()
156-
.transform((val) => val ?? defaults.show),
157-
groupBy: groupBySchema.optional().transform((val) => val ?? defaults.groupBy),
158-
view: viewSchema,
148+
.union([
149+
z.array(showSchema).transform((arr) => new Set(arr)),
150+
z.literal("none").transform(() => new Set([])),
151+
])
152+
.optional(),
153+
groupBy: groupBySchema.optional(),
154+
view: viewSchema.optional(),
159155
});
160156

161-
const validQueryKeys: string[] = querySchema.keyof().options;
162-
const validNestedKeys: Record<string, string[]> = {
163-
view: ["noTasksMessage"],
164-
};
157+
function findUnknownKeys(obj: Record<string, unknown>, schema: z.ZodObject): string[] {
158+
const keys: string[] = [];
165159

166-
function parseObjectZod(query: Record<string, unknown>): [Query, QueryWarning[]] {
167-
const warnings: QueryWarning[] = [];
160+
const validKeys = schema.keyof().options;
161+
162+
for (const key of Object.keys(obj)) {
163+
if (!validKeys.includes(key)) {
164+
keys.push(key);
165+
continue;
166+
}
167+
168+
const value = obj[key];
169+
if (typeof value !== "object" || value === null) {
170+
continue;
171+
}
172+
173+
let childSchema: z.ZodObject | undefined;
174+
const schemaField = schema.shape[key];
168175

169-
for (const key of Object.keys(query)) {
170-
if (!validQueryKeys.includes(key)) {
171-
warnings.push(t().query.warning.unknownKey(key));
172-
} else if (validNestedKeys[key]) {
173-
// Validate nested keys
174-
const nestedObj = query[key];
175-
if (typeof nestedObj === "object" && nestedObj !== null) {
176-
for (const nestedKey of Object.keys(nestedObj)) {
177-
if (!validNestedKeys[key].includes(nestedKey)) {
178-
warnings.push(t().query.warning.unknownKey(`${key}.${nestedKey}`));
179-
}
180-
}
176+
// If the subobject is directly a ZodObject, use that.
177+
if (schemaField instanceof z.ZodObject) {
178+
childSchema = schemaField;
179+
}
180+
181+
// If the subobject is optional, unwrap it and use the type.
182+
if (schemaField instanceof z.ZodOptional) {
183+
const unwrapped = schemaField.unwrap();
184+
if (unwrapped instanceof z.ZodObject) {
185+
childSchema = unwrapped;
181186
}
182187
}
188+
189+
if (childSchema !== undefined) {
190+
const nestedKeys = findUnknownKeys(value as Record<string, unknown>, childSchema);
191+
keys.push(...nestedKeys.map((w) => `${key}.${w}`));
192+
}
193+
}
194+
195+
return keys;
196+
}
197+
198+
function parseObjectZod(query: Record<string, unknown>): [Query, QueryWarning[]] {
199+
const warnings: QueryWarning[] = [];
200+
201+
for (const key of findUnknownKeys(query, querySchema)) {
202+
warnings.push(t().query.warning.unknownKey(key));
183203
}
184204

185205
const out = querySchema.safeParse(query);
@@ -200,13 +220,8 @@ function parseObjectZod(query: Record<string, unknown>): [Query, QueryWarning[]]
200220

201221
return [
202222
{
203-
name: out.data.name,
204-
filter: out.data.filter,
205-
autorefresh: out.data.autorefresh,
206-
sorting: out.data.sorting,
207-
show,
208-
groupBy: out.data.groupBy,
209-
view: out.data.view,
223+
...defaultQuery(),
224+
...out.data,
210225
},
211226
warnings,
212227
];

0 commit comments

Comments
 (0)