@@ -6,23 +6,12 @@ import { GroupVariant, type Query, ShowMetadataVariant, SortingVariant } from "@
66
77type 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-
209export 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
3939export 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
145142const 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