Summary
internal/apiquery does not preserve the value semantics of several Go primitive numeric types.
int8 and uint8 are not handled by encodePrimitive, so those values produce no query pair at all.
float32 is handled together with float64 using strconv.FormatFloat(..., 64), so a value such as float32(0.1) is widened before formatting and can be serialized as 0.10000000149011612 instead of 0.1.
This is inconsistent with the sibling multipart/form encoder, whose primitive path supports int8/uint8 and formats float32 with a 32-bit precision argument.
Reproduction
On current main (d082a010f7c6cacf407d8a1581446a7857f9f1bb), these calls exercise the problem directly:
values, _ := Marshal(map[string]any{"value": int8(-8)})
fmt.Println(values.Encode())
// current: ""
// expected: "value=-8"
values, _ = Marshal(map[string]any{"value": uint8(8)})
fmt.Println(values.Encode())
// current: ""
// expected: "value=8"
values, _ = Marshal(map[string]any{"value": float32(0.1)})
fmt.Println(values.Get("value"))
// current: "0.10000000149011612"
// expected: "0.1"
Root cause
internal/apiquery/encoder.go currently handles:
case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64:
...
case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
...
case reflect.Float32, reflect.Float64:
strconv.FormatFloat(value.Float(), 'f', -1, 64)
The narrow 8-bit integer kinds are missing, and the float branch always asks FormatFloat to preserve 64-bit precision even when the source value is a float32.
Expected behavior
The query encoder should serialize supported Go primitive values without silently dropping them or exposing precision artifacts introduced only by reflection widening:
int8 should follow the other signed integer kinds;
uint8 should follow the other unsigned integer kinds;
float32 should use bitSize=32;
float64 should retain bitSize=64.
Suggested fix
Add the missing integer kinds and split the float formatting cases by source width. Add focused table-driven regressions for int8, uint8, and float32(0.1).
Impact
This is query-serialization correctness. Callers using these legitimate Go primitive types can currently send a different numeric value than intended, or omit the parameter entirely, without receiving an error.
Summary
internal/apiquerydoes not preserve the value semantics of several Go primitive numeric types.int8anduint8are not handled byencodePrimitive, so those values produce no query pair at all.float32is handled together withfloat64usingstrconv.FormatFloat(..., 64), so a value such asfloat32(0.1)is widened before formatting and can be serialized as0.10000000149011612instead of0.1.This is inconsistent with the sibling multipart/form encoder, whose primitive path supports
int8/uint8and formatsfloat32with a 32-bit precision argument.Reproduction
On current
main(d082a010f7c6cacf407d8a1581446a7857f9f1bb), these calls exercise the problem directly:Root cause
internal/apiquery/encoder.gocurrently handles:The narrow 8-bit integer kinds are missing, and the float branch always asks
FormatFloatto preserve 64-bit precision even when the source value is afloat32.Expected behavior
The query encoder should serialize supported Go primitive values without silently dropping them or exposing precision artifacts introduced only by reflection widening:
int8should follow the other signed integer kinds;uint8should follow the other unsigned integer kinds;float32should usebitSize=32;float64should retainbitSize=64.Suggested fix
Add the missing integer kinds and split the float formatting cases by source width. Add focused table-driven regressions for
int8,uint8, andfloat32(0.1).Impact
This is query-serialization correctness. Callers using these legitimate Go primitive types can currently send a different numeric value than intended, or omit the parameter entirely, without receiving an error.