Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#### Fixes

* [#905](https://github.com/ruby-grape/grape-swagger/pull/905): Allow Float, BigDecimal and beginless ranges in `values` to set `minimum` and `maximum`, and skip infinite bounds that cannot be serialized to JSON - [@artyomb](https://github.com/artyomb).
* [#1016](https://github.com/ruby-grape/grape-swagger/pull/1016): Hide the nested parameters of a Hash parameter marked `documentation: { hidden: true }`, and pass the resolved `token_owner` to parameter-level `hidden:` procs like route-level ones (raising `TokenOwnerNotFound` if the configured method is missing) - [@numbata](https://github.com/numbata).
* [#1013](https://github.com/ruby-grape/grape-swagger/pull/1013): Fix array `default` values being placed inside `items` instead of on the array parameter or property, and emit an empty array `default` - [@numbata](https://github.com/numbata).
* [#1012](https://github.com/ruby-grape/grape-swagger/pull/1012): Keep the Danger PR check running in CI by pinning `git` below 6 - [@numbata](https://github.com/numbata).
Expand Down
4 changes: 2 additions & 2 deletions lib/grape-swagger/doc_methods/move_params.rb
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,8 @@ def parse_model(ref)
end

def property_keys
%i[type format description minimum maximum items enum default additional_properties additionalProperties
example minLength maxLength minProperties maxProperties]
%i[type format description minimum maximum exclusiveMaximum items enum default additional_properties
additionalProperties example minLength maxLength minProperties maxProperties]
end

def array_schema_keys
Expand Down
23 changes: 21 additions & 2 deletions lib/grape-swagger/doc_methods/parse_params.rb
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ def parse_enum_or_range_values(values)
when Proc
parse_enum_or_range_values(values.call) if values.parameters.empty?
when Range
parse_range_values(values) if values.first.is_a?(Integer)
parse_range_values(values) if range_bounds_real?(values)
when Array
{ enum: values }
else
Expand All @@ -202,7 +202,26 @@ def parse_enum_or_range_values(values)
end

def parse_range_values(values)
{ minimum: values.begin, maximum: values.end }.compact
range = { minimum: range_bound(values.begin), maximum: range_bound(values.end) }.compact
range[:exclusiveMaximum] = true if values.exclude_end? && range.key?(:maximum)
range
end

def range_bounds_real?(values)
bounds = [values.begin, values.end].compact
bounds.any? && bounds.all? { |bound| real_number?(bound) }
end

def real_number?(bound)
bound.is_a?(Numeric) && bound.real?
end

# Only finite numbers are valid JSON; BigDecimal and Rational are emitted as Integer when whole, else Float.
def range_bound(bound)
return unless real_number?(bound) && bound.finite?
return bound if bound.is_a?(Integer) || bound.is_a?(Float)

bound.to_i == bound ? bound.to_i : bound.to_f
end
end
end
Expand Down
88 changes: 88 additions & 0 deletions spec/lib/parse_params_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,94 @@
end
end

describe 'first Float' do
specify do
parsed_range = subject.send(:parse_enum_or_range_values, -5.0..5.0)
expect(parsed_range).to eql(minimum: -5.0, maximum: 5.0)
end
end

describe 'with an infinite end' do
specify do
expect(subject.send(:parse_enum_or_range_values, 1.0..Float::INFINITY)).to eql(minimum: 1.0)
expect(subject.send(:parse_enum_or_range_values, 1..Float::INFINITY)).to eql(minimum: 1)
end
end

describe 'with an infinite begin' do
specify do
parsed_range = subject.send(:parse_enum_or_range_values, -Float::INFINITY..5.0)
expect(parsed_range).to eql(maximum: 5.0)
end
end

describe 'beginless' do
specify do
expect(subject.send(:parse_enum_or_range_values, ..5.0)).to eql(maximum: 5.0)
expect(subject.send(:parse_enum_or_range_values, ..5)).to eql(maximum: 5)
end
end

describe 'endless Float' do
specify do
parsed_range = subject.send(:parse_enum_or_range_values, 1.5..)
expect(parsed_range).to eql(minimum: 1.5)
end
end

describe 'BigDecimal' do
specify do
parsed_range = subject.send(:parse_enum_or_range_values, BigDecimal('1')..BigDecimal('2.5'))
expect(parsed_range).to eql(minimum: 1, maximum: 2.5)
end

it 'keeps whole values beyond Float precision exact' do
parsed_range = subject.send(:parse_enum_or_range_values, BigDecimal('9007199254740993')..)
expect(parsed_range).to eql(minimum: 9_007_199_254_740_993)
end

it 'skips NaN and infinite bounds' do
expect(subject.send(:parse_enum_or_range_values, ..BigDecimal('NaN'))).to eql({})
expect(subject.send(:parse_enum_or_range_values, BigDecimal('-Infinity')..BigDecimal('3'))).to eql(maximum: 3)
end
end

describe 'Rational' do
specify do
parsed_range = subject.send(:parse_enum_or_range_values, 1r..3/2r)
expect(parsed_range).to eql(minimum: 1, maximum: 1.5)
end
end

describe 'Float NaN' do
specify do
parsed_range = subject.send(:parse_enum_or_range_values, Float::NAN..)
expect(parsed_range).to eql({})
end
end

describe 'with an exclusive end' do
specify do
expect(subject.send(:parse_enum_or_range_values, 1...5))
.to eql(minimum: 1, maximum: 5, exclusiveMaximum: true)
expect(subject.send(:parse_enum_or_range_values, 0.0...1.0))
.to eql(minimum: 0.0, maximum: 1.0, exclusiveMaximum: true)
end

it 'does not flag an endless or infinite end' do
expect(subject.send(:parse_enum_or_range_values, 1...)).to eql(minimum: 1)
expect(subject.send(:parse_enum_or_range_values, 1.0...Float::INFINITY)).to eql(minimum: 1.0)
end
end

describe 'with Complex bounds' do
specify do
expect(subject.send(:parse_enum_or_range_values, ..Complex(1, 2))).to be_nil
expect(subject.send(:parse_enum_or_range_values, Complex(1, 2)..)).to be_nil
expect(subject.send(:parse_enum_or_range_values, 1..Complex(2, 0))).to be_nil
end
end

describe 'first String' do
specify do
parsed_range = subject.send(:parse_enum_or_range_values, 'a'..'z')
Expand Down
87 changes: 85 additions & 2 deletions spec/swagger_v2/param_values_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,47 @@ def app
{ message: 'hi' }
end

desc 'range_infinite_float' do
consumes ['application/x-www-form-urlencoded']
end
params do
requires :float, type: Float, values: 1.0..Float::INFINITY
end
post :range_infinite_float do
{ message: 'hi' }
end

desc 'range_exclusive_float' do
consumes ['application/x-www-form-urlencoded']
end
params do
requires :float, type: Float, values: 0.0...1.0
end
post :range_exclusive_float do
{ message: 'hi' }
end

desc 'range_exclusive_body' do
consumes ['application/json']
end
params do
requires :float, type: Float, values: 0.0...1.0
requires :list, type: Array[Integer], values: 1...5
end
post :range_exclusive_body do
{ message: 'hi' }
end

desc 'range_exclusive_query_array' do
consumes ['application/json']
end
params do
requires :list, type: Array[Integer], values: 1...5
end
get :range_exclusive_query_array do
{ message: 'hi' }
end

add_swagger_documentation
end
end
Expand Down Expand Up @@ -167,13 +208,55 @@ def first_parameter_info(request)
context 'Range values' do
subject(:range_float) { first_parameter_info('range_float') }

it 'has float range values as string' do
it 'has float range values as minimum and maximum' do
expect(range_float).to eq [{
'in' => 'formData',
'name' => 'float',
'type' => 'number',
'required' => true,
'format' => 'float'
'format' => 'float',
'minimum' => -5.0,
'maximum' => 5.0
}]
end
end

context 'Range values with an exclusive end' do
subject(:range_exclusive_float) { first_parameter_info('range_exclusive_float') }

it 'flags the maximum as exclusive' do
expect(range_exclusive_float.first).to include('minimum' => 0.0, 'maximum' => 1.0, 'exclusiveMaximum' => true)
end
end

context 'Range values with an exclusive end in a query array' do
it 'keeps exclusiveMaximum on the array items' do
get '/swagger_doc/range_exclusive_query_array'
parameters = JSON.parse(last_response.body)['paths']['/range_exclusive_query_array']['get']['parameters']
expect(parameters.first['items']).to include('minimum' => 1, 'maximum' => 5, 'exclusiveMaximum' => true)
end
end

context 'Range values with an exclusive end in a body definition' do
it 'keeps exclusiveMaximum on properties and array items' do
get '/swagger_doc/range_exclusive_body'
props = JSON.parse(last_response.body)['definitions']['postRangeExclusiveBody']['properties']
expect(props['float']).to include('maximum' => 1.0, 'exclusiveMaximum' => true)
expect(props['list']['items']).to include('minimum' => 1, 'maximum' => 5, 'exclusiveMaximum' => true)
end
end

context 'Range values with an infinite end' do
subject(:range_infinite_float) { first_parameter_info('range_infinite_float') }

it 'has only the finite bound as minimum' do
expect(range_infinite_float).to eq [{
'in' => 'formData',
'name' => 'float',
'type' => 'number',
'required' => true,
'format' => 'float',
'minimum' => 1.0
}]
end
end
Expand Down
Loading