grape 0.6.1 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of grape might be problematic. Click here for more details.
- checksums.yaml +9 -9
- data/.gitignore +7 -0
- data/.rubocop.yml +5 -0
- data/.travis.yml +3 -3
- data/CHANGELOG.md +42 -3
- data/CONTRIBUTING.md +118 -0
- data/Gemfile +4 -4
- data/README.md +312 -52
- data/Rakefile +6 -1
- data/UPGRADING.md +124 -0
- data/lib/grape.rb +2 -0
- data/lib/grape/api.rb +95 -44
- data/lib/grape/cookies.rb +0 -2
- data/lib/grape/endpoint.rb +63 -39
- data/lib/grape/error_formatter/base.rb +0 -3
- data/lib/grape/error_formatter/json.rb +0 -2
- data/lib/grape/error_formatter/txt.rb +0 -2
- data/lib/grape/error_formatter/xml.rb +0 -2
- data/lib/grape/exceptions/base.rb +0 -2
- data/lib/grape/exceptions/incompatible_option_values.rb +0 -3
- data/lib/grape/exceptions/invalid_formatter.rb +0 -3
- data/lib/grape/exceptions/invalid_versioner_option.rb +0 -4
- data/lib/grape/exceptions/invalid_with_option_for_represent.rb +0 -2
- data/lib/grape/exceptions/missing_mime_type.rb +0 -4
- data/lib/grape/exceptions/missing_option.rb +0 -3
- data/lib/grape/exceptions/missing_vendor_option.rb +0 -3
- data/lib/grape/exceptions/unknown_options.rb +0 -4
- data/lib/grape/exceptions/unknown_validator.rb +0 -2
- data/lib/grape/exceptions/validation_errors.rb +6 -5
- data/lib/grape/formatter/base.rb +0 -3
- data/lib/grape/formatter/json.rb +0 -2
- data/lib/grape/formatter/serializable_hash.rb +15 -16
- data/lib/grape/formatter/txt.rb +0 -2
- data/lib/grape/formatter/xml.rb +0 -2
- data/lib/grape/http/request.rb +2 -4
- data/lib/grape/locale/en.yml +1 -1
- data/lib/grape/middleware/auth/oauth2.rb +15 -6
- data/lib/grape/middleware/base.rb +7 -7
- data/lib/grape/middleware/error.rb +11 -6
- data/lib/grape/middleware/formatter.rb +80 -78
- data/lib/grape/middleware/globals.rb +13 -0
- data/lib/grape/middleware/versioner/accept_version_header.rb +0 -2
- data/lib/grape/middleware/versioner/header.rb +5 -3
- data/lib/grape/middleware/versioner/param.rb +2 -4
- data/lib/grape/middleware/versioner/path.rb +3 -4
- data/lib/grape/namespace.rb +0 -1
- data/lib/grape/parser/base.rb +0 -3
- data/lib/grape/parser/json.rb +0 -2
- data/lib/grape/parser/xml.rb +0 -2
- data/lib/grape/path.rb +1 -3
- data/lib/grape/route.rb +0 -3
- data/lib/grape/util/hash_stack.rb +1 -1
- data/lib/grape/validations.rb +72 -22
- data/lib/grape/validations/coerce.rb +5 -4
- data/lib/grape/validations/default.rb +5 -3
- data/lib/grape/validations/presence.rb +1 -1
- data/lib/grape/validations/regexp.rb +0 -2
- data/lib/grape/validations/values.rb +2 -1
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api_spec.rb +385 -96
- data/spec/grape/endpoint_spec.rb +162 -15
- data/spec/grape/entity_spec.rb +25 -0
- data/spec/grape/exceptions/validation_errors_spec.rb +19 -0
- data/spec/grape/middleware/auth/oauth2_spec.rb +60 -15
- data/spec/grape/middleware/base_spec.rb +3 -8
- data/spec/grape/middleware/error_spec.rb +2 -2
- data/spec/grape/middleware/exception_spec.rb +4 -4
- data/spec/grape/middleware/formatter_spec.rb +7 -4
- data/spec/grape/middleware/versioner/param_spec.rb +8 -7
- data/spec/grape/path_spec.rb +24 -14
- data/spec/grape/util/hash_stack_spec.rb +8 -8
- data/spec/grape/validations/coerce_spec.rb +75 -33
- data/spec/grape/validations/default_spec.rb +57 -0
- data/spec/grape/validations/presence_spec.rb +13 -11
- data/spec/grape/validations/values_spec.rb +76 -2
- data/spec/grape/validations_spec.rb +443 -20
- data/spec/spec_helper.rb +2 -2
- data/spec/support/content_type_helpers.rb +11 -0
- metadata +9 -38
@@ -32,6 +32,38 @@ describe Grape::Validations::DefaultValidator do
|
|
32
32
|
get '/message' do
|
33
33
|
{ id: params[:id], type1: params[:type1], type2: params[:type2] }
|
34
34
|
end
|
35
|
+
|
36
|
+
params do
|
37
|
+
optional :random, default: -> { Random.rand }
|
38
|
+
optional :not_random, default: Random.rand
|
39
|
+
end
|
40
|
+
get '/numbers' do
|
41
|
+
{ random_number: params[:random], non_random_number: params[:non_random_number] }
|
42
|
+
end
|
43
|
+
|
44
|
+
params do
|
45
|
+
# NOTE: The :foo parameter could be made required with json body
|
46
|
+
# params, and then an empty hash would be valid. With query parameters
|
47
|
+
# it must be optional if it isn't provided at all, as otherwise
|
48
|
+
# the validaton for the Hash itself fails because there is no such
|
49
|
+
# thing as an empty hash.
|
50
|
+
optional :foo, type: Hash do
|
51
|
+
optional :bar, default: 'foo-bar'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
get '/group' do
|
55
|
+
{ foo_bar: params[:foo][:bar] }
|
56
|
+
end
|
57
|
+
|
58
|
+
params do
|
59
|
+
optional :array, type: Array do
|
60
|
+
requires :name
|
61
|
+
optional :with_default, default: 'default'
|
62
|
+
end
|
63
|
+
end
|
64
|
+
get '/array' do
|
65
|
+
{ array: params[:array] }
|
66
|
+
end
|
35
67
|
end
|
36
68
|
end
|
37
69
|
end
|
@@ -63,4 +95,29 @@ describe Grape::Validations::DefaultValidator do
|
|
63
95
|
last_response.status.should == 200
|
64
96
|
last_response.body.should == { id: '1', type1: 'default-type1', type2: 'default-type2' }.to_json
|
65
97
|
end
|
98
|
+
|
99
|
+
it 'sets lambda based defaults at the time of call' do
|
100
|
+
get("/numbers")
|
101
|
+
last_response.status.should == 200
|
102
|
+
before = JSON.parse(last_response.body)
|
103
|
+
get("/numbers")
|
104
|
+
last_response.status.should == 200
|
105
|
+
after = JSON.parse(last_response.body)
|
106
|
+
|
107
|
+
before['non_random_number'].should == after['non_random_number']
|
108
|
+
before['random_number'].should_not == after['random_number']
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'set default values for optional grouped params' do
|
112
|
+
get('/group')
|
113
|
+
last_response.status.should == 200
|
114
|
+
last_response.body.should == { foo_bar: 'foo-bar' }.to_json
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'sets default values for grouped arrays' do
|
118
|
+
get('/array?array[][name]=name&array[][name]=name2&array[][with_default]=bar2')
|
119
|
+
last_response.status.should == 200
|
120
|
+
last_response.body.should == { array: [{ name: "name", with_default: "default" }, { name: "name2", with_default: "bar2" }] }.to_json
|
121
|
+
end
|
122
|
+
|
66
123
|
end
|
@@ -28,8 +28,9 @@ describe Grape::Validations::PresenceValidator do
|
|
28
28
|
end
|
29
29
|
|
30
30
|
params do
|
31
|
-
|
32
|
-
requires :first_name
|
31
|
+
requires :user, type: Hash do
|
32
|
+
requires :first_name
|
33
|
+
requires :last_name
|
33
34
|
end
|
34
35
|
end
|
35
36
|
get '/nested' do
|
@@ -37,11 +38,12 @@ describe Grape::Validations::PresenceValidator do
|
|
37
38
|
end
|
38
39
|
|
39
40
|
params do
|
40
|
-
|
41
|
+
requires :admin, type: Hash do
|
41
42
|
requires :admin_name
|
42
|
-
|
43
|
-
|
44
|
-
requires :first_name
|
43
|
+
requires :super, type: Hash do
|
44
|
+
requires :user, type: Hash do
|
45
|
+
requires :first_name
|
46
|
+
requires :last_name
|
45
47
|
end
|
46
48
|
end
|
47
49
|
end
|
@@ -96,7 +98,7 @@ describe Grape::Validations::PresenceValidator do
|
|
96
98
|
it 'validates nested parameters' do
|
97
99
|
get '/nested'
|
98
100
|
last_response.status.should == 400
|
99
|
-
last_response.body.should == '{"error":"user[first_name] is missing"}'
|
101
|
+
last_response.body.should == '{"error":"user is missing, user[first_name] is missing, user[last_name] is missing"}'
|
100
102
|
|
101
103
|
get '/nested', user: { first_name: "Billy" }
|
102
104
|
last_response.status.should == 400
|
@@ -110,19 +112,19 @@ describe Grape::Validations::PresenceValidator do
|
|
110
112
|
it 'validates triple nested parameters' do
|
111
113
|
get '/nested_triple'
|
112
114
|
last_response.status.should == 400
|
113
|
-
last_response.body.should
|
115
|
+
last_response.body.should include '{"error":"admin is missing'
|
114
116
|
|
115
117
|
get '/nested_triple', user: { first_name: "Billy" }
|
116
118
|
last_response.status.should == 400
|
117
|
-
last_response.body.should
|
119
|
+
last_response.body.should include '{"error":"admin is missing'
|
118
120
|
|
119
121
|
get '/nested_triple', admin: { super: { first_name: "Billy" } }
|
120
122
|
last_response.status.should == 400
|
121
|
-
last_response.body.should == '{"error":"admin[admin_name] is missing, admin[super][user][first_name] is missing"}'
|
123
|
+
last_response.body.should == '{"error":"admin[admin_name] is missing, admin[super][user] is missing, admin[super][user][first_name] is missing, admin[super][user][last_name] is missing"}'
|
122
124
|
|
123
125
|
get '/nested_triple', super: { user: { first_name: "Billy", last_name: "Bob" } }
|
124
126
|
last_response.status.should == 400
|
125
|
-
last_response.body.should
|
127
|
+
last_response.body.should include '{"error":"admin is missing'
|
126
128
|
|
127
129
|
get '/nested_triple', admin: { super: { user: { first_name: "Billy" } } }
|
128
130
|
last_response.status.should == 400
|
@@ -2,25 +2,53 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Grape::Validations::ValuesValidator do
|
4
4
|
|
5
|
+
class ValuesModel
|
6
|
+
DEFAULT_VALUES = ['valid-type1', 'valid-type2', 'valid-type3']
|
7
|
+
class << self
|
8
|
+
def values
|
9
|
+
@values ||= []
|
10
|
+
[DEFAULT_VALUES + @values].flatten.uniq
|
11
|
+
end
|
12
|
+
|
13
|
+
def add_value(value)
|
14
|
+
@values ||= []
|
15
|
+
@values << value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
5
20
|
module ValidationsSpec
|
6
21
|
module ValuesValidatorSpec
|
7
22
|
class API < Grape::API
|
8
23
|
default_format :json
|
9
24
|
|
10
25
|
params do
|
11
|
-
requires :type, values:
|
26
|
+
requires :type, values: ValuesModel.values
|
12
27
|
end
|
13
28
|
get '/' do
|
14
29
|
{ type: params[:type] }
|
15
30
|
end
|
16
31
|
|
17
32
|
params do
|
18
|
-
optional :type, values:
|
33
|
+
optional :type, values: ValuesModel.values, default: 'valid-type2'
|
19
34
|
end
|
20
35
|
get '/default/valid' do
|
21
36
|
{ type: params[:type] }
|
22
37
|
end
|
23
38
|
|
39
|
+
params do
|
40
|
+
optional :type, values: -> { ValuesModel.values }, default: 'valid-type2'
|
41
|
+
end
|
42
|
+
get '/lambda' do
|
43
|
+
{ type: params[:type] }
|
44
|
+
end
|
45
|
+
|
46
|
+
params do
|
47
|
+
requires :type, type: Integer, desc: "An integer", values: [10, 11], default: 10
|
48
|
+
end
|
49
|
+
get '/values/coercion' do
|
50
|
+
{ type: params[:type] }
|
51
|
+
end
|
24
52
|
end
|
25
53
|
end
|
26
54
|
end
|
@@ -41,12 +69,46 @@ describe Grape::Validations::ValuesValidator do
|
|
41
69
|
last_response.body.should eq({ error: "type does not have a valid value" }.to_json)
|
42
70
|
end
|
43
71
|
|
72
|
+
it 'does not allow nil value for a parameter' do
|
73
|
+
get("/", type: nil)
|
74
|
+
last_response.status.should eq 400
|
75
|
+
last_response.body.should eq({ error: "type does not have a valid value" }.to_json)
|
76
|
+
end
|
77
|
+
|
44
78
|
it 'allows a valid default value' do
|
45
79
|
get("/default/valid")
|
46
80
|
last_response.status.should eq 200
|
47
81
|
last_response.body.should eq({ type: "valid-type2" }.to_json)
|
48
82
|
end
|
49
83
|
|
84
|
+
it 'allows a proc for values' do
|
85
|
+
get('/lambda', type: 'valid-type1')
|
86
|
+
last_response.status.should eq 200
|
87
|
+
last_response.body.should eq({ type: "valid-type1" }.to_json)
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'does not validate updated values without proc' do
|
91
|
+
ValuesModel.add_value('valid-type4')
|
92
|
+
|
93
|
+
get('/', type: 'valid-type4')
|
94
|
+
last_response.status.should eq 400
|
95
|
+
last_response.body.should eq({ error: "type does not have a valid value" }.to_json)
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'validates against values in a proc' do
|
99
|
+
ValuesModel.add_value('valid-type4')
|
100
|
+
|
101
|
+
get('/lambda', type: 'valid-type4')
|
102
|
+
last_response.status.should eq 200
|
103
|
+
last_response.body.should eq({ type: "valid-type4" }.to_json)
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'does not allow an invalid value for a parameter using lambda' do
|
107
|
+
get("/lambda", type: 'invalid-type')
|
108
|
+
last_response.status.should eq 400
|
109
|
+
last_response.body.should eq({ error: "type does not have a valid value" }.to_json)
|
110
|
+
end
|
111
|
+
|
50
112
|
it 'raises IncompatibleOptionValues on an invalid default value' do
|
51
113
|
subject = Class.new(Grape::API)
|
52
114
|
expect {
|
@@ -61,4 +123,16 @@ describe Grape::Validations::ValuesValidator do
|
|
61
123
|
}.to raise_error Grape::Exceptions::IncompatibleOptionValues
|
62
124
|
end
|
63
125
|
|
126
|
+
it 'allows values to be a kind of the coerced type not just an instance of it' do
|
127
|
+
get("/values/coercion", type: 10)
|
128
|
+
last_response.status.should eq 200
|
129
|
+
last_response.body.should eq({ type: 10 }.to_json)
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'raises IncompatibleOptionValues when values contains a value that is not a kind of the type' do
|
133
|
+
subject = Class.new(Grape::API)
|
134
|
+
expect {
|
135
|
+
subject.params { requires :type, values: [10.5, 11], type: Integer }
|
136
|
+
}.to raise_error Grape::Exceptions::IncompatibleOptionValues
|
137
|
+
end
|
64
138
|
end
|
@@ -78,13 +78,137 @@ describe Grape::Validations do
|
|
78
78
|
end
|
79
79
|
end
|
80
80
|
|
81
|
-
context '
|
81
|
+
context 'requires :all using Grape::Entity documentation' do
|
82
|
+
def define_requires_all
|
83
|
+
documentation = {
|
84
|
+
required_field: { type: String },
|
85
|
+
optional_field: { type: String }
|
86
|
+
}
|
87
|
+
subject.params do
|
88
|
+
requires :all, except: :optional_field, using: documentation
|
89
|
+
end
|
90
|
+
end
|
82
91
|
before do
|
92
|
+
define_requires_all
|
93
|
+
subject.get '/required' do
|
94
|
+
'required works'
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'adds entity documentation to declared params' do
|
99
|
+
define_requires_all
|
100
|
+
subject.settings[:declared_params].should == [:required_field, :optional_field]
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'errors when required_field is not present' do
|
104
|
+
get '/required'
|
105
|
+
last_response.status.should == 400
|
106
|
+
last_response.body.should == 'required_field is missing'
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'works when required_field is present' do
|
110
|
+
get '/required', required_field: 'woof'
|
111
|
+
last_response.status.should == 200
|
112
|
+
last_response.body.should == 'required works'
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context 'requires :none using Grape::Entity documentation' do
|
117
|
+
def define_requires_none
|
118
|
+
documentation = {
|
119
|
+
required_field: { type: String },
|
120
|
+
optional_field: { type: String }
|
121
|
+
}
|
122
|
+
subject.params do
|
123
|
+
requires :none, except: :required_field, using: documentation
|
124
|
+
end
|
125
|
+
end
|
126
|
+
before do
|
127
|
+
define_requires_none
|
128
|
+
subject.get '/required' do
|
129
|
+
'required works'
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'adds entity documentation to declared params' do
|
134
|
+
define_requires_none
|
135
|
+
subject.settings[:declared_params].should == [:required_field, :optional_field]
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'errors when required_field is not present' do
|
139
|
+
get '/required'
|
140
|
+
last_response.status.should == 400
|
141
|
+
last_response.body.should == 'required_field is missing'
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'works when required_field is present' do
|
145
|
+
get '/required', required_field: 'woof'
|
146
|
+
last_response.status.should == 200
|
147
|
+
last_response.body.should == 'required works'
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
context 'required with an Array block' do
|
152
|
+
before do
|
153
|
+
subject.params do
|
154
|
+
requires :items, type: Array do
|
155
|
+
requires :key
|
156
|
+
end
|
157
|
+
end
|
158
|
+
subject.get '/required' do
|
159
|
+
'required works'
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'errors when param not present' do
|
164
|
+
get '/required'
|
165
|
+
last_response.status.should == 400
|
166
|
+
last_response.body.should == 'items is missing'
|
167
|
+
end
|
168
|
+
|
169
|
+
it "errors when param is not an Array" do
|
170
|
+
get '/required', items: "hello"
|
171
|
+
last_response.status.should == 400
|
172
|
+
last_response.body.should == 'items is invalid, items[key] is missing'
|
173
|
+
|
174
|
+
get '/required', items: { key: 'foo' }
|
175
|
+
last_response.status.should == 400
|
176
|
+
last_response.body.should == 'items is invalid'
|
177
|
+
end
|
178
|
+
|
179
|
+
it "doesn't throw a missing param when param is present" do
|
180
|
+
get '/required', items: [{ key: 'hello' }, { key: 'world' }]
|
181
|
+
last_response.status.should == 200
|
182
|
+
last_response.body.should == 'required works'
|
183
|
+
end
|
184
|
+
|
185
|
+
it "doesn't allow any key in the options hash other than type" do
|
186
|
+
expect {
|
187
|
+
subject.params do
|
188
|
+
requires(:items, desc: 'Foo') do
|
189
|
+
requires :key
|
190
|
+
end
|
191
|
+
end
|
192
|
+
}.to raise_error ArgumentError
|
193
|
+
end
|
194
|
+
|
195
|
+
it 'adds to declared parameters' do
|
83
196
|
subject.params do
|
84
197
|
requires :items do
|
85
198
|
requires :key
|
86
199
|
end
|
87
200
|
end
|
201
|
+
subject.settings[:declared_params].should == [items: [:key]]
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
context 'required with a Hash block' do
|
206
|
+
before do
|
207
|
+
subject.params do
|
208
|
+
requires :items, type: Hash do
|
209
|
+
requires :key
|
210
|
+
end
|
211
|
+
end
|
88
212
|
subject.get '/required' do
|
89
213
|
'required works'
|
90
214
|
end
|
@@ -93,16 +217,26 @@ describe Grape::Validations do
|
|
93
217
|
it 'errors when param not present' do
|
94
218
|
get '/required'
|
95
219
|
last_response.status.should == 400
|
96
|
-
last_response.body.should == 'items[key] is missing'
|
220
|
+
last_response.body.should == 'items is missing, items[key] is missing'
|
221
|
+
end
|
222
|
+
|
223
|
+
it "errors when param is not a Hash" do
|
224
|
+
get '/required', items: "hello"
|
225
|
+
last_response.status.should == 400
|
226
|
+
last_response.body.should == 'items is invalid, items[key] is missing'
|
227
|
+
|
228
|
+
get '/required', items: [{ key: 'foo' }]
|
229
|
+
last_response.status.should == 400
|
230
|
+
last_response.body.should == 'items is invalid'
|
97
231
|
end
|
98
232
|
|
99
233
|
it "doesn't throw a missing param when param is present" do
|
100
|
-
get '/required', items:
|
234
|
+
get '/required', items: { key: 'hello' }
|
101
235
|
last_response.status.should == 200
|
102
236
|
last_response.body.should == 'required works'
|
103
237
|
end
|
104
238
|
|
105
|
-
it "doesn't allow
|
239
|
+
it "doesn't allow any key in the options hash other than type" do
|
106
240
|
expect {
|
107
241
|
subject.params do
|
108
242
|
requires(:items, desc: 'Foo') do
|
@@ -137,7 +271,7 @@ describe Grape::Validations do
|
|
137
271
|
it 'errors when param not present' do
|
138
272
|
get '/required'
|
139
273
|
last_response.status.should == 400
|
140
|
-
last_response.body.should == 'items
|
274
|
+
last_response.body.should == 'items is missing'
|
141
275
|
end
|
142
276
|
|
143
277
|
it "doesn't throw a missing param when param is present" do
|
@@ -156,10 +290,206 @@ describe Grape::Validations do
|
|
156
290
|
end
|
157
291
|
end
|
158
292
|
|
159
|
-
context '
|
293
|
+
context 'validation within arrays' do
|
160
294
|
before do
|
161
295
|
subject.params do
|
162
|
-
|
296
|
+
group :children do
|
297
|
+
requires :name
|
298
|
+
group :parents do
|
299
|
+
requires :name
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
subject.get '/within_array' do
|
304
|
+
'within array works'
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
it 'can handle new scopes within child elements' do
|
309
|
+
get '/within_array', children: [
|
310
|
+
{ name: 'John', parents: [{ name: 'Jane' }, { name: 'Bob' }] },
|
311
|
+
{ name: 'Joe', parents: [{ name: 'Josie' }] }
|
312
|
+
]
|
313
|
+
last_response.status.should == 200
|
314
|
+
last_response.body.should == 'within array works'
|
315
|
+
end
|
316
|
+
|
317
|
+
it 'errors when a parameter is not present' do
|
318
|
+
get '/within_array', children: [
|
319
|
+
{ name: 'Jim', parents: [{}] },
|
320
|
+
{ name: 'Job', parents: [{ name: 'Joy' }] }
|
321
|
+
]
|
322
|
+
# NOTE: with body parameters in json or XML or similar this
|
323
|
+
# should actually fail with: children[parents][name] is missing.
|
324
|
+
last_response.status.should == 400
|
325
|
+
last_response.body.should == 'children[parents] is missing'
|
326
|
+
end
|
327
|
+
|
328
|
+
it 'safely handles empty arrays and blank parameters' do
|
329
|
+
# NOTE: with body parameters in json or XML or similar this
|
330
|
+
# should actually return 200, since an empty array is valid.
|
331
|
+
get '/within_array', children: []
|
332
|
+
last_response.status.should == 400
|
333
|
+
last_response.body.should == 'children is missing'
|
334
|
+
get '/within_array', children: [name: 'Jay']
|
335
|
+
last_response.status.should == 400
|
336
|
+
last_response.body.should == 'children[parents] is missing'
|
337
|
+
end
|
338
|
+
|
339
|
+
it "errors when param is not an Array" do
|
340
|
+
# NOTE: would be nicer if these just returned 'children is invalid'
|
341
|
+
get '/within_array', children: "hello"
|
342
|
+
last_response.status.should == 400
|
343
|
+
last_response.body.should == 'children is invalid, children[name] is missing, children[parents] is missing, children[parents] is invalid, children[parents][name] is missing'
|
344
|
+
|
345
|
+
get '/within_array', children: { name: 'foo' }
|
346
|
+
last_response.status.should == 400
|
347
|
+
last_response.body.should == 'children is invalid, children[parents] is missing'
|
348
|
+
|
349
|
+
get '/within_array', children: [name: 'Jay', parents: { name: 'Fred' }]
|
350
|
+
last_response.status.should == 400
|
351
|
+
last_response.body.should == 'children[parents] is invalid'
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
context 'with block param' do
|
356
|
+
before do
|
357
|
+
subject.params do
|
358
|
+
requires :planets do
|
359
|
+
requires :name
|
360
|
+
end
|
361
|
+
end
|
362
|
+
subject.get '/req' do
|
363
|
+
'within array works'
|
364
|
+
end
|
365
|
+
subject.put '/req' do
|
366
|
+
''
|
367
|
+
end
|
368
|
+
|
369
|
+
subject.params do
|
370
|
+
group :stars do
|
371
|
+
requires :name
|
372
|
+
end
|
373
|
+
end
|
374
|
+
subject.get '/grp' do
|
375
|
+
'within array works'
|
376
|
+
end
|
377
|
+
subject.put '/grp' do
|
378
|
+
''
|
379
|
+
end
|
380
|
+
|
381
|
+
subject.params do
|
382
|
+
requires :name
|
383
|
+
optional :moons do
|
384
|
+
requires :name
|
385
|
+
end
|
386
|
+
end
|
387
|
+
subject.get '/opt' do
|
388
|
+
'within array works'
|
389
|
+
end
|
390
|
+
subject.put '/opt' do
|
391
|
+
''
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
it 'requires defaults to Array type' do
|
396
|
+
get '/req', planets: "Jupiter, Saturn"
|
397
|
+
last_response.status.should == 400
|
398
|
+
last_response.body.should == 'planets is invalid, planets[name] is missing'
|
399
|
+
|
400
|
+
get '/req', planets: { name: 'Jupiter' }
|
401
|
+
last_response.status.should == 400
|
402
|
+
last_response.body.should == 'planets is invalid'
|
403
|
+
|
404
|
+
get '/req', planets: [{ name: 'Venus' }, { name: 'Mars' }]
|
405
|
+
last_response.status.should == 200
|
406
|
+
|
407
|
+
put_with_json '/req', planets: []
|
408
|
+
last_response.status.should == 200
|
409
|
+
end
|
410
|
+
|
411
|
+
it 'optional defaults to Array type' do
|
412
|
+
get '/opt', name: "Jupiter", moons: "Europa, Ganymede"
|
413
|
+
last_response.status.should == 400
|
414
|
+
last_response.body.should == 'moons is invalid, moons[name] is missing'
|
415
|
+
|
416
|
+
get '/opt', name: "Jupiter", moons: { name: 'Ganymede' }
|
417
|
+
last_response.status.should == 400
|
418
|
+
last_response.body.should == 'moons is invalid'
|
419
|
+
|
420
|
+
get '/opt', name: "Jupiter", moons: [{ name: 'Io' }, { name: 'Callisto' }]
|
421
|
+
last_response.status.should == 200
|
422
|
+
|
423
|
+
put_with_json '/opt', name: "Venus"
|
424
|
+
last_response.status.should == 200
|
425
|
+
|
426
|
+
put_with_json '/opt', name: "Mercury", moons: []
|
427
|
+
last_response.status.should == 200
|
428
|
+
end
|
429
|
+
|
430
|
+
it 'group defaults to Array type' do
|
431
|
+
get '/grp', stars: "Sun"
|
432
|
+
last_response.status.should == 400
|
433
|
+
last_response.body.should == 'stars is invalid, stars[name] is missing'
|
434
|
+
|
435
|
+
get '/grp', stars: { name: 'Sun' }
|
436
|
+
last_response.status.should == 400
|
437
|
+
last_response.body.should == 'stars is invalid'
|
438
|
+
|
439
|
+
get '/grp', stars: [{ name: 'Sun' }]
|
440
|
+
last_response.status.should == 200
|
441
|
+
|
442
|
+
put_with_json '/grp', stars: []
|
443
|
+
last_response.status.should == 200
|
444
|
+
end
|
445
|
+
end
|
446
|
+
|
447
|
+
context 'validation within arrays with JSON' do
|
448
|
+
before do
|
449
|
+
subject.params do
|
450
|
+
group :children do
|
451
|
+
requires :name
|
452
|
+
group :parents do
|
453
|
+
requires :name
|
454
|
+
end
|
455
|
+
end
|
456
|
+
end
|
457
|
+
subject.put '/within_array' do
|
458
|
+
'within array works'
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
462
|
+
it 'can handle new scopes within child elements' do
|
463
|
+
put_with_json '/within_array', children: [
|
464
|
+
{ name: 'John', parents: [{ name: 'Jane' }, { name: 'Bob' }] },
|
465
|
+
{ name: 'Joe', parents: [{ name: 'Josie' }] }
|
466
|
+
]
|
467
|
+
last_response.status.should == 200
|
468
|
+
last_response.body.should == 'within array works'
|
469
|
+
end
|
470
|
+
|
471
|
+
it 'errors when a parameter is not present' do
|
472
|
+
put_with_json '/within_array', children: [
|
473
|
+
{ name: 'Jim', parents: [{}] },
|
474
|
+
{ name: 'Job', parents: [{ name: 'Joy' }] }
|
475
|
+
]
|
476
|
+
last_response.status.should == 400
|
477
|
+
last_response.body.should == 'children[parents][name] is missing'
|
478
|
+
end
|
479
|
+
|
480
|
+
it 'safely handles empty arrays and blank parameters' do
|
481
|
+
put_with_json '/within_array', children: []
|
482
|
+
last_response.status.should == 200
|
483
|
+
put_with_json '/within_array', children: [name: 'Jay']
|
484
|
+
last_response.status.should == 400
|
485
|
+
last_response.body.should == 'children[parents] is missing'
|
486
|
+
end
|
487
|
+
end
|
488
|
+
|
489
|
+
context 'optional with an Array block' do
|
490
|
+
before do
|
491
|
+
subject.params do
|
492
|
+
optional :items, type: Array do
|
163
493
|
requires :key
|
164
494
|
end
|
165
495
|
end
|
@@ -175,17 +505,27 @@ describe Grape::Validations do
|
|
175
505
|
end
|
176
506
|
|
177
507
|
it "doesn't throw a missing param when both group and param are given" do
|
178
|
-
get '/optional_group', items: { key: 'foo' }
|
508
|
+
get '/optional_group', items: [{ key: 'foo' }]
|
179
509
|
last_response.status.should == 200
|
180
510
|
last_response.body.should == 'optional group works'
|
181
511
|
end
|
182
512
|
|
183
513
|
it "errors when group is present, but required param is not" do
|
184
|
-
get '/optional_group', items: { not_key: 'foo' }
|
514
|
+
get '/optional_group', items: [{ not_key: 'foo' }]
|
185
515
|
last_response.status.should == 400
|
186
516
|
last_response.body.should == 'items[key] is missing'
|
187
517
|
end
|
188
518
|
|
519
|
+
it "errors when param is present but isn't an Array" do
|
520
|
+
get '/optional_group', items: "hello"
|
521
|
+
last_response.status.should == 400
|
522
|
+
last_response.body.should == 'items is invalid, items[key] is missing'
|
523
|
+
|
524
|
+
get '/optional_group', items: { key: 'foo' }
|
525
|
+
last_response.status.should == 400
|
526
|
+
last_response.body.should == 'items is invalid'
|
527
|
+
end
|
528
|
+
|
189
529
|
it 'adds to declared parameters' do
|
190
530
|
subject.params do
|
191
531
|
optional :items do
|
@@ -196,13 +536,13 @@ describe Grape::Validations do
|
|
196
536
|
end
|
197
537
|
end
|
198
538
|
|
199
|
-
context 'nested optional blocks' do
|
539
|
+
context 'nested optional Array blocks' do
|
200
540
|
before do
|
201
541
|
subject.params do
|
202
|
-
optional :items do
|
542
|
+
optional :items, type: Array do
|
203
543
|
requires :key
|
204
|
-
optional(:optional_subitems) { requires :value }
|
205
|
-
requires(:required_subitems) { requires :value }
|
544
|
+
optional(:optional_subitems, type: Array) { requires :value }
|
545
|
+
requires(:required_subitems, type: Array) { requires :value }
|
206
546
|
end
|
207
547
|
end
|
208
548
|
subject.get('/nested_optional_group') { 'nested optional group works' }
|
@@ -215,25 +555,39 @@ describe Grape::Validations do
|
|
215
555
|
end
|
216
556
|
|
217
557
|
it 'does internal validations if the outer group is present' do
|
218
|
-
get '/nested_optional_group', items: { key: 'foo' }
|
558
|
+
get '/nested_optional_group', items: [{ key: 'foo' }]
|
219
559
|
last_response.status.should == 400
|
220
|
-
last_response.body.should == 'items[required_subitems]
|
560
|
+
last_response.body.should == 'items[required_subitems] is missing'
|
221
561
|
|
222
|
-
get '/nested_optional_group', items: { key: 'foo', required_subitems: { value: 'bar' } }
|
562
|
+
get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }] }]
|
223
563
|
last_response.status.should == 200
|
224
564
|
last_response.body.should == 'nested optional group works'
|
225
565
|
end
|
226
566
|
|
227
567
|
it 'handles deep nesting' do
|
228
|
-
get '/nested_optional_group', items: { key: 'foo', required_subitems: { value: 'bar' }, optional_subitems: { not_value: 'baz' } }
|
568
|
+
get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }], optional_subitems: [{ not_value: 'baz' }] }]
|
229
569
|
last_response.status.should == 400
|
230
570
|
last_response.body.should == 'items[optional_subitems][value] is missing'
|
231
571
|
|
232
|
-
get '/nested_optional_group', items: { key: 'foo', required_subitems: { value: 'bar' }, optional_subitems: { value: 'baz' } }
|
572
|
+
get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }], optional_subitems: [{ value: 'baz' }] }]
|
233
573
|
last_response.status.should == 200
|
234
574
|
last_response.body.should == 'nested optional group works'
|
235
575
|
end
|
236
576
|
|
577
|
+
it 'handles validation within arrays' do
|
578
|
+
get '/nested_optional_group', items: [{ key: 'foo' }]
|
579
|
+
last_response.status.should == 400
|
580
|
+
last_response.body.should == 'items[required_subitems] is missing'
|
581
|
+
|
582
|
+
get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }] }]
|
583
|
+
last_response.status.should == 200
|
584
|
+
last_response.body.should == 'nested optional group works'
|
585
|
+
|
586
|
+
get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }], optional_subitems: [{ not_value: 'baz' }] }]
|
587
|
+
last_response.status.should == 400
|
588
|
+
last_response.body.should == 'items[optional_subitems][value] is missing'
|
589
|
+
end
|
590
|
+
|
237
591
|
it 'adds to declared parameters' do
|
238
592
|
subject.params do
|
239
593
|
optional :items do
|
@@ -287,11 +641,11 @@ describe Grape::Validations do
|
|
287
641
|
end
|
288
642
|
|
289
643
|
it 'validates when param is present' do
|
290
|
-
get '/optional_custom',
|
644
|
+
get '/optional_custom', custom: 'im custom'
|
291
645
|
last_response.status.should == 200
|
292
646
|
last_response.body.should == 'optional with custom works!'
|
293
647
|
|
294
|
-
get '/optional_custom',
|
648
|
+
get '/optional_custom', custom: 'im wrong'
|
295
649
|
last_response.status.should == 400
|
296
650
|
last_response.body.should == 'custom is not custom!'
|
297
651
|
end
|
@@ -414,5 +768,74 @@ describe Grape::Validations do
|
|
414
768
|
end
|
415
769
|
end
|
416
770
|
end # end custom validation
|
771
|
+
|
772
|
+
context 'named' do
|
773
|
+
context 'can be defined' do
|
774
|
+
it 'in helpers' do
|
775
|
+
subject.helpers do
|
776
|
+
params :pagination do
|
777
|
+
end
|
778
|
+
end
|
779
|
+
end
|
780
|
+
|
781
|
+
it 'in helper module which kind of Grape::API::Helpers' do
|
782
|
+
module SharedParams
|
783
|
+
extend Grape::API::Helpers
|
784
|
+
params :pagination do
|
785
|
+
end
|
786
|
+
end
|
787
|
+
subject.helpers SharedParams
|
788
|
+
end
|
789
|
+
end
|
790
|
+
|
791
|
+
context 'can be included in usual params' do
|
792
|
+
before do
|
793
|
+
module SharedParams
|
794
|
+
extend Grape::API::Helpers
|
795
|
+
params :period do
|
796
|
+
optional :start_date
|
797
|
+
optional :end_date
|
798
|
+
end
|
799
|
+
end
|
800
|
+
subject.helpers SharedParams
|
801
|
+
|
802
|
+
subject.helpers do
|
803
|
+
params :pagination do
|
804
|
+
optional :page, type: Integer
|
805
|
+
optional :per_page, type: Integer
|
806
|
+
end
|
807
|
+
end
|
808
|
+
end
|
809
|
+
|
810
|
+
it 'by #use' do
|
811
|
+
subject.params do
|
812
|
+
use :pagination
|
813
|
+
end
|
814
|
+
subject.settings[:declared_params].should eq [:page, :per_page]
|
815
|
+
end
|
816
|
+
|
817
|
+
it 'by #use with multiple params' do
|
818
|
+
subject.params do
|
819
|
+
use :pagination, :period
|
820
|
+
end
|
821
|
+
subject.settings[:declared_params].should eq [:page, :per_page, :start_date, :end_date]
|
822
|
+
end
|
823
|
+
|
824
|
+
end
|
825
|
+
end
|
826
|
+
|
827
|
+
context 'documentation' do
|
828
|
+
it 'can be included with a hash' do
|
829
|
+
documentation = { example: 'Joe' }
|
830
|
+
|
831
|
+
subject.params do
|
832
|
+
requires 'first_name', documentation: documentation
|
833
|
+
end
|
834
|
+
subject.get '/' do
|
835
|
+
end
|
836
|
+
|
837
|
+
subject.routes.first.route_params['first_name'][:documentation].should eq(documentation)
|
838
|
+
end
|
839
|
+
end
|
417
840
|
end
|
418
841
|
end
|