diff --git a/internal/validation/conversion.go b/internal/validation/conversion.go index c313bab8..ab0d2195 100644 --- a/internal/validation/conversion.go +++ b/internal/validation/conversion.go @@ -14,9 +14,15 @@ func GetIntFromInterface(value interface{}) (int, bool) { return v, true case float64: // Reject fractional or out-of-range values rather than silently truncating - // JSON-derived numbers (e.g. 3.9 -> 3). Bounds use platform int width so - // 32-bit targets (js/wasm) don't accept values that overflow int. - if v != math.Trunc(v) || v < math.MinInt || v > math.MaxInt { + // JSON-derived numbers (e.g. 3.9 -> 3). Bounds use math.MaxInt so this + // stays correct across platform int widths. + // + // float64(math.MaxInt) itself rounds up to 2^63 on platforms where int is + // 64 bits (2^63-1 isn't exactly representable), so comparing v against it + // directly would wrongly reject the true max int on platforms where MaxInt + // fits exactly in a float64 (e.g. a 32-bit int). Add 1 to get the true + // exclusive upper bound on either width. + if v != math.Trunc(v) || v < math.MinInt || v >= float64(math.MaxInt)+1 { return 0, false } return int(v), true diff --git a/internal/validation/conversion_test.go b/internal/validation/conversion_test.go index 8a13ff36..812ce173 100644 --- a/internal/validation/conversion_test.go +++ b/internal/validation/conversion_test.go @@ -8,6 +8,11 @@ import ( ) func TestGetIntFromInterface(t *testing.T) { + // Derived from math.MaxInt (rather than a hardcoded 2^63) so the boundary + // cases stay in range regardless of platform int width. + nearMaxInt := float64(math.MaxInt) - 4096 + justAboveMaxInt := float64(math.MaxInt) + 4096 + tests := []struct { name string value interface{} @@ -20,6 +25,8 @@ func TestGetIntFromInterface(t *testing.T) { {"float64 whole", float64(10), 10, true}, {"float64 fractional rejected", float64(3.9), 0, false}, {"float64 above int range rejected", float64(math.MaxInt) * 2, 0, false}, + {"float64 just above max int rejected", justAboveMaxInt, 0, false}, + {"float64 just below max int accepted", nearMaxInt, int(nearMaxInt), true}, {"numeric string", "123", 123, true}, {"negative numeric string", "-5", -5, true}, {"empty string", "", 0, false},