grape 1.7.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +46 -0
- data/CONTRIBUTING.md +31 -1
- data/README.md +38 -8
- data/grape.gemspec +2 -2
- data/lib/grape/api.rb +2 -2
- data/lib/grape/content_types.rb +2 -8
- data/lib/grape/dsl/desc.rb +3 -2
- data/lib/grape/dsl/inside_route.rb +6 -6
- data/lib/grape/dsl/parameters.rb +6 -1
- data/lib/grape/dsl/request_response.rb +3 -2
- data/lib/grape/dsl/settings.rb +2 -6
- data/lib/grape/endpoint.rb +21 -19
- data/lib/grape/error_formatter/base.rb +1 -1
- data/lib/grape/exceptions/base.rb +4 -3
- data/lib/grape/exceptions/missing_group_type.rb +1 -6
- data/lib/grape/exceptions/unsupported_group_type.rb +1 -6
- data/lib/grape/exceptions/validation_errors.rb +1 -6
- data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +3 -3
- data/lib/grape/extensions/hash.rb +4 -7
- data/lib/grape/extensions/hashie/mash.rb +3 -3
- data/lib/grape/formatter/serializable_hash.rb +7 -7
- data/lib/grape/middleware/auth/base.rb +1 -1
- data/lib/grape/middleware/error.rb +1 -1
- data/lib/grape/middleware/formatter.rb +1 -1
- data/lib/grape/middleware/stack.rb +1 -1
- data/lib/grape/middleware/versioner/header.rb +11 -19
- data/lib/grape/request.rb +1 -1
- data/lib/grape/router/attribute_translator.rb +1 -1
- data/lib/grape/router/route.rb +1 -3
- data/lib/grape/types/invalid_value.rb +8 -0
- data/lib/grape/util/cache.rb +1 -1
- data/lib/grape/util/lazy_value.rb +3 -11
- data/lib/grape/util/strict_hash_configuration.rb +3 -4
- data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
- data/lib/grape/validations/params_scope.rb +9 -3
- data/lib/grape/validations/single_attribute_iterator.rb +3 -1
- data/lib/grape/validations/types/custom_type_coercer.rb +2 -16
- data/lib/grape/validations/types/invalid_value.rb +0 -7
- data/lib/grape/validations/validators/base.rb +9 -20
- data/lib/grape/validations/validators/default_validator.rb +2 -20
- data/lib/grape/validations/validators/multiple_params_base.rb +4 -8
- data/lib/grape/validations/validators/values_validator.rb +14 -5
- data/lib/grape/version.rb +1 -1
- data/lib/grape.rb +19 -3
- data/spec/grape/api/custom_validations_spec.rb +14 -57
- data/spec/grape/api_remount_spec.rb +36 -0
- data/spec/grape/api_spec.rb +15 -21
- data/spec/grape/dsl/desc_spec.rb +84 -85
- data/spec/grape/dsl/inside_route_spec.rb +6 -10
- data/spec/grape/dsl/request_response_spec.rb +21 -2
- data/spec/grape/endpoint_spec.rb +11 -10
- data/spec/grape/exceptions/body_parse_errors_spec.rb +40 -0
- data/spec/grape/exceptions/invalid_accept_header_spec.rb +3 -0
- data/spec/grape/exceptions/missing_group_type_spec.rb +5 -9
- data/spec/grape/exceptions/unsupported_group_type_spec.rb +5 -9
- data/spec/grape/grape_spec.rb +9 -0
- data/spec/grape/integration/rack_spec.rb +6 -5
- data/spec/grape/middleware/base_spec.rb +7 -5
- data/spec/grape/middleware/formatter_spec.rb +7 -7
- data/spec/grape/request_spec.rb +4 -14
- data/spec/grape/validations/multiple_attributes_iterator_spec.rb +6 -8
- data/spec/grape/validations/single_attribute_iterator_spec.rb +8 -9
- data/spec/grape/validations/validators/base_spec.rb +38 -0
- data/spec/grape/validations/validators/values_spec.rb +56 -0
- data/spec/grape/validations_spec.rb +36 -12
- data/spec/shared/deprecated_class_examples.rb +16 -0
- metadata +112 -114
- data/lib/grape/config.rb +0 -34
- data/lib/grape/extensions/deep_mergeable_hash.rb +0 -21
- data/lib/grape/extensions/deep_symbolize_hash.rb +0 -32
- data/spec/grape/config_spec.rb +0 -17
- data/spec/grape/dsl/configuration_spec.rb +0 -14
- data/spec/grape/validations/attributes_iterator_spec.rb +0 -4
@@ -91,6 +91,46 @@ describe Grape::Exceptions::ValidationErrors do
|
|
91
91
|
end
|
92
92
|
end
|
93
93
|
|
94
|
+
context 'api with rescue_from :grape_exceptions handler with block' do
|
95
|
+
subject { Class.new(Grape::API) }
|
96
|
+
|
97
|
+
before do
|
98
|
+
subject.rescue_from :grape_exceptions do |e|
|
99
|
+
rack_response "Custom Error Contents, Original Message: #{e.message}", 400
|
100
|
+
end
|
101
|
+
|
102
|
+
subject.params do
|
103
|
+
requires :beer
|
104
|
+
end
|
105
|
+
|
106
|
+
subject.post '/beer' do
|
107
|
+
'beer received'
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def app
|
112
|
+
subject
|
113
|
+
end
|
114
|
+
|
115
|
+
context 'with content_type json' do
|
116
|
+
it 'returns body parsing error message' do
|
117
|
+
post '/beer', 'test', 'CONTENT_TYPE' => 'application/json'
|
118
|
+
expect(last_response.status).to eq 400
|
119
|
+
expect(last_response.body).to include 'message body does not match declared format'
|
120
|
+
expect(last_response.body).to include 'Custom Error Contents, Original Message'
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
context 'with content_type xml' do
|
125
|
+
it 'returns body parsing error message' do
|
126
|
+
post '/beer', 'test', 'CONTENT_TYPE' => 'application/xml'
|
127
|
+
expect(last_response.status).to eq 400
|
128
|
+
expect(last_response.body).to include 'message body does not match declared format'
|
129
|
+
expect(last_response.body).to include 'Custom Error Contents, Original Message'
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
94
134
|
context 'api without a rescue handler' do
|
95
135
|
subject { Class.new(Grape::API) }
|
96
136
|
|
@@ -10,11 +10,13 @@ describe Grape::Exceptions::InvalidAcceptHeader do
|
|
10
10
|
expect(last_response.body).to eq('beer received')
|
11
11
|
end
|
12
12
|
end
|
13
|
+
|
13
14
|
shared_examples_for 'a cascaded request' do
|
14
15
|
it 'does not find a matching route' do
|
15
16
|
expect(last_response.status).to eq 404
|
16
17
|
end
|
17
18
|
end
|
19
|
+
|
18
20
|
shared_examples_for 'a not-cascaded request' do
|
19
21
|
it 'does not include the X-Cascade=pass header' do
|
20
22
|
expect(last_response.headers['X-Cascade']).to be_nil
|
@@ -24,6 +26,7 @@ describe Grape::Exceptions::InvalidAcceptHeader do
|
|
24
26
|
expect(last_response.status).to eq 406
|
25
27
|
end
|
26
28
|
end
|
29
|
+
|
27
30
|
shared_examples_for 'a rescued request' do
|
28
31
|
it 'does not include the X-Cascade=pass header' do
|
29
32
|
expect(last_response.headers['X-Cascade']).to be_nil
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'shared/deprecated_class_examples'
|
4
|
+
|
3
5
|
RSpec.describe Grape::Exceptions::MissingGroupType do
|
4
6
|
describe '#message' do
|
5
7
|
subject { described_class.new.message }
|
@@ -7,15 +9,9 @@ RSpec.describe Grape::Exceptions::MissingGroupType do
|
|
7
9
|
it { is_expected.to include 'group type is required' }
|
8
10
|
end
|
9
11
|
|
10
|
-
describe '
|
11
|
-
|
12
|
-
|
13
|
-
it 'puts a deprecation warning' do
|
14
|
-
expect(Warning).to receive(:warn) do |message|
|
15
|
-
expect(message).to include('`Grape::Exceptions::MissingGroupTypeError` is deprecated')
|
16
|
-
end
|
12
|
+
describe 'Grape::Exceptions::MissingGroupTypeError' do
|
13
|
+
let(:deprecated_class) { Grape::Exceptions::MissingGroupTypeError }
|
17
14
|
|
18
|
-
|
19
|
-
end
|
15
|
+
it_behaves_like 'deprecated class'
|
20
16
|
end
|
21
17
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'shared/deprecated_class_examples'
|
4
|
+
|
3
5
|
RSpec.describe Grape::Exceptions::UnsupportedGroupType do
|
4
6
|
subject { described_class.new }
|
5
7
|
|
@@ -9,15 +11,9 @@ RSpec.describe Grape::Exceptions::UnsupportedGroupType do
|
|
9
11
|
it { is_expected.to include 'group type must be Array, Hash, JSON or Array[JSON]' }
|
10
12
|
end
|
11
13
|
|
12
|
-
describe '
|
13
|
-
|
14
|
-
|
15
|
-
it 'puts a deprecation warning' do
|
16
|
-
expect(Warning).to receive(:warn) do |message|
|
17
|
-
expect(message).to include('`Grape::Exceptions::UnsupportedGroupTypeError` is deprecated')
|
18
|
-
end
|
14
|
+
describe 'Grape::Exceptions::UnsupportedGroupTypeError' do
|
15
|
+
let(:deprecated_class) { Grape::Exceptions::UnsupportedGroupTypeError }
|
19
16
|
|
20
|
-
|
21
|
-
end
|
17
|
+
it_behaves_like 'deprecated class'
|
22
18
|
end
|
23
19
|
end
|
@@ -27,19 +27,20 @@ describe Rack do
|
|
27
27
|
end
|
28
28
|
|
29
29
|
context 'when the app is mounted' do
|
30
|
-
|
31
|
-
|
30
|
+
let(:ping_mount) do
|
31
|
+
Class.new(Grape::API) do
|
32
32
|
get 'ping'
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
-
let
|
37
|
-
app_to_mount =
|
38
|
-
Class.new(Grape::API) do
|
36
|
+
let(:app) do
|
37
|
+
app_to_mount = ping_mount
|
38
|
+
app = Class.new(Grape::API) do
|
39
39
|
namespace 'namespace' do
|
40
40
|
mount app_to_mount
|
41
41
|
end
|
42
42
|
end
|
43
|
+
Rack::Builder.new(app)
|
43
44
|
end
|
44
45
|
|
45
46
|
it 'finds the app on the namespace' do
|
@@ -70,18 +70,18 @@ describe Grape::Middleware::Base do
|
|
70
70
|
|
71
71
|
it 'is able to access the response' do
|
72
72
|
subject.call({})
|
73
|
-
expect(subject.response).to
|
73
|
+
expect(subject.response).to be_a(Rack::Response)
|
74
74
|
end
|
75
75
|
|
76
76
|
describe '#response' do
|
77
77
|
subject do
|
78
|
-
puts described_class
|
79
78
|
described_class.new(response)
|
80
79
|
end
|
81
80
|
|
82
81
|
before { subject.call({}) }
|
83
82
|
|
84
83
|
context 'when Array' do
|
84
|
+
let(:rack_response) { Rack::Response.new('test', 204, abc: 1) }
|
85
85
|
let(:response) { ->(_) { [204, { abc: 1 }, 'test'] } }
|
86
86
|
|
87
87
|
it 'status' do
|
@@ -97,12 +97,14 @@ describe Grape::Middleware::Base do
|
|
97
97
|
end
|
98
98
|
|
99
99
|
it 'returns the memoized Rack::Response instance' do
|
100
|
-
|
100
|
+
allow(Rack::Response).to receive(:new).and_return(rack_response)
|
101
|
+
expect(subject.response).to eq(rack_response)
|
101
102
|
end
|
102
103
|
end
|
103
104
|
|
104
105
|
context 'when Rack::Response' do
|
105
|
-
let(:
|
106
|
+
let(:rack_response) { Rack::Response.new('test', 204, abc: 1) }
|
107
|
+
let(:response) { ->(_) { rack_response } }
|
106
108
|
|
107
109
|
it 'status' do
|
108
110
|
expect(subject.response.status).to eq(204)
|
@@ -117,7 +119,7 @@ describe Grape::Middleware::Base do
|
|
117
119
|
end
|
118
120
|
|
119
121
|
it 'returns the memoized Rack::Response instance' do
|
120
|
-
expect(subject.response).to
|
122
|
+
expect(subject.response).to eq(rack_response)
|
121
123
|
end
|
122
124
|
end
|
123
125
|
end
|
@@ -13,7 +13,7 @@ describe Grape::Middleware::Formatter do
|
|
13
13
|
|
14
14
|
it 'looks at the bodies for possibly serializable data' do
|
15
15
|
_, _, bodies = *subject.call('PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json')
|
16
|
-
bodies.each { |b| expect(b).to eq(::Grape::Json.dump(body)) }
|
16
|
+
bodies.each { |b| expect(b).to eq(::Grape::Json.dump(body)) } # rubocop:disable RSpec/IteratedExpectation
|
17
17
|
end
|
18
18
|
|
19
19
|
context 'default format' do
|
@@ -26,7 +26,7 @@ describe Grape::Middleware::Formatter do
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
-
subject.call('PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json').to_a.last.each { |b| expect(b).to eq('"bar"') }
|
29
|
+
subject.call('PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json').to_a.last.each { |b| expect(b).to eq('"bar"') } # rubocop:disable RSpec/IteratedExpectation
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
@@ -40,7 +40,7 @@ describe Grape::Middleware::Formatter do
|
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
|
-
subject.call('PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/vnd.api+json').to_a.last.each { |b| expect(b).to eq('{"foos":[{"bar":"baz"}] }') }
|
43
|
+
subject.call('PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/vnd.api+json').to_a.last.each { |b| expect(b).to eq('{"foos":[{"bar":"baz"}] }') } # rubocop:disable RSpec/IteratedExpectation
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
@@ -53,8 +53,7 @@ describe Grape::Middleware::Formatter do
|
|
53
53
|
'<bar/>'
|
54
54
|
end
|
55
55
|
end
|
56
|
-
|
57
|
-
subject.call('PATH_INFO' => '/somewhere.xml', 'HTTP_ACCEPT' => 'application/json').to_a.last.each { |b| expect(b).to eq('<bar/>') }
|
56
|
+
subject.call('PATH_INFO' => '/somewhere.xml', 'HTTP_ACCEPT' => 'application/json').to_a.last.each { |b| expect(b).to eq('<bar/>') } # rubocop:disable RSpec/IteratedExpectation
|
58
57
|
end
|
59
58
|
end
|
60
59
|
end
|
@@ -243,6 +242,7 @@ describe Grape::Middleware::Formatter do
|
|
243
242
|
end
|
244
243
|
|
245
244
|
context 'input' do
|
245
|
+
content_types = ['application/json', 'application/json; charset=utf-8'].freeze
|
246
246
|
%w[POST PATCH PUT DELETE].each do |method|
|
247
247
|
context 'when body is not nil or empty' do
|
248
248
|
context 'when Content-Type is supported' do
|
@@ -320,7 +320,7 @@ describe Grape::Middleware::Formatter do
|
|
320
320
|
end
|
321
321
|
end
|
322
322
|
|
323
|
-
|
323
|
+
content_types.each do |content_type|
|
324
324
|
context content_type do
|
325
325
|
it "parses the body from #{method} and copies values into rack.request.form_hash" do
|
326
326
|
io = StringIO.new('{"is_boolean":true,"string":"thing"}')
|
@@ -405,7 +405,7 @@ describe Grape::Middleware::Formatter do
|
|
405
405
|
env = { 'PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json' }
|
406
406
|
status, headers, body = subject.call(env)
|
407
407
|
expect(status).to be == 200
|
408
|
-
expect(headers).to be == { '
|
408
|
+
expect(headers.transform_keys(&:downcase)).to be == { 'content-type' => 'application/json' }
|
409
409
|
expect(read_chunks(body)).to be == ['data']
|
410
410
|
end
|
411
411
|
end
|
data/spec/grape/request_spec.rb
CHANGED
@@ -62,29 +62,19 @@ module Grape
|
|
62
62
|
end
|
63
63
|
end
|
64
64
|
|
65
|
-
describe 'when the
|
65
|
+
describe 'when the build_params_with is set to Hashie' do
|
66
66
|
subject(:request_params) { described_class.new(env, **opts).params }
|
67
67
|
|
68
|
-
before do
|
69
|
-
Grape.configure do |config|
|
70
|
-
config.param_builder = Grape::Extensions::Hashie::Mash::ParamBuilder
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
after do
|
75
|
-
Grape.config.reset
|
76
|
-
end
|
77
|
-
|
78
68
|
context 'when the API does not include a specific param builder' do
|
79
69
|
let(:opts) { {} }
|
80
70
|
|
81
|
-
it { is_expected.to be_a(
|
71
|
+
it { is_expected.to be_a(Hash) }
|
82
72
|
end
|
83
73
|
|
84
74
|
context 'when the API includes a specific param builder' do
|
85
|
-
let(:opts) { { build_params_with: Grape::Extensions::
|
75
|
+
let(:opts) { { build_params_with: Grape::Extensions::Hashie::Mash::ParamBuilder } }
|
86
76
|
|
87
|
-
it { is_expected.to be_a(
|
77
|
+
it { is_expected.to be_a(Hashie::Mash) }
|
88
78
|
end
|
89
79
|
end
|
90
80
|
|
@@ -12,8 +12,8 @@ describe Grape::Validations::MultipleAttributesIterator do
|
|
12
12
|
{ first: 'string', second: 'string' }
|
13
13
|
end
|
14
14
|
|
15
|
-
it 'yields the whole params hash
|
16
|
-
expect { |b| iterator.each(&b) }.to yield_with_args(params
|
15
|
+
it 'yields the whole params hash without the list of attrs' do
|
16
|
+
expect { |b| iterator.each(&b) }.to yield_with_args(params)
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
@@ -23,17 +23,15 @@ describe Grape::Validations::MultipleAttributesIterator do
|
|
23
23
|
end
|
24
24
|
|
25
25
|
it 'yields each element of the array without the list of attrs' do
|
26
|
-
expect { |b| iterator.each(&b) }.to yield_successive_args(
|
26
|
+
expect { |b| iterator.each(&b) }.to yield_successive_args(params[0], params[1])
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
30
|
context 'when params is empty optional placeholder' do
|
31
|
-
let(:params)
|
32
|
-
[Grape::DSL::Parameters::EmptyOptionalValue, { first: 'string2', second: 'string2' }]
|
33
|
-
end
|
31
|
+
let(:params) { [Grape::DSL::Parameters::EmptyOptionalValue] }
|
34
32
|
|
35
|
-
it '
|
36
|
-
expect { |b| iterator.each(&b) }.to yield_successive_args
|
33
|
+
it 'does not yield it' do
|
34
|
+
expect { |b| iterator.each(&b) }.to yield_successive_args
|
37
35
|
end
|
38
36
|
end
|
39
37
|
end
|
@@ -14,7 +14,7 @@ describe Grape::Validations::SingleAttributeIterator do
|
|
14
14
|
|
15
15
|
it 'yields params and every single attribute from the list' do
|
16
16
|
expect { |b| iterator.each(&b) }
|
17
|
-
.to yield_successive_args([params, :first, false
|
17
|
+
.to yield_successive_args([params, :first, false], [params, :second, false])
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
@@ -25,8 +25,8 @@ describe Grape::Validations::SingleAttributeIterator do
|
|
25
25
|
|
26
26
|
it 'yields every single attribute from the list for each of the array elements' do
|
27
27
|
expect { |b| iterator.each(&b) }.to yield_successive_args(
|
28
|
-
[params[0], :first, false
|
29
|
-
[params[1], :first, false
|
28
|
+
[params[0], :first, false], [params[0], :second, false],
|
29
|
+
[params[1], :first, false], [params[1], :second, false]
|
30
30
|
)
|
31
31
|
end
|
32
32
|
|
@@ -35,9 +35,9 @@ describe Grape::Validations::SingleAttributeIterator do
|
|
35
35
|
|
36
36
|
it 'marks params with empty values' do
|
37
37
|
expect { |b| iterator.each(&b) }.to yield_successive_args(
|
38
|
-
[params[0], :first, true
|
39
|
-
[params[1], :first, true
|
40
|
-
[params[2], :first, false
|
38
|
+
[params[0], :first, true], [params[0], :second, true],
|
39
|
+
[params[1], :first, true], [params[1], :second, true],
|
40
|
+
[params[2], :first, false], [params[2], :second, false]
|
41
41
|
)
|
42
42
|
end
|
43
43
|
end
|
@@ -45,10 +45,9 @@ describe Grape::Validations::SingleAttributeIterator do
|
|
45
45
|
context 'when missing optional value' do
|
46
46
|
let(:params) { [Grape::DSL::Parameters::EmptyOptionalValue, 10] }
|
47
47
|
|
48
|
-
it '
|
48
|
+
it 'does not yield skipped values' do
|
49
49
|
expect { |b| iterator.each(&b) }.to yield_successive_args(
|
50
|
-
[params[
|
51
|
-
[params[1], :first, false, false], [params[1], :second, false, false]
|
50
|
+
[params[1], :first, false], [params[1], :second, false]
|
52
51
|
)
|
53
52
|
end
|
54
53
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe Grape::Validations::Validators::Base do
|
4
|
+
describe '#inherited' do
|
5
|
+
context 'when validator is anonymous' do
|
6
|
+
subject(:custom_validator) { Class.new(described_class) }
|
7
|
+
|
8
|
+
it 'does not register the validator' do
|
9
|
+
expect(Grape::Validations).not_to receive(:register_validator)
|
10
|
+
custom_validator
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Anonymous class does not have a name and class A < B would leak.
|
15
|
+
# Simulates inherited callback
|
16
|
+
context "when validator's underscored name does not end with _validator" do
|
17
|
+
subject(:custom_validator) { described_class.inherited(TestModule::CustomValidatorABC) }
|
18
|
+
|
19
|
+
before { stub_const('TestModule::CustomValidatorABC', Class.new) }
|
20
|
+
|
21
|
+
it 'registers the custom validator with a short name' do
|
22
|
+
expect(Grape::Validations).to receive(:register_validator).with('custom_validator_abc', TestModule::CustomValidatorABC)
|
23
|
+
custom_validator
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context "when validator's underscored name ends with _validator" do
|
28
|
+
subject(:custom_validator) { described_class.inherited(TestModule::CustomValidator) }
|
29
|
+
|
30
|
+
before { stub_const('TestModule::CustomValidator', Class.new) }
|
31
|
+
|
32
|
+
it 'registers the custom validator with short name not ending with validator' do
|
33
|
+
expect(Grape::Validations).to receive(:register_validator).with('custom', TestModule::CustomValidator)
|
34
|
+
custom_validator
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -30,6 +30,10 @@ describe Grape::Validations::Validators::ValuesValidator do
|
|
30
30
|
def include?(value)
|
31
31
|
values.include?(value)
|
32
32
|
end
|
33
|
+
|
34
|
+
def even?(value)
|
35
|
+
value.to_i.even?
|
36
|
+
end
|
33
37
|
end
|
34
38
|
end
|
35
39
|
end
|
@@ -114,6 +118,13 @@ describe Grape::Validations::Validators::ValuesValidator do
|
|
114
118
|
{ type: params[:type] }
|
115
119
|
end
|
116
120
|
|
121
|
+
params do
|
122
|
+
optional :type, type: Integer, values: 1..
|
123
|
+
end
|
124
|
+
get '/endless' do
|
125
|
+
{ type: params[:type] }
|
126
|
+
end
|
127
|
+
|
117
128
|
params do
|
118
129
|
requires :type, values: ->(v) { ValuesModel.include? v }
|
119
130
|
end
|
@@ -234,6 +245,18 @@ describe Grape::Validations::Validators::ValuesValidator do
|
|
234
245
|
end
|
235
246
|
get '/proc/message'
|
236
247
|
|
248
|
+
params do
|
249
|
+
requires :number, values: { value: ->(v) { ValuesModel.even? v }, message: 'must be even' }
|
250
|
+
end
|
251
|
+
get '/proc/custom_message' do
|
252
|
+
{ message: 'success' }
|
253
|
+
end
|
254
|
+
|
255
|
+
params do
|
256
|
+
requires :input_one, :input_two, values: { value: ->(v1, v2) { v1 + v2 > 10 } }
|
257
|
+
end
|
258
|
+
get '/proc/arity2'
|
259
|
+
|
237
260
|
params do
|
238
261
|
optional :name, type: String, values: %w[a b], allow_blank: true
|
239
262
|
end
|
@@ -374,6 +397,18 @@ describe Grape::Validations::Validators::ValuesValidator do
|
|
374
397
|
expect(last_response.body).to eq({ error: 'type does not have a valid value' }.to_json)
|
375
398
|
end
|
376
399
|
|
400
|
+
it 'validates against values in an endless range', if: ActiveSupport::VERSION::MAJOR >= 6 do
|
401
|
+
get('/endless', type: 10)
|
402
|
+
expect(last_response.status).to eq 200
|
403
|
+
expect(last_response.body).to eq({ type: 10 }.to_json)
|
404
|
+
end
|
405
|
+
|
406
|
+
it 'does not allow an invalid value for a parameter using an endless range', if: ActiveSupport::VERSION::MAJOR >= 6 do
|
407
|
+
get('/endless', type: 0)
|
408
|
+
expect(last_response.status).to eq 400
|
409
|
+
expect(last_response.body).to eq({ error: 'type does not have a valid value' }.to_json)
|
410
|
+
end
|
411
|
+
|
377
412
|
it 'does not allow non-numeric string value for int value using lambda' do
|
378
413
|
get('/lambda_int_val', number: 'foo')
|
379
414
|
expect(last_response.status).to eq 400
|
@@ -673,5 +708,26 @@ describe Grape::Validations::Validators::ValuesValidator do
|
|
673
708
|
expect(last_response.status).to eq 400
|
674
709
|
expect(last_response.body).to eq({ error: 'type failed check' }.to_json)
|
675
710
|
end
|
711
|
+
|
712
|
+
context 'when proc has an arity of 1' do
|
713
|
+
it 'accepts a valid value' do
|
714
|
+
get '/proc/custom_message', number: 4
|
715
|
+
expect(last_response.status).to eq 200
|
716
|
+
expect(last_response.body).to eq({ message: 'success' }.to_json)
|
717
|
+
end
|
718
|
+
|
719
|
+
it 'rejects an invalid value' do
|
720
|
+
get '/proc/custom_message', number: 5
|
721
|
+
expect(last_response.status).to eq 400
|
722
|
+
expect(last_response.body).to eq({ error: 'number must be even' }.to_json)
|
723
|
+
end
|
724
|
+
end
|
725
|
+
|
726
|
+
context 'when arity is > 1' do
|
727
|
+
it 'returns an error status code' do
|
728
|
+
get '/proc/arity2', input_one: 2, input_two: 3
|
729
|
+
expect(last_response.status).to eq 400
|
730
|
+
end
|
731
|
+
end
|
676
732
|
end
|
677
733
|
end
|
@@ -179,11 +179,12 @@ describe Grape::Validations do
|
|
179
179
|
context 'requires :all using Grape::Entity documentation' do
|
180
180
|
def define_requires_all
|
181
181
|
documentation = {
|
182
|
-
required_field: { type: String },
|
183
|
-
optional_field: { type: String }
|
182
|
+
required_field: { type: String, required: true, param_type: 'query' },
|
183
|
+
optional_field: { type: String },
|
184
|
+
optional_array_field: { type: Array[String], is_array: true }
|
184
185
|
}
|
185
186
|
subject.params do
|
186
|
-
requires :all, except:
|
187
|
+
requires :all, except: %i[optional_field optional_array_field], using: documentation
|
187
188
|
end
|
188
189
|
end
|
189
190
|
before do
|
@@ -195,7 +196,7 @@ describe Grape::Validations do
|
|
195
196
|
|
196
197
|
it 'adds entity documentation to declared params' do
|
197
198
|
define_requires_all
|
198
|
-
expect(Grape::Validations::ParamsScope::Attr.attrs_keys(declared_params)).to eq(%i[required_field optional_field])
|
199
|
+
expect(Grape::Validations::ParamsScope::Attr.attrs_keys(declared_params)).to eq(%i[required_field optional_field optional_array_field])
|
199
200
|
end
|
200
201
|
|
201
202
|
it 'errors when required_field is not present' do
|
@@ -214,8 +215,8 @@ describe Grape::Validations do
|
|
214
215
|
context 'requires :none using Grape::Entity documentation' do
|
215
216
|
def define_requires_none
|
216
217
|
documentation = {
|
217
|
-
required_field: { type: String },
|
218
|
-
optional_field: { type:
|
218
|
+
required_field: { type: String, example: 'Foo' },
|
219
|
+
optional_field: { type: Integer, format: 'int64' }
|
219
220
|
}
|
220
221
|
subject.params do
|
221
222
|
requires :none, except: :required_field, using: documentation
|
@@ -1071,12 +1072,12 @@ describe Grape::Validations do
|
|
1071
1072
|
}
|
1072
1073
|
# debugger
|
1073
1074
|
get '/multi_level', data
|
1074
|
-
expect(last_response.body.split(', ')).to
|
1075
|
-
|
1076
|
-
|
1077
|
-
|
1078
|
-
|
1079
|
-
|
1075
|
+
expect(last_response.body.split(', ')).to contain_exactly(
|
1076
|
+
'top[3][top_id] is empty',
|
1077
|
+
'top[2][middle_1][0][middle_1_id] is empty',
|
1078
|
+
'top[1][middle_1][1][middle_2][0][middle_2_id] is empty',
|
1079
|
+
'top[0][middle_1][1][middle_2][1][bottom][0][bottom_id] is empty'
|
1080
|
+
)
|
1080
1081
|
expect(last_response.status).to eq(400)
|
1081
1082
|
end
|
1082
1083
|
end
|
@@ -1509,6 +1510,29 @@ describe Grape::Validations do
|
|
1509
1510
|
end
|
1510
1511
|
end
|
1511
1512
|
|
1513
|
+
context 'with block and empty args' do
|
1514
|
+
before do
|
1515
|
+
subject.helpers do
|
1516
|
+
params :shared_params do |empty_args|
|
1517
|
+
optional :param, default: empty_args[:some]
|
1518
|
+
end
|
1519
|
+
end
|
1520
|
+
subject.format :json
|
1521
|
+
subject.params do
|
1522
|
+
use :shared_params
|
1523
|
+
end
|
1524
|
+
subject.get '/shared_params' do
|
1525
|
+
:ok
|
1526
|
+
end
|
1527
|
+
end
|
1528
|
+
|
1529
|
+
it 'works' do
|
1530
|
+
get '/shared_params'
|
1531
|
+
|
1532
|
+
expect(last_response.status).to eq(200)
|
1533
|
+
end
|
1534
|
+
end
|
1535
|
+
|
1512
1536
|
context 'all or none' do
|
1513
1537
|
context 'optional params' do
|
1514
1538
|
before do
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.shared_examples 'deprecated class' do
|
4
|
+
subject { deprecated_class.new }
|
5
|
+
|
6
|
+
around do |example|
|
7
|
+
old_deprec_behavior = ActiveSupport::Deprecation.behavior
|
8
|
+
ActiveSupport::Deprecation.behavior = :raise
|
9
|
+
example.run
|
10
|
+
ActiveSupport::Deprecation.behavior = old_deprec_behavior
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'raises an ActiveSupport::DeprecationException' do
|
14
|
+
expect { subject }.to raise_error(ActiveSupport::DeprecationException)
|
15
|
+
end
|
16
|
+
end
|