Skip to content

Commit

Permalink
Fix bit_length checks
Browse files Browse the repository at this point in the history
Need to save a bit because `int32` and `int64` are signed integers.

`MAX_INT_WITH_ACCURATE_FLOAT` is necessary because XL integers can't be
stored accurately:

```
>> (2.pow(63) - 1)
=> 9223372036854775807
>> (2.pow(63) - 1).to_f
=> 9.223372036854776e+18
>> (2.pow(63) - 1).to_f.to_i > (2.pow(63) - 1)
=> true
```
  • Loading branch information
davishmcclurg committed Jan 28, 2025
1 parent 09841e7 commit 386c2a6
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 47 deletions.
4 changes: 2 additions & 2 deletions lib/json_schemer/openapi30/meta.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ module OpenAPI30
BASE_URI = URI('json-schemer://openapi30/schema')
# https://spec.openapis.org/oas/v3.0.3#data-types
FORMATS = OpenAPI31::FORMATS.merge(
'int32' => proc { |instance, _format| !instance.is_a?(Integer) || instance.floor.bit_length <= 32 },
'int64' => proc { |instance, _format| !instance.is_a?(Integer) || instance.floor.bit_length <= 64 },
'int32' => proc { |instance, _format| !instance.is_a?(Integer) || instance.floor.bit_length < 32 },
'int64' => proc { |instance, _format| !instance.is_a?(Integer) || instance.floor.bit_length < 64 },
'byte' => proc { |instance, _value| !instance.is_a?(String) || ContentEncoding::BASE64.call(instance).first },
'binary' => proc { |instance, _value| !instance.is_a?(String) || instance.encoding == Encoding::BINARY },
'date' => Format::DATE
Expand Down
4 changes: 2 additions & 2 deletions lib/json_schemer/openapi31/meta.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ module OpenAPI31
FORMATS = {
'int32' => proc do |instance, _format|
valid_type = instance.is_a?(Numeric) && (instance.is_a?(Integer) || instance.floor == instance)
!valid_type || instance.floor.bit_length <= 32
!valid_type || instance.floor.bit_length < 32
end,
'int64' => proc do |instance, _format|
valid_type = instance.is_a?(Numeric) && (instance.is_a?(Integer) || instance.floor == instance)
!valid_type || instance.floor.bit_length <= 64
!valid_type || instance.floor.bit_length < 64
end,
'float' => proc { |instance, _format| !instance.is_a?(Numeric) || instance.is_a?(Float) },
'double' => proc { |instance, _format| !instance.is_a?(Numeric) || instance.is_a?(Float) },
Expand Down
92 changes: 49 additions & 43 deletions test/open_api_test.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
require 'test_helper'

class OpenAPITest < Minitest::Test
MAX_INT32 = 2.pow(31) - 1
MAX_INT64 = 2.pow(63) - 1
MAX_INT_WITH_ACCURATE_FLOAT = 2.pow(53)

CAT_SCHEMA = {
'type' => 'object',
'properties' => {
Expand Down Expand Up @@ -183,7 +187,7 @@ def test_discriminator_specification_example
invalid_pack_size = {
'petType' => 'Dog',
'name' => 'Heaven',
'packSize' => 2.pow(32)
'packSize' => 2.pow(31)
}
missing_pet_type = {
'name' => 'Brian'
Expand Down Expand Up @@ -813,17 +817,18 @@ def test_openapi31_formats

assert(schemer.valid_schema?)
# int32
assert(schemer.valid?({ 'a' => 2.pow(31) }))
assert(schemer.valid?({ 'a' => 2.pow(31).to_f }))
assert(schemer.valid?({ 'a' => 2.pow(31).to_s }))
refute(schemer.valid?({ 'a' => 2.pow(32) }))
refute(schemer.valid?({ 'a' => 2.pow(32).to_f }))
assert(schemer.valid?({ 'a' => MAX_INT32 }))
assert(schemer.valid?({ 'a' => MAX_INT32.to_f }))
assert(schemer.valid?({ 'a' => MAX_INT32.to_s }))
refute(schemer.valid?({ 'a' => 2.pow(31) }))
refute(schemer.valid?({ 'a' => 2.pow(31).to_f }))
# int64
assert(schemer.valid?({ 'b' => 2.pow(63) }))
assert(schemer.valid?({ 'b' => 2.pow(63).to_f }))
assert(schemer.valid?({ 'a' => 2.pow(63).to_s }))
refute(schemer.valid?({ 'b' => 2.pow(64) }))
refute(schemer.valid?({ 'b' => 2.pow(64).to_f }))
assert(schemer.valid?({ 'b' => MAX_INT64 }))
assert(schemer.valid?({ 'b' => MAX_INT_WITH_ACCURATE_FLOAT }))
assert(schemer.valid?({ 'b' => MAX_INT_WITH_ACCURATE_FLOAT.to_f }))
assert(schemer.valid?({ 'a' => MAX_INT64.to_s }))
refute(schemer.valid?({ 'b' => 2.pow(63) }))
refute(schemer.valid?({ 'b' => 2.pow(63).to_f }))
# float
assert(schemer.valid?({ 'c' => 2.0 }))
assert(schemer.valid?({ 'c' => 2.to_s }))
Expand Down Expand Up @@ -852,15 +857,16 @@ def test_openapi31_formats_with_type

assert(schemer.valid_schema?)
# int32
assert(schemer.valid?({ 'a' => 2.pow(31) }))
assert(schemer.valid?({ 'a' => 2.pow(31).to_f }))
refute(schemer.valid?({ 'a' => 2.pow(32) }))
refute(schemer.valid?({ 'a' => 2.pow(32).to_f }))
assert(schemer.valid?({ 'a' => MAX_INT32 }))
assert(schemer.valid?({ 'a' => MAX_INT32.to_f }))
refute(schemer.valid?({ 'a' => 2.pow(31) }))
refute(schemer.valid?({ 'a' => 2.pow(31).to_f }))
# int64
assert(schemer.valid?({ 'b' => 2.pow(63) }))
assert(schemer.valid?({ 'b' => 2.pow(63).to_f }))
refute(schemer.valid?({ 'b' => 2.pow(64) }))
refute(schemer.valid?({ 'b' => 2.pow(64).to_f }))
assert(schemer.valid?({ 'b' => MAX_INT64 }))
assert(schemer.valid?({ 'b' => MAX_INT_WITH_ACCURATE_FLOAT }))
assert(schemer.valid?({ 'b' => MAX_INT_WITH_ACCURATE_FLOAT.to_f }))
refute(schemer.valid?({ 'b' => 2.pow(63) }))
refute(schemer.valid?({ 'b' => 2.pow(63).to_f }))
# float
assert(schemer.valid?({ 'c' => 2.0 }))
refute(schemer.valid?({ 'c' => 2 }))
Expand All @@ -887,13 +893,13 @@ def test_openapi31_formats_with_multiple_types

assert(schemer.valid_schema?)
# int32
assert(schemer.valid?({ 'a' => 2.pow(31) }))
assert(schemer.valid?({ 'a' => MAX_INT32 }))
assert(schemer.valid?({ 'a' => nil }))
refute(schemer.valid?({ 'a' => 2.pow(32) }))
refute(schemer.valid?({ 'a' => 2.pow(31) }))
# int64
assert(schemer.valid?({ 'b' => 2.pow(63) }))
assert(schemer.valid?({ 'b' => MAX_INT64 }))
assert(schemer.valid?({ 'b' => nil }))
refute(schemer.valid?({ 'b' => 2.pow(64) }))
refute(schemer.valid?({ 'b' => 2.pow(63) }))
# float
assert(schemer.valid?({ 'c' => 2.0 }))
assert(schemer.valid?({ 'c' => nil }))
Expand Down Expand Up @@ -927,17 +933,17 @@ def test_openapi30_formats

assert(schemer.valid_schema?)
# int32
assert(schemer.valid?({ 'a' => 2.pow(31) }))
assert(schemer.valid?({ 'a' => MAX_INT32 }))
assert(schemer.valid?({ 'a' => MAX_INT32.to_f }))
assert(schemer.valid?({ 'a' => MAX_INT32.to_s }))
assert(schemer.valid?({ 'a' => 2.pow(31).to_f }))
assert(schemer.valid?({ 'a' => 2.pow(31).to_s }))
assert(schemer.valid?({ 'a' => 2.pow(32).to_f }))
refute(schemer.valid?({ 'a' => 2.pow(32) }))
refute(schemer.valid?({ 'a' => 2.pow(31) }))
# int64
assert(schemer.valid?({ 'b' => 2.pow(63) }))
assert(schemer.valid?({ 'b' => MAX_INT64 }))
assert(schemer.valid?({ 'b' => MAX_INT64.to_f }))
assert(schemer.valid?({ 'a' => MAX_INT64.to_s }))
assert(schemer.valid?({ 'b' => 2.pow(63).to_f }))
assert(schemer.valid?({ 'a' => 2.pow(63).to_s }))
assert(schemer.valid?({ 'b' => 2.pow(64).to_f }))
refute(schemer.valid?({ 'b' => 2.pow(64) }))
refute(schemer.valid?({ 'b' => 2.pow(63) }))
# float
assert(schemer.valid?({ 'c' => 2.0 }))
assert(schemer.valid?({ 'c' => 2.to_s }))
Expand Down Expand Up @@ -986,15 +992,15 @@ def test_openapi30_formats_with_type

assert(schemer.valid_schema?)
# int32
assert(schemer.valid?({ 'a' => 2.pow(31) }))
refute(schemer.valid?({ 'a' => 2.pow(31).to_s }))
refute(schemer.valid?({ 'a' => 2.pow(32) }))
refute(schemer.valid?({ 'a' => 2.pow(32).to_f }))
assert(schemer.valid?({ 'a' => MAX_INT32 }))
refute(schemer.valid?({ 'a' => MAX_INT32.to_s }))
refute(schemer.valid?({ 'a' => 2.pow(31) }))
refute(schemer.valid?({ 'a' => 2.pow(31).to_f }))
# int64
assert(schemer.valid?({ 'b' => 2.pow(63) }))
refute(schemer.valid?({ 'a' => 2.pow(63).to_s }))
refute(schemer.valid?({ 'b' => 2.pow(64) }))
refute(schemer.valid?({ 'b' => 2.pow(64).to_f }))
assert(schemer.valid?({ 'b' => MAX_INT64 }))
refute(schemer.valid?({ 'a' => MAX_INT64.to_s }))
refute(schemer.valid?({ 'b' => 2.pow(63) }))
refute(schemer.valid?({ 'b' => 2.pow(63).to_f }))
# float
assert(schemer.valid?({ 'c' => 2.0 }))
refute(schemer.valid?({ 'c' => 2 }))
Expand Down Expand Up @@ -1043,13 +1049,13 @@ def test_openapi30_nullable_formats

assert(schemer.valid_schema?)
# int32
assert(schemer.valid?({ 'a' => 2.pow(31) }))
assert(schemer.valid?({ 'a' => MAX_INT32 }))
assert(schemer.valid?({ 'a' => nil }))
refute(schemer.valid?({ 'a' => 2.pow(32) }))
refute(schemer.valid?({ 'a' => 2.pow(31) }))
# int64
assert(schemer.valid?({ 'b' => 2.pow(63) }))
assert(schemer.valid?({ 'b' => MAX_INT64 }))
assert(schemer.valid?({ 'b' => nil }))
refute(schemer.valid?({ 'b' => 2.pow(64) }))
refute(schemer.valid?({ 'b' => 2.pow(63) }))
# float
assert(schemer.valid?({ 'c' => 2.0 }))
assert(schemer.valid?({ 'c' => nil }))
Expand Down

0 comments on commit 386c2a6

Please sign in to comment.