Skip to content

Commit d990492

Browse files
committed
Implement basic zodSchemaToParamSchema
1 parent 4e39fd8 commit d990492

File tree

3 files changed

+97
-5
lines changed

3 files changed

+97
-5
lines changed

src/lib/parse.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ export const parseUrlSearchParams = (
2222
)
2323
}
2424

25-
// TODO:
2625
// const paramSchema = zodSchemaToParamSchema(schema)
2726
// traverse paramSchema, and build a new object
2827
// for each node, lookup expected key in searchParams

src/lib/schema.test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import test from 'ava'
2+
import { z } from 'zod'
3+
4+
import { UnparseableSchemaError, zodSchemaToParamSchema } from './schema.js'
5+
6+
test('parse flat object schemas', (t) => {
7+
t.deepEqual(zodSchemaToParamSchema(z.object({ foo: z.string() })), {
8+
foo: 'string',
9+
})
10+
t.deepEqual(
11+
zodSchemaToParamSchema(
12+
z.object({
13+
a: z.string(),
14+
b: z.number(),
15+
c: z.boolean(),
16+
d: z.array(z.string()),
17+
}),
18+
),
19+
{
20+
a: 'string',
21+
b: 'number',
22+
c: 'boolean',
23+
d: 'string_array',
24+
},
25+
)
26+
})
27+
28+
test('cannot parse non-object schemas', (t) => {
29+
t.throws(() => zodSchemaToParamSchema(z.number()), {
30+
instanceOf: UnparseableSchemaError,
31+
})
32+
t.throws(() => zodSchemaToParamSchema(z.enum(['foo'])), {
33+
instanceOf: UnparseableSchemaError,
34+
})
35+
t.throws(() => zodSchemaToParamSchema(z.string()), {
36+
instanceOf: UnparseableSchemaError,
37+
})
38+
t.throws(() => zodSchemaToParamSchema(z.map(z.string(), z.string())), {
39+
instanceOf: UnparseableSchemaError,
40+
})
41+
t.throws(() => zodSchemaToParamSchema(z.array(z.string())), {
42+
instanceOf: UnparseableSchemaError,
43+
})
44+
t.throws(() => zodSchemaToParamSchema(z.null()), {
45+
instanceOf: UnparseableSchemaError,
46+
})
47+
t.throws(() => zodSchemaToParamSchema(z.union([z.number(), z.string()])), {
48+
instanceOf: UnparseableSchemaError,
49+
})
50+
})

src/lib/schema.ts

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,14 @@
66

77
import type { ZodTypeAny } from 'zod'
88

9-
import { isZodObject } from './zod.js'
9+
import {
10+
isZodArray,
11+
isZodBoolean,
12+
isZodNumber,
13+
isZodObject,
14+
isZodSchema,
15+
isZodString,
16+
} from './zod.js'
1017

1118
type ValueType =
1219
| 'string'
@@ -23,15 +30,51 @@ interface ParamSchema {
2330
export const zodSchemaToParamSchema = (schema: ZodTypeAny): ParamSchema => {
2431
if (!isZodObject(schema)) {
2532
throw new UnparseableSchemaError(
33+
[],
2634
'top level schema must be an object schema',
2735
)
2836
}
29-
return {}
37+
const paramSchema = nestedZodSchemaToParamSchema(schema, [])
38+
if (typeof paramSchema === 'string') {
39+
throw new Error('Expected ParamSchema not ValueType')
40+
}
41+
return paramSchema
42+
}
43+
44+
const nestedZodSchemaToParamSchema = (
45+
schema: ZodTypeAny,
46+
path: string[],
47+
): ParamSchema | ValueType => {
48+
if (isZodObject(schema)) {
49+
const shape = schema.shape as unknown as Record<string, unknown>
50+
const entries = Object.entries(shape).reduce<
51+
Array<[string, ParamSchema | ValueType]>
52+
>((acc, entry) => {
53+
const [k, v] = entry
54+
const currentPath = [...path, k]
55+
if (isZodSchema(v)) {
56+
return [...acc, [k, nestedZodSchemaToParamSchema(v, currentPath)]]
57+
}
58+
throw new UnparseableSchemaError(currentPath, 'unexpected non-zod schema')
59+
}, [])
60+
return Object.fromEntries(entries)
61+
}
62+
63+
if (isZodNumber(schema)) return 'number'
64+
if (isZodString(schema)) return 'string'
65+
if (isZodBoolean(schema)) return 'boolean'
66+
if (isZodArray(schema)) {
67+
// TODO: handle number_array
68+
return 'string_array'
69+
}
70+
71+
throw new UnparseableSchemaError(path, `schema type is not supported`)
3072
}
3173

3274
export class UnparseableSchemaError extends Error {
33-
constructor(message: string) {
34-
super(`Could not parse Zod schema: ${message}`)
75+
constructor(path: string[], message: string) {
76+
const part = path.length === 0 ? '' : ` at ${path.join('.')}`
77+
super(`Could not parse Zod schema${part}: ${message}`)
3578
this.name = this.constructor.name
3679
}
3780
}

0 commit comments

Comments
 (0)