@@ -10,6 +10,28 @@ import {
1010 InvalidQueryParamsError ,
1111} from "./http-exceptions.js"
1212
13+ /**
14+ * Get the type identifier from a Zod schema definition.
15+ * Supports both Zod 3 (_def.typeName) and Zod 4 (_def.type).
16+ */
17+ const getZodTypeIdentifier = ( def : any ) : string | undefined => {
18+ // Zod 4 uses _def.type (lowercase string like "string", "boolean", "optional")
19+ // Zod 3 uses _def.typeName (ZodFirstPartyTypeKind enum values)
20+ return def . type ?? def . typeName
21+ }
22+
23+ /**
24+ * Check if a type identifier matches a specific Zod type.
25+ * Supports both Zod 3 (ZodFirstPartyTypeKind) and Zod 4 (lowercase strings).
26+ */
27+ const isZodType = (
28+ typeIdentifier : string | undefined ,
29+ zod3Kind : ( typeof ZodFirstPartyTypeKind ) [ keyof typeof ZodFirstPartyTypeKind ] ,
30+ zod4Type : string
31+ ) : boolean => {
32+ return typeIdentifier === zod3Kind || typeIdentifier === zod4Type
33+ }
34+
1335const getZodObjectSchemaFromZodEffectSchema = (
1436 isZodEffect : boolean ,
1537 schema : z . ZodTypeAny
@@ -18,50 +40,75 @@ const getZodObjectSchemaFromZodEffectSchema = (
1840 return schema as z . ZodObject < any >
1941 }
2042
21- let currentSchema = schema
22-
23- while ( currentSchema instanceof z . ZodEffects ) {
24- currentSchema = currentSchema . _def . schema
43+ let currentSchema = schema as any
44+
45+ // Handle both Zod 3 (ZodEffects with _def.schema) and Zod 4 (pipe with _def.in)
46+ while (
47+ currentSchema instanceof z . ZodEffects ||
48+ getZodTypeIdentifier ( currentSchema . _def ) === "pipe"
49+ ) {
50+ if ( currentSchema instanceof z . ZodEffects ) {
51+ currentSchema = currentSchema . _def . schema
52+ } else if ( currentSchema . _def . in ) {
53+ // Zod 4 pipe structure
54+ currentSchema = currentSchema . _def . in
55+ } else {
56+ break
57+ }
2558 }
2659
2760 return currentSchema as z . ZodObject < any >
2861}
2962
3063/**
3164 * This function is used to get the correct schema from a ZodEffect | ZodDefault | ZodOptional schema.
32- * TODO: this function should handle all special cases of ZodSchema and not just ZodEffect | ZodDefault | ZodOptional
65+ * Supports both Zod 3 and Zod 4 internal structures.
3366 */
3467const getZodDefFromZodSchemaHelpers = ( schema : z . ZodTypeAny ) => {
35- const special_zod_types = [
36- ZodFirstPartyTypeKind . ZodOptional ,
37- ZodFirstPartyTypeKind . ZodDefault ,
38- ZodFirstPartyTypeKind . ZodEffects ,
39- ]
40-
41- while ( special_zod_types . includes ( schema . _def . typeName ) ) {
68+ let currentSchema = schema as any
69+ let typeId = getZodTypeIdentifier ( currentSchema . _def )
70+
71+ // Keep unwrapping optional, default, effects/pipe until we get to the base type
72+ while (
73+ isZodType ( typeId , ZodFirstPartyTypeKind . ZodOptional , "optional" ) ||
74+ isZodType ( typeId , ZodFirstPartyTypeKind . ZodDefault , "default" ) ||
75+ isZodType ( typeId , ZodFirstPartyTypeKind . ZodEffects , "pipe" )
76+ ) {
4277 if (
43- schema . _def . typeName === ZodFirstPartyTypeKind . ZodOptional ||
44- schema . _def . typeName === ZodFirstPartyTypeKind . ZodDefault
78+ isZodType ( typeId , ZodFirstPartyTypeKind . ZodOptional , "optional" ) ||
79+ isZodType ( typeId , ZodFirstPartyTypeKind . ZodDefault , "default" )
4580 ) {
46- schema = schema . _def . innerType
47- continue
81+ currentSchema = currentSchema . _def . innerType
82+ } else if ( isZodType ( typeId , ZodFirstPartyTypeKind . ZodEffects , "pipe" ) ) {
83+ // Zod 3 uses _def.schema, Zod 4 uses _def.in
84+ currentSchema = currentSchema . _def . schema ?? currentSchema . _def . in
4885 }
4986
50- if ( schema . _def . typeName === ZodFirstPartyTypeKind . ZodEffects ) {
51- schema = schema . _def . schema
52- continue
87+ if ( ! currentSchema ?. _def ) {
88+ break
5389 }
90+ typeId = getZodTypeIdentifier ( currentSchema . _def )
5491 }
55- return schema . _def
92+
93+ return currentSchema . _def
5694}
5795
5896const tryGetZodSchemaAsObject = (
5997 schema : z . ZodTypeAny
6098) : z . ZodObject < any > | undefined => {
61- const isZodEffect = schema . _def . typeName === ZodFirstPartyTypeKind . ZodEffects
99+ const typeId = getZodTypeIdentifier ( schema . _def )
100+ const isZodEffect = isZodType (
101+ typeId ,
102+ ZodFirstPartyTypeKind . ZodEffects ,
103+ "pipe"
104+ )
62105 const safe_schema = getZodObjectSchemaFromZodEffectSchema ( isZodEffect , schema )
63- const isZodObject =
64- safe_schema . _def . typeName === ZodFirstPartyTypeKind . ZodObject
106+ const safeTypeId = getZodTypeIdentifier ( safe_schema . _def )
107+ const isZodObject = isZodType (
108+ safeTypeId ,
109+ ZodFirstPartyTypeKind . ZodObject ,
110+ "object"
111+ )
65112
66113 if ( ! isZodObject ) {
67114 return undefined
@@ -72,12 +119,14 @@ const tryGetZodSchemaAsObject = (
72119
73120const isZodSchemaArray = ( schema : z . ZodTypeAny ) => {
74121 const def = getZodDefFromZodSchemaHelpers ( schema )
75- return def . typeName === ZodFirstPartyTypeKind . ZodArray
122+ const typeId = getZodTypeIdentifier ( def )
123+ return isZodType ( typeId , ZodFirstPartyTypeKind . ZodArray , "array" )
76124}
77125
78126const isZodSchemaBoolean = ( schema : z . ZodTypeAny ) => {
79127 const def = getZodDefFromZodSchemaHelpers ( schema )
80- return def . typeName === ZodFirstPartyTypeKind . ZodBoolean
128+ const typeId = getZodTypeIdentifier ( def )
129+ return isZodType ( typeId , ZodFirstPartyTypeKind . ZodBoolean , "boolean" )
81130}
82131
83132const parseQueryParams = (
0 commit comments