@@ -20,9 +20,61 @@ const toKebabCase = (str: string): string => {
2020 * @param {z.ZodTypeAny } schema The Zod schema to convert
2121 * @returns {z.ZodTypeAny } The converted Zod schema
2222 */
23- const preprocessCamelCase = < T extends z . ZodTypeAny > ( schema : T ) : z . ZodTypeAny => {
24- return z . preprocess ( ( val : Record < string , unknown > ) => camelcaseKeys ( val , { deep : true } ) , schema )
25- }
23+ const preprocessCamelCase = < T extends z . ZodTypeAny > ( schema : T ) : z . ZodTypeAny =>
24+ z . preprocess ( ( val : any ) => {
25+ if ( ! val || typeof val !== 'object' ) return val
26+
27+ // eslint-disable-next-line jsdoc/require-jsdoc
28+ function deepCloneJson < V > ( v : V ) : V {
29+ return JSON . parse ( JSON . stringify ( v ) )
30+ }
31+
32+ // deep-clone to avoid mutating original input (JSON clone is fine for config objects)
33+ const clone = deepCloneJson ( val )
34+
35+ // collect paths and values of all headers objects
36+ // eslint-disable-next-line jsdoc/require-jsdoc
37+ const headerEntries : Array < { path : ( string | number ) [ ] ; value : any } > = [ ]
38+
39+ const extract = ( obj : any , path : ( string | number ) [ ] ) => {
40+ if ( ! obj || typeof obj !== 'object' ) return
41+ if ( Array . isArray ( obj ) ) {
42+ obj . forEach ( ( it , i ) => extract ( it , path . concat ( i ) ) )
43+ return
44+ }
45+ for ( const [ k , v ] of Object . entries ( obj ) ) {
46+ if ( k === 'headers' && v && typeof v === 'object' && ! Array . isArray ( v ) ) {
47+ headerEntries . push ( { path : path . concat ( k ) , value : v } )
48+ delete obj [ k ]
49+ } else {
50+ extract ( v , path . concat ( k ) )
51+ }
52+ }
53+ }
54+
55+ extract ( clone , [ ] )
56+
57+ // camelcase everything else deeply
58+ const camelized = camelcaseKeys ( clone , { deep : true } )
59+
60+ // helper to set by path
61+ const setByPath = ( root : any , path : ( string | number ) [ ] , value : any ) => {
62+ let cur = root
63+ for ( let i = 0 ; i < path . length - 1 ; i ++ ) {
64+ const p = path [ i ]
65+ if ( cur [ p ] === undefined ) cur [ p ] = typeof path [ i + 1 ] === 'number' ? [ ] : { }
66+ cur = cur [ p ]
67+ }
68+ cur [ path [ path . length - 1 ] ] = value
69+ }
70+
71+ // restore headers at same paths
72+ for ( const { path, value } of headerEntries ) {
73+ setByPath ( camelized , path , value )
74+ }
75+
76+ return camelized
77+ } , schema )
2678
2779// Zod schemas for external API responses
2880const ServiceMetadataSchema = z
0 commit comments