1
1
import type { ZodTypeAny } from 'zod'
2
2
3
3
import {
4
- isZodArray ,
5
- isZodBoolean ,
6
- isZodNumber ,
7
- isZodObject ,
8
- isZodSchema ,
9
- isZodString ,
10
- } from './zod.js'
4
+ type ParamSchema ,
5
+ type ValueType ,
6
+ zodSchemaToParamSchema ,
7
+ } from './schema.js'
8
+ import { isZodObject } from './zod.js'
11
9
12
10
export const parseUrlSearchParams = (
13
11
query : URLSearchParams | string ,
@@ -22,33 +20,48 @@ export const parseUrlSearchParams = (
22
20
)
23
21
}
24
22
25
- // const paramSchema = zodSchemaToParamSchema(schema)
26
- // traverse paramSchema, and build a new object
27
- // for each node, lookup expected key in searchParams
28
- // if match, try to parse and include in object, otherwise, skip node
29
-
30
- // TODO: For array parsing, try to lookup foo=, then foo[]= patterns,
31
- // if only one match, try to detect commas, otherwise ignore commas.
32
- // if both foo= and foo[]= this is a parse error
33
-
34
- const obj : Record < string , unknown > = { }
35
- for ( const [ k , v ] of Object . entries (
36
- schema . shape as unknown as Record < string , unknown > ,
37
- ) ) {
38
- if ( searchParams . has ( k ) ) {
39
- if ( isZodSchema ( v ) ) obj [ k ] = parse ( k , searchParams . getAll ( k ) , v )
40
- }
23
+ const paramSchema = zodSchemaToParamSchema ( schema )
24
+ return parseFromParamSchema ( searchParams , paramSchema , [ ] ) as Record <
25
+ string ,
26
+ unknown
27
+ >
28
+ }
29
+
30
+ const parseFromParamSchema = (
31
+ searchParams : URLSearchParams ,
32
+ node : ParamSchema | ValueType ,
33
+ path : string [ ] ,
34
+ ) : Record < string , unknown > | unknown => {
35
+ if ( typeof node === 'string' ) {
36
+ // TODO: For array parsing, try to lookup foo=, then foo[]= patterns,
37
+ // if only one match, try to detect commas, otherwise ignore commas.
38
+ // if both foo= and foo[]= this is a parse error
39
+ // more generally, try to find a matching key for this node in the searchParams
40
+ // and throw if conflicting keys are found, e.g, both foo= and foo[]=
41
+ const key = path . join ( '.' )
42
+ return parse ( key , searchParams . getAll ( key ) , node )
41
43
}
42
44
43
- return obj
45
+ const entries = Object . entries ( node ) . reduce <
46
+ Array < [ string , Record < string , unknown > | unknown ] >
47
+ > ( ( acc , entry ) => {
48
+ const [ k , v ] = entry
49
+ const currentPath = [ ...path , k ]
50
+ return [ ...acc , [ k , parseFromParamSchema ( searchParams , v , currentPath ) ] ]
51
+ } , [ ] )
52
+
53
+ return Object . fromEntries ( entries )
44
54
}
45
55
46
- const parse = ( k : string , values : string [ ] , schema : ZodTypeAny ) : unknown => {
56
+ const parse = ( k : string , values : string [ ] , type : ValueType ) : unknown => {
47
57
// TODO: Add better errors with coercion. If coercion fails, passthough?
48
- if ( isZodNumber ( schema ) ) return Number ( values [ 0 ] )
49
- if ( isZodBoolean ( schema ) ) return values [ 0 ] === 'true'
50
- if ( isZodString ( schema ) ) return String ( values [ 0 ] )
51
- if ( isZodArray ( schema ) ) return values
58
+ // TODO: Is this Number parsing safe?
59
+ if ( values . length === 0 ) return undefined
60
+ if ( type === 'number' ) return Number ( values [ 0 ] )
61
+ if ( type === 'boolean' ) return values [ 0 ] === 'true'
62
+ if ( type === 'string' ) return String ( values [ 0 ] )
63
+ if ( type === 'string_array' ) return values
64
+ if ( type === 'number_array' ) return values . map ( ( v ) => Number ( v ) )
52
65
throw new UnparseableSearchParamError ( k , 'unsupported type' )
53
66
}
54
67
0 commit comments