grape 1.7.0 → 1.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|