@@ -174,18 +174,83 @@ func cast(v string) any {
174174 return v
175175}
176176
177+ // getPropertySchema looks up a property's schema from an object schema's Properties map.
178+ // Returns nil if objectSchema is nil, has no Properties, or the property is not found.
179+ func getPropertySchema (objectSchema * base.Schema , propertyName string ) * base.Schema {
180+ if objectSchema == nil || objectSchema .Properties == nil {
181+ return nil
182+ }
183+ proxy := objectSchema .Properties .GetOrZero (propertyName )
184+ if proxy == nil {
185+ return nil
186+ }
187+ return proxy .Schema ()
188+ }
189+
190+ // castWithSchema casts a string value consulting the schema for the property's declared type.
191+ // If the schema says the property is a string, the value is returned as-is (no numeric/bool guessing).
192+ // For other declared types, it falls back to cast() which produces correct results for integer,
193+ // number, and boolean values. The explicit string check prevents the most common miscast: numeric-
194+ // looking strings like "10" being converted to int64 when the schema declares type: string.
195+ func castWithSchema (v string , objectSchema * base.Schema , propertyName string ) any {
196+ propSchema := getPropertySchema (objectSchema , propertyName )
197+ if propSchema != nil {
198+ for _ , t := range propSchema .Type {
199+ if t == String {
200+ return v
201+ }
202+ }
203+ }
204+ return cast (v )
205+ }
206+
207+ // constructKVFromDelimited is the shared implementation for constructing key=value maps
208+ // from delimited strings (comma, period, semicolon). The delimiter determines how to split
209+ // entries, and each entry is further split on '=' to extract key-value pairs.
210+ func constructKVFromDelimited (values string , delimiter string , sch * base.Schema ) map [string ]interface {} {
211+ props := make (map [string ]interface {})
212+ exploded := strings .Split (values , delimiter )
213+ for i := range exploded {
214+ obK := strings .Split (exploded [i ], Equals )
215+ if len (obK ) == 2 {
216+ props [obK [0 ]] = castWithSchema (obK [1 ], sch , obK [0 ])
217+ }
218+ }
219+ return props
220+ }
221+
222+ // constructParamMapFromDelimitedEncoding is the shared implementation for constructing
223+ // parameter maps from pipe-delimited or space-delimited query parameter values.
224+ // Entries alternate between keys and values (key|value|key|value or key value key value).
225+ func constructParamMapFromDelimitedEncoding (values []* QueryParam , delimiter string , sch * base.Schema ) map [string ]interface {} {
226+ decoded := make (map [string ]interface {})
227+ for _ , v := range values {
228+ props := make (map [string ]interface {})
229+ exploded := strings .Split (v .Values [0 ], delimiter )
230+ for i := range exploded {
231+ if i % 2 == 0 && i + 1 < len (exploded ) {
232+ props [exploded [i ]] = castWithSchema (exploded [i + 1 ], sch , exploded [i ])
233+ }
234+ }
235+ decoded [v .Key ] = props
236+ }
237+ return decoded
238+ }
239+
177240// ConstructParamMapFromDeepObjectEncoding will construct a map from the query parameters that are encoded as
178241// deep objects. It's kind of a crazy way to do things, but hey, each to their own.
179242func ConstructParamMapFromDeepObjectEncoding (values []* QueryParam , sch * base.Schema ) map [string ]interface {} {
180- // deepObject encoding is a technique used to encode objects into query parameters. Kinda nuts.
181243 decoded := make (map [string ]interface {})
182244 for _ , v := range values {
183- if decoded [v .Key ] == nil {
245+ castForProp := func (val string ) any {
246+ return castWithSchema (val , sch , v .Property )
247+ }
184248
249+ if decoded [v .Key ] == nil {
185250 props := make (map [string ]interface {})
186251 rawValues := make ([]interface {}, len (v .Values ))
187252 for i := range v .Values {
188- rawValues [i ] = cast (v .Values [i ])
253+ rawValues [i ] = castForProp (v .Values [i ])
189254 }
190255 // check if the schema for the param is an array
191256 if sch != nil && slices .Contains (sch .Type , Array ) {
@@ -202,15 +267,14 @@ func ConstructParamMapFromDeepObjectEncoding(values []*QueryParam, sch *base.Sch
202267 }
203268
204269 if len (props ) == 0 {
205- props [v .Property ] = cast (v .Values [0 ])
270+ props [v .Property ] = castForProp (v .Values [0 ])
206271 }
207272 decoded [v .Key ] = props
208273 } else {
209-
210274 added := false
211275 rawValues := make ([]interface {}, len (v .Values ))
212276 for i := range v .Values {
213- rawValues [i ] = cast (v .Values [i ])
277+ rawValues [i ] = castForProp (v .Values [i ])
214278 }
215279 // check if the schema for the param is an array
216280 if sch != nil && slices .Contains (sch .Type , Array ) {
@@ -225,138 +289,70 @@ func ConstructParamMapFromDeepObjectEncoding(values []*QueryParam, sch *base.Sch
225289 added = true
226290 }
227291 if ! added {
228- decoded [v .Key ].(map [string ]interface {})[v .Property ] = cast (v .Values [0 ])
292+ decoded [v .Key ].(map [string ]interface {})[v .Property ] = castForProp (v .Values [0 ])
229293 }
230-
231294 }
232295 }
233296 return decoded
234297}
235298
236299// ConstructParamMapFromQueryParamInput will construct a param map from an existing map of *QueryParam slices.
300+ //
301+ // Deprecated: use ConstructParamMapFromQueryParamInputWithSchema instead.
237302func ConstructParamMapFromQueryParamInput (values map [string ][]* QueryParam ) map [string ]interface {} {
238- decoded := make (map [string ]interface {})
239- for _ , q := range values {
240- for _ , v := range q {
241- decoded [v .Key ] = cast (v .Values [0 ])
242- }
243- }
244- return decoded
303+ return ConstructParamMapFromQueryParamInputWithSchema (values , nil )
245304}
246305
247306// ConstructParamMapFromPipeEncoding will construct a map from the query parameters that are encoded as
248- // pipe separated values. Perhaps the most sane way to delimit/encode properties.
307+ // pipe separated values.
308+ //
309+ // Deprecated: use ConstructParamMapFromPipeEncodingWithSchema instead.
249310func ConstructParamMapFromPipeEncoding (values []* QueryParam ) map [string ]interface {} {
250- // Pipes are always a good alternative to commas, personally I think they're better, if I were encoding, I would
251- // use pipes instead of commas, so much can go wrong with a comma, but a pipe? hardly ever.
252- decoded := make (map [string ]interface {})
253- for _ , v := range values {
254- props := make (map [string ]interface {})
255- // explode PSV into array
256- exploded := strings .Split (v .Values [0 ], Pipe )
257- for i := range exploded {
258- if i % 2 == 0 {
259- props [exploded [i ]] = cast (exploded [i + 1 ])
260- }
261- }
262- decoded [v .Key ] = props
263- }
264- return decoded
311+ return ConstructParamMapFromPipeEncodingWithSchema (values , nil )
265312}
266313
267314// ConstructParamMapFromSpaceEncoding will construct a map from the query parameters that are encoded as
268- // space delimited values. This is perhaps the worst way to delimit anything other than a paragraph of text.
315+ // space delimited values.
316+ //
317+ // Deprecated: use ConstructParamMapFromSpaceEncodingWithSchema instead.
269318func ConstructParamMapFromSpaceEncoding (values []* QueryParam ) map [string ]interface {} {
270- // Don't use spaces to delimit anything unless you really know what the hell you're doing. Perhaps the
271- // easiest way to blow something up, unless you're tokenizing strings... don't do this.
272- decoded := make (map [string ]interface {})
273- for _ , v := range values {
274- props := make (map [string ]interface {})
275- // explode SSV into array
276- exploded := strings .Split (v .Values [0 ], Space )
277- for i := range exploded {
278- if i % 2 == 0 {
279- props [exploded [i ]] = cast (exploded [i + 1 ])
280- }
281- }
282- decoded [v .Key ] = props
283- }
284- return decoded
319+ return ConstructParamMapFromSpaceEncodingWithSchema (values , nil )
285320}
286321
287322// ConstructMapFromCSV will construct a map from a comma separated value string.
323+ //
324+ // Deprecated: use ConstructMapFromCSVWithSchema instead.
288325func ConstructMapFromCSV (csv string ) map [string ]interface {} {
289- decoded := make (map [string ]interface {})
290- // explode SSV into array
291- exploded := strings .Split (csv , Comma )
292- for i := range exploded {
293- if i % 2 == 0 {
294- if len (exploded ) == i + 1 {
295- break
296- }
297- decoded [exploded [i ]] = cast (exploded [i + 1 ])
298- }
299- }
300- return decoded
326+ return ConstructMapFromCSVWithSchema (csv , nil )
301327}
302328
303329// ConstructKVFromCSV will construct a map from a comma separated value string that denotes key value pairs.
330+ //
331+ // Deprecated: use ConstructKVFromCSVWithSchema instead.
304332func ConstructKVFromCSV (values string ) map [string ]interface {} {
305- props := make (map [string ]interface {})
306- exploded := strings .Split (values , Comma )
307- for i := range exploded {
308- obK := strings .Split (exploded [i ], Equals )
309- if len (obK ) == 2 {
310- props [obK [0 ]] = cast (obK [1 ])
311- }
312- }
313- return props
333+ return ConstructKVFromCSVWithSchema (values , nil )
314334}
315335
316- // ConstructKVFromLabelEncoding will construct a map from a comma separated value string that denotes key value pairs.
336+ // ConstructKVFromLabelEncoding will construct a map from a period separated value string that denotes key value pairs.
337+ //
338+ // Deprecated: use ConstructKVFromLabelEncodingWithSchema instead.
317339func ConstructKVFromLabelEncoding (values string ) map [string ]interface {} {
318- props := make (map [string ]interface {})
319- exploded := strings .Split (values , Period )
320- for i := range exploded {
321- obK := strings .Split (exploded [i ], Equals )
322- if len (obK ) == 2 {
323- props [obK [0 ]] = cast (obK [1 ])
324- }
325- }
326- return props
340+ return ConstructKVFromLabelEncodingWithSchema (values , nil )
327341}
328342
329- // ConstructKVFromMatrixCSV will construct a map from a comma separated value string that denotes key value pairs.
343+ // ConstructKVFromMatrixCSV will construct a map from a semicolon separated value string that denotes key value pairs.
344+ //
345+ // Deprecated: use ConstructKVFromMatrixCSVWithSchema instead.
330346func ConstructKVFromMatrixCSV (values string ) map [string ]interface {} {
331- props := make (map [string ]interface {})
332- exploded := strings .Split (values , SemiColon )
333- for i := range exploded {
334- obK := strings .Split (exploded [i ], Equals )
335- if len (obK ) == 2 {
336- props [obK [0 ]] = cast (obK [1 ])
337- }
338- }
339- return props
347+ return ConstructKVFromMatrixCSVWithSchema (values , nil )
340348}
341349
342350// ConstructParamMapFromFormEncodingArray will construct a map from the query parameters that are encoded as
343351// form encoded values.
352+ //
353+ // Deprecated: use ConstructParamMapFromFormEncodingArrayWithSchema instead.
344354func ConstructParamMapFromFormEncodingArray (values []* QueryParam ) map [string ]interface {} {
345- decoded := make (map [string ]interface {})
346- for _ , v := range values {
347- props := make (map [string ]interface {})
348- // explode SSV into array
349- exploded := strings .Split (v .Values [0 ], Comma )
350- for i := range exploded {
351- if i % 2 == 0 {
352- if len (exploded ) > i + 1 {
353- props [exploded [i ]] = cast (exploded [i + 1 ])
354- }
355- }
356- }
357- decoded [v .Key ] = props
358- }
359- return decoded
355+ return ConstructParamMapFromFormEncodingArrayWithSchema (values , nil )
360356}
361357
362358// DoesFormParamContainDelimiter will determine if a form parameter contains a delimiter.
@@ -391,3 +387,80 @@ func CollapseCSVIntoSpaceDelimitedStyle(key string, values []string) string {
391387func CollapseCSVIntoPipeDelimitedStyle (key string , values []string ) string {
392388 return fmt .Sprintf ("%s=%s" , key , strings .Join (values , Pipe ))
393389}
390+
391+ // ConstructParamMapFromQueryParamInputWithSchema constructs a param map from an existing map of
392+ // *QueryParam slices, using the object schema to determine property types before casting.
393+ func ConstructParamMapFromQueryParamInputWithSchema (values map [string ][]* QueryParam , sch * base.Schema ) map [string ]interface {} {
394+ decoded := make (map [string ]interface {})
395+ for _ , q := range values {
396+ for _ , v := range q {
397+ decoded [v .Key ] = castWithSchema (v .Values [0 ], sch , v .Key )
398+ }
399+ }
400+ return decoded
401+ }
402+
403+ // ConstructParamMapFromPipeEncodingWithSchema constructs a map from pipe-delimited query parameters,
404+ // using the object schema to determine property types before casting.
405+ func ConstructParamMapFromPipeEncodingWithSchema (values []* QueryParam , sch * base.Schema ) map [string ]interface {} {
406+ return constructParamMapFromDelimitedEncoding (values , Pipe , sch )
407+ }
408+
409+ // ConstructParamMapFromSpaceEncodingWithSchema constructs a map from space-delimited query parameters,
410+ // using the object schema to determine property types before casting.
411+ func ConstructParamMapFromSpaceEncodingWithSchema (values []* QueryParam , sch * base.Schema ) map [string ]interface {} {
412+ return constructParamMapFromDelimitedEncoding (values , Space , sch )
413+ }
414+
415+ // ConstructMapFromCSVWithSchema constructs a map from a comma separated value string,
416+ // using the object schema to determine property types before casting.
417+ func ConstructMapFromCSVWithSchema (csv string , sch * base.Schema ) map [string ]interface {} {
418+ decoded := make (map [string ]interface {})
419+ exploded := strings .Split (csv , Comma )
420+ for i := range exploded {
421+ if i % 2 == 0 {
422+ if len (exploded ) == i + 1 {
423+ break
424+ }
425+ decoded [exploded [i ]] = castWithSchema (exploded [i + 1 ], sch , exploded [i ])
426+ }
427+ }
428+ return decoded
429+ }
430+
431+ // ConstructKVFromCSVWithSchema constructs a map from a comma-separated key=value string,
432+ // using the object schema to determine property types before casting.
433+ func ConstructKVFromCSVWithSchema (values string , sch * base.Schema ) map [string ]interface {} {
434+ return constructKVFromDelimited (values , Comma , sch )
435+ }
436+
437+ // ConstructKVFromLabelEncodingWithSchema constructs a map from a period-separated key=value string,
438+ // using the object schema to determine property types before casting.
439+ func ConstructKVFromLabelEncodingWithSchema (values string , sch * base.Schema ) map [string ]interface {} {
440+ return constructKVFromDelimited (values , Period , sch )
441+ }
442+
443+ // ConstructKVFromMatrixCSVWithSchema constructs a map from a semicolon-separated key=value string,
444+ // using the object schema to determine property types before casting.
445+ func ConstructKVFromMatrixCSVWithSchema (values string , sch * base.Schema ) map [string ]interface {} {
446+ return constructKVFromDelimited (values , SemiColon , sch )
447+ }
448+
449+ // ConstructParamMapFromFormEncodingArrayWithSchema constructs a map from form-encoded query parameters,
450+ // using the object schema to determine property types before casting.
451+ func ConstructParamMapFromFormEncodingArrayWithSchema (values []* QueryParam , sch * base.Schema ) map [string ]interface {} {
452+ decoded := make (map [string ]interface {})
453+ for _ , v := range values {
454+ props := make (map [string ]interface {})
455+ exploded := strings .Split (v .Values [0 ], Comma )
456+ for i := range exploded {
457+ if i % 2 == 0 {
458+ if len (exploded ) > i + 1 {
459+ props [exploded [i ]] = castWithSchema (exploded [i + 1 ], sch , exploded [i ])
460+ }
461+ }
462+ }
463+ decoded [v .Key ] = props
464+ }
465+ return decoded
466+ }
0 commit comments