Skip to content

Fix Float truncation in integer coercion, also for array-form type - #214

Merged
geemus merged 8 commits into
ota42y:masterfrom
takayamaki:coerce-non-string-type-array
Oct 20, 2026
Merged

geemus merged 8 commits into
ota42y:masterfrom
takayamaki:coerce-non-string-type-array

Conversation

@takayamaki

Copy link
Copy Markdown
Contributor

With coerce_value on, type: integer coerces a value with Integer(value).
For a Float, this drops the fractional part.
So 1.5 passes type: integer and becomes 1.

This bug has been there since coerce_value was added in 7b4af75 (2018).
I think the truncation was never intended.
The README shows coercion for Strings like "1".
No spec covers a Float with coercion.

committee turns coerce_value on for JSON request bodies by default.
So committee passes a JSON body with 1.5 for an integer field.

I found this in a real app.
Some request specs sent a Float to an integer field and expected a 400.
They failed only after I rewrote nullable: true as type: [integer, "null"] for OpenAPI 3.1.
This is because #212 made array-form type coerce only Strings.
So the two forms gave different results for the same Float.

Changes

  • Integer coercion no longer truncates a Float.
    A Float with no fractional part, like 100000.0, still becomes 100000.
    A Float with a fractional part, like 1.5, now fails.
    A non-finite Float now fails with ValidateError instead of raising FloatDomainError.
  • Array-form type now coerces any non-nil value the same way as a single type, not only a String.
    Without this, 100000.0 would still pass type: integer and fail type: [integer, "null"].
    nil still goes to the nil validator.
value type: integer before type: [integer, "null"] before both after
"7" 7 7 7
100000.0 100000 ValidateError 100000
1.5 1 ValidateError ValidateError

With coerce_value off, nothing changes.

Behavior change

With coerce_value on, a request with 1.5 for an integer field passed before.
It now fails.
This breaks anyone who relied on the truncation.
If you want to keep the truncation, I can drop that part and keep only the array-form change.

Notes

  • OpenAPI 3.1.1 says 1 and 1.0 are both integers:
    https://spec.openapis.org/oas/v3.1.1.html#data-types
    With coercion, this PR matches that.
    Without coercion, type: integer still rejects 1.0.
    I will report this in a separate issue.
  • With coerce_value on, format: binary now accepts an uploaded file object in a type array.
    A single type: string already does this.

With coerce_value on, a single type: integer turns the Float 100000.0 into 100000.
type: [integer, "null"] only tried coercion for Strings, so the same Float failed with a type mismatch.
Rewriting nullable: true as a type array could change the validation result.
A single type sends nil to the nil validator before any type check.
Without this, nil for a type array without null raised a type mismatch instead of NotNullError.
This already works, and the next change must keep it.
The type array spec now uses eql, because eq also passes when 100000.0 stays a Float.
Integer() truncates a Float, so 1.5 became 1 and passed type: integer.
coerce_value was written for Strings such as "5", and no spec covered a Float.
A Float with no fractional part, such as 100000.0, is still converted.
Float::INFINITY and Float::NAN raised FloatDomainError instead of a ValidateError.
Array-form type now uses the integer coercion, so it follows the single type.
@takayamaki
takayamaki marked this pull request as ready for review October 19, 2026 16:46
@takayamaki takayamaki mentioned this pull request Oct 19, 2026
1 of 8 tasks

@geemus geemus left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm, thanks

@geemus
geemus merged commit 7222468 into ota42y:master Oct 20, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants