11import type { ZodTypeAny } from 'zod'
22
33import {
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'
119
1210export const parseUrlSearchParams = (
1311 query : URLSearchParams | string ,
@@ -22,33 +20,48 @@ export const parseUrlSearchParams = (
2220 )
2321 }
2422
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 )
4143 }
4244
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 )
4454}
4555
46- const parse = ( k : string , values : string [ ] , schema : ZodTypeAny ) : unknown => {
56+ const parse = ( k : string , values : string [ ] , type : ValueType ) : unknown => {
4757 // 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 ) )
5265 throw new UnparseableSearchParamError ( k , 'unsupported type' )
5366}
5467
0 commit comments