grape 1.5.0 → 1.6.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 +51 -2
- data/README.md +43 -10
- data/UPGRADING.md +91 -0
- data/grape.gemspec +5 -5
- data/lib/grape/api/instance.rb +13 -17
- data/lib/grape/api.rb +7 -14
- data/lib/grape/cookies.rb +2 -0
- data/lib/grape/dsl/callbacks.rb +1 -1
- data/lib/grape/dsl/desc.rb +3 -5
- data/lib/grape/dsl/helpers.rb +6 -4
- data/lib/grape/dsl/inside_route.rb +18 -9
- data/lib/grape/dsl/middleware.rb +4 -4
- data/lib/grape/dsl/parameters.rb +11 -7
- data/lib/grape/dsl/request_response.rb +9 -6
- data/lib/grape/dsl/routing.rb +7 -6
- data/lib/grape/dsl/settings.rb +5 -5
- data/lib/grape/endpoint.rb +21 -36
- data/lib/grape/error_formatter/json.rb +2 -6
- data/lib/grape/error_formatter/xml.rb +2 -6
- data/lib/grape/exceptions/empty_message_body.rb +11 -0
- data/lib/grape/exceptions/validation.rb +2 -3
- data/lib/grape/exceptions/validation_errors.rb +1 -1
- data/lib/grape/formatter/json.rb +1 -0
- data/lib/grape/formatter/serializable_hash.rb +2 -1
- data/lib/grape/formatter/xml.rb +1 -0
- data/lib/grape/locale/en.yml +1 -1
- data/lib/grape/middleware/auth/base.rb +3 -3
- data/lib/grape/middleware/base.rb +4 -2
- data/lib/grape/middleware/error.rb +1 -1
- data/lib/grape/middleware/formatter.rb +4 -4
- data/lib/grape/middleware/stack.rb +10 -16
- data/lib/grape/middleware/versioner/accept_version_header.rb +3 -5
- data/lib/grape/middleware/versioner/header.rb +6 -4
- data/lib/grape/middleware/versioner/param.rb +1 -0
- data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
- data/lib/grape/middleware/versioner/path.rb +2 -0
- data/lib/grape/parser/json.rb +1 -1
- data/lib/grape/parser/xml.rb +1 -1
- data/lib/grape/path.rb +1 -0
- data/lib/grape/request.rb +3 -0
- data/lib/grape/router/attribute_translator.rb +1 -1
- data/lib/grape/router/pattern.rb +1 -1
- data/lib/grape/router/route.rb +2 -2
- data/lib/grape/router.rb +6 -0
- data/lib/grape/util/inheritable_setting.rb +1 -3
- data/lib/grape/util/lazy_value.rb +3 -2
- data/lib/grape/validations/attributes_iterator.rb +8 -0
- data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
- data/lib/grape/validations/params_scope.rb +92 -58
- data/lib/grape/validations/single_attribute_iterator.rb +1 -1
- data/lib/grape/validations/types/custom_type_coercer.rb +3 -2
- data/lib/grape/validations/types/dry_type_coercer.rb +1 -1
- data/lib/grape/validations/types/invalid_value.rb +24 -0
- data/lib/grape/validations/types/json.rb +2 -1
- data/lib/grape/validations/types/primitive_coercer.rb +3 -3
- data/lib/grape/validations/types.rb +1 -4
- data/lib/grape/validations/validator_factory.rb +1 -1
- data/lib/grape/validations/validators/all_or_none.rb +1 -0
- data/lib/grape/validations/validators/as.rb +4 -8
- data/lib/grape/validations/validators/at_least_one_of.rb +1 -0
- data/lib/grape/validations/validators/base.rb +12 -7
- data/lib/grape/validations/validators/coerce.rb +8 -9
- data/lib/grape/validations/validators/default.rb +1 -0
- data/lib/grape/validations/validators/exactly_one_of.rb +1 -0
- data/lib/grape/validations/validators/multiple_params_base.rb +5 -2
- data/lib/grape/validations/validators/mutual_exclusion.rb +1 -0
- data/lib/grape/validations/validators/presence.rb +1 -0
- data/lib/grape/validations/validators/regexp.rb +1 -0
- data/lib/grape/validations/validators/same_as.rb +1 -0
- data/lib/grape/validations/validators/values.rb +3 -0
- data/lib/grape/version.rb +1 -1
- data/lib/grape.rb +3 -1
- data/spec/grape/api/custom_validations_spec.rb +1 -0
- data/spec/grape/api/routes_with_requirements_spec.rb +8 -8
- data/spec/grape/api_remount_spec.rb +9 -4
- data/spec/grape/api_spec.rb +203 -37
- data/spec/grape/dsl/callbacks_spec.rb +1 -1
- data/spec/grape/dsl/middleware_spec.rb +1 -1
- data/spec/grape/dsl/parameters_spec.rb +1 -0
- data/spec/grape/dsl/routing_spec.rb +1 -1
- data/spec/grape/endpoint/declared_spec.rb +259 -1
- data/spec/grape/endpoint_spec.rb +18 -5
- data/spec/grape/entity_spec.rb +10 -10
- data/spec/grape/middleware/auth/dsl_spec.rb +1 -1
- data/spec/grape/middleware/error_spec.rb +1 -2
- data/spec/grape/middleware/formatter_spec.rb +2 -2
- data/spec/grape/middleware/stack_spec.rb +4 -3
- data/spec/grape/request_spec.rb +1 -1
- data/spec/grape/validations/multiple_attributes_iterator_spec.rb +13 -3
- data/spec/grape/validations/params_scope_spec.rb +37 -3
- data/spec/grape/validations/single_attribute_iterator_spec.rb +17 -6
- data/spec/grape/validations/types/primitive_coercer_spec.rb +2 -2
- data/spec/grape/validations/validators/coerce_spec.rb +129 -22
- data/spec/grape/validations/validators/except_values_spec.rb +2 -2
- data/spec/grape/validations/validators/values_spec.rb +15 -11
- data/spec/grape/validations_spec.rb +280 -0
- data/spec/shared/versioning_examples.rb +22 -22
- data/spec/spec_helper.rb +1 -1
- data/spec/support/basic_auth_encode_helpers.rb +1 -1
- data/spec/support/versioned_helpers.rb +1 -1
- metadata +8 -6
@@ -31,9 +31,9 @@ describe Grape::Validations::CoerceValidator do
|
|
31
31
|
|
32
32
|
it 'i18n error on malformed input' do
|
33
33
|
I18n.available_locales = %i[en zh-CN]
|
34
|
-
I18n.load_path << File.expand_path('
|
34
|
+
I18n.load_path << File.expand_path('zh-CN.yml', __dir__)
|
35
35
|
I18n.reload!
|
36
|
-
I18n.locale = 'zh-CN'
|
36
|
+
I18n.locale = :'zh-CN'
|
37
37
|
subject.params do
|
38
38
|
requires :age, type: Integer
|
39
39
|
end
|
@@ -48,7 +48,7 @@ describe Grape::Validations::CoerceValidator do
|
|
48
48
|
|
49
49
|
it 'gives an english fallback error when default locale message is blank' do
|
50
50
|
I18n.available_locales = %i[en pt-BR]
|
51
|
-
I18n.locale = 'pt-BR'
|
51
|
+
I18n.locale = :'pt-BR'
|
52
52
|
subject.params do
|
53
53
|
requires :age, type: Integer
|
54
54
|
end
|
@@ -84,9 +84,10 @@ describe Grape::Validations::CoerceValidator do
|
|
84
84
|
before do
|
85
85
|
subject.params do
|
86
86
|
requires :a, types: { value: [Boolean, String], message: 'type cast is invalid' }, coerce_with: (lambda do |val|
|
87
|
-
|
87
|
+
case val
|
88
|
+
when 'yup'
|
88
89
|
true
|
89
|
-
|
90
|
+
when 'false'
|
90
91
|
0
|
91
92
|
else
|
92
93
|
val
|
@@ -227,23 +228,51 @@ describe Grape::Validations::CoerceValidator do
|
|
227
228
|
expect(last_response.body).to eq('NilClass')
|
228
229
|
end
|
229
230
|
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
231
|
+
context 'a custom type' do
|
232
|
+
it 'coerces the given value' do
|
233
|
+
subject.params do
|
234
|
+
requires :uri, coerce: SecureURIOnly
|
235
|
+
end
|
236
|
+
subject.get '/secure_uri' do
|
237
|
+
params[:uri].class
|
238
|
+
end
|
239
|
+
|
240
|
+
get 'secure_uri', uri: 'https://www.example.com'
|
241
|
+
|
242
|
+
expect(last_response.status).to eq(200)
|
243
|
+
expect(last_response.body).to eq('URI::HTTPS')
|
244
|
+
|
245
|
+
get 'secure_uri', uri: 'http://www.example.com'
|
246
|
+
|
247
|
+
expect(last_response.status).to eq(400)
|
248
|
+
expect(last_response.body).to eq('uri is invalid')
|
236
249
|
end
|
237
250
|
|
238
|
-
|
251
|
+
context 'returning the InvalidValue instance when invalid' do
|
252
|
+
let(:custom_type) do
|
253
|
+
Class.new do
|
254
|
+
def self.parse(_val)
|
255
|
+
Grape::Types::InvalidValue.new('must be unique')
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
it 'uses a custom message added to the invalid value' do
|
261
|
+
type = custom_type
|
239
262
|
|
240
|
-
|
241
|
-
|
263
|
+
subject.params do
|
264
|
+
requires :name, type: type
|
265
|
+
end
|
266
|
+
subject.get '/whatever' do
|
267
|
+
params[:name].class
|
268
|
+
end
|
242
269
|
|
243
|
-
|
270
|
+
get 'whatever', name: 'Bob'
|
244
271
|
|
245
|
-
|
246
|
-
|
272
|
+
expect(last_response.status).to eq(400)
|
273
|
+
expect(last_response.body).to eq('name must be unique')
|
274
|
+
end
|
275
|
+
end
|
247
276
|
end
|
248
277
|
|
249
278
|
context 'Array' do
|
@@ -622,7 +651,7 @@ describe Grape::Validations::CoerceValidator do
|
|
622
651
|
|
623
652
|
it 'parses parameters with Array[Array[String]] type and coerce_with' do
|
624
653
|
subject.params do
|
625
|
-
requires :values, type: Array[Array[String]], coerce_with: ->(val) { val.is_a?(String) ? [val.split(
|
654
|
+
requires :values, type: Array[Array[String]], coerce_with: ->(val) { val.is_a?(String) ? [val.split(',').map(&:strip)] : val }
|
626
655
|
end
|
627
656
|
subject.post '/coerce_nested_strings' do
|
628
657
|
params[:values]
|
@@ -678,6 +707,44 @@ describe Grape::Validations::CoerceValidator do
|
|
678
707
|
expect(JSON.parse(last_response.body)).to eq([1, 1, 1, 1])
|
679
708
|
end
|
680
709
|
|
710
|
+
context 'Array type and coerce_with should' do
|
711
|
+
before do
|
712
|
+
subject.params do
|
713
|
+
optional :arr, type: Array, coerce_with: (lambda do |val|
|
714
|
+
if val.nil?
|
715
|
+
[]
|
716
|
+
else
|
717
|
+
val
|
718
|
+
end
|
719
|
+
end)
|
720
|
+
end
|
721
|
+
subject.get '/' do
|
722
|
+
params[:arr].class.to_s
|
723
|
+
end
|
724
|
+
end
|
725
|
+
|
726
|
+
it 'coerce nil value to array' do
|
727
|
+
get '/', arr: nil
|
728
|
+
|
729
|
+
expect(last_response.status).to eq(200)
|
730
|
+
expect(last_response.body).to eq('Array')
|
731
|
+
end
|
732
|
+
|
733
|
+
it 'not coerce missing field' do
|
734
|
+
get '/'
|
735
|
+
|
736
|
+
expect(last_response.status).to eq(200)
|
737
|
+
expect(last_response.body).to eq('NilClass')
|
738
|
+
end
|
739
|
+
|
740
|
+
it 'coerce array as array' do
|
741
|
+
get '/', arr: []
|
742
|
+
|
743
|
+
expect(last_response.status).to eq(200)
|
744
|
+
expect(last_response.body).to eq('Array')
|
745
|
+
end
|
746
|
+
end
|
747
|
+
|
681
748
|
it 'uses parse where available' do
|
682
749
|
subject.params do
|
683
750
|
requires :ints, type: Array, coerce_with: JSON do
|
@@ -726,13 +793,52 @@ describe Grape::Validations::CoerceValidator do
|
|
726
793
|
expect(last_response.body).to eq('3')
|
727
794
|
end
|
728
795
|
|
796
|
+
context 'Integer type and coerce_with should' do
|
797
|
+
before do
|
798
|
+
subject.params do
|
799
|
+
optional :int, type: Integer, coerce_with: (lambda do |val|
|
800
|
+
if val.nil?
|
801
|
+
0
|
802
|
+
else
|
803
|
+
val.to_i
|
804
|
+
end
|
805
|
+
end)
|
806
|
+
end
|
807
|
+
subject.get '/' do
|
808
|
+
params[:int].class.to_s
|
809
|
+
end
|
810
|
+
end
|
811
|
+
|
812
|
+
it 'coerce nil value to integer' do
|
813
|
+
get '/', int: nil
|
814
|
+
|
815
|
+
expect(last_response.status).to eq(200)
|
816
|
+
expect(last_response.body).to eq('Integer')
|
817
|
+
end
|
818
|
+
|
819
|
+
it 'not coerce missing field' do
|
820
|
+
get '/'
|
821
|
+
|
822
|
+
expect(last_response.status).to eq(200)
|
823
|
+
expect(last_response.body).to eq('NilClass')
|
824
|
+
end
|
825
|
+
|
826
|
+
it 'coerce integer as integer' do
|
827
|
+
get '/', int: 1
|
828
|
+
|
829
|
+
expect(last_response.status).to eq(200)
|
830
|
+
expect(last_response.body).to eq('Integer')
|
831
|
+
end
|
832
|
+
end
|
833
|
+
|
729
834
|
context 'Integer type and coerce_with potentially returning nil' do
|
730
835
|
before do
|
731
836
|
subject.params do
|
732
837
|
requires :int, type: Integer, coerce_with: (lambda do |val|
|
733
|
-
|
838
|
+
case val
|
839
|
+
when '0'
|
734
840
|
nil
|
735
|
-
|
841
|
+
when /^-?\d+$/
|
736
842
|
val.to_i
|
737
843
|
else
|
738
844
|
val
|
@@ -1097,9 +1203,10 @@ describe Grape::Validations::CoerceValidator do
|
|
1097
1203
|
before do
|
1098
1204
|
subject.params do
|
1099
1205
|
requires :a, types: [Boolean, String], coerce_with: (lambda do |val|
|
1100
|
-
|
1206
|
+
case val
|
1207
|
+
when 'yup'
|
1101
1208
|
true
|
1102
|
-
|
1209
|
+
when 'false'
|
1103
1210
|
0
|
1104
1211
|
else
|
1105
1212
|
val
|
@@ -5,7 +5,7 @@ require 'spec_helper'
|
|
5
5
|
describe Grape::Validations::ExceptValuesValidator do
|
6
6
|
module ValidationsSpec
|
7
7
|
class ExceptValuesModel
|
8
|
-
DEFAULT_EXCEPTS = [
|
8
|
+
DEFAULT_EXCEPTS = %w[invalid-type1 invalid-type2 invalid-type3].freeze
|
9
9
|
class << self
|
10
10
|
attr_accessor :excepts
|
11
11
|
|
@@ -170,7 +170,7 @@ describe Grape::Validations::ExceptValuesValidator do
|
|
170
170
|
it 'raises IncompatibleOptionValues when type is incompatible with values array' do
|
171
171
|
subject = Class.new(Grape::API)
|
172
172
|
expect do
|
173
|
-
subject.params { optional :type, except_values: [
|
173
|
+
subject.params { optional :type, except_values: %w[valid-type1 valid-type2 valid-type3], type: Symbol }
|
174
174
|
end.to raise_error Grape::Exceptions::IncompatibleOptionValues
|
175
175
|
end
|
176
176
|
|
@@ -5,8 +5,8 @@ require 'spec_helper'
|
|
5
5
|
describe Grape::Validations::ValuesValidator do
|
6
6
|
module ValidationsSpec
|
7
7
|
class ValuesModel
|
8
|
-
DEFAULT_VALUES = [
|
9
|
-
DEFAULT_EXCEPTS = [
|
8
|
+
DEFAULT_VALUES = %w[valid-type1 valid-type2 valid-type3].freeze
|
9
|
+
DEFAULT_EXCEPTS = %w[invalid-type1 invalid-type2 invalid-type3].freeze
|
10
10
|
class << self
|
11
11
|
def values
|
12
12
|
@values ||= []
|
@@ -27,6 +27,10 @@ describe Grape::Validations::ValuesValidator do
|
|
27
27
|
@excepts ||= []
|
28
28
|
@excepts << except
|
29
29
|
end
|
30
|
+
|
31
|
+
def include?(value)
|
32
|
+
values.include?(value)
|
33
|
+
end
|
30
34
|
end
|
31
35
|
end
|
32
36
|
|
@@ -106,7 +110,7 @@ describe Grape::Validations::ValuesValidator do
|
|
106
110
|
end
|
107
111
|
|
108
112
|
params do
|
109
|
-
requires :type, values: ->(v) { ValuesModel.
|
113
|
+
requires :type, values: ->(v) { ValuesModel.include? v }
|
110
114
|
end
|
111
115
|
get '/lambda_val' do
|
112
116
|
{ type: params[:type] }
|
@@ -214,14 +218,14 @@ describe Grape::Validations::ValuesValidator do
|
|
214
218
|
put '/optional_with_array_of_string_values'
|
215
219
|
|
216
220
|
params do
|
217
|
-
requires :type, values: { proc: ->(v) { ValuesModel.
|
221
|
+
requires :type, values: { proc: ->(v) { ValuesModel.include? v } }
|
218
222
|
end
|
219
223
|
get '/proc' do
|
220
224
|
{ type: params[:type] }
|
221
225
|
end
|
222
226
|
|
223
227
|
params do
|
224
|
-
requires :type, values: { proc: ->(v) { ValuesModel.
|
228
|
+
requires :type, values: { proc: ->(v) { ValuesModel.include? v }, message: 'failed check' }
|
225
229
|
end
|
226
230
|
get '/proc/message'
|
227
231
|
|
@@ -420,21 +424,21 @@ describe Grape::Validations::ValuesValidator do
|
|
420
424
|
it 'raises IncompatibleOptionValues on an invalid default value from proc' do
|
421
425
|
subject = Class.new(Grape::API)
|
422
426
|
expect do
|
423
|
-
subject.params { optional :type, values: [
|
427
|
+
subject.params { optional :type, values: %w[valid-type1 valid-type2 valid-type3], default: "#{ValidationsSpec::ValuesModel.values.sample}_invalid" }
|
424
428
|
end.to raise_error Grape::Exceptions::IncompatibleOptionValues
|
425
429
|
end
|
426
430
|
|
427
431
|
it 'raises IncompatibleOptionValues on an invalid default value' do
|
428
432
|
subject = Class.new(Grape::API)
|
429
433
|
expect do
|
430
|
-
subject.params { optional :type, values: [
|
434
|
+
subject.params { optional :type, values: %w[valid-type1 valid-type2 valid-type3], default: 'invalid-type' }
|
431
435
|
end.to raise_error Grape::Exceptions::IncompatibleOptionValues
|
432
436
|
end
|
433
437
|
|
434
438
|
it 'raises IncompatibleOptionValues when type is incompatible with values array' do
|
435
439
|
subject = Class.new(Grape::API)
|
436
440
|
expect do
|
437
|
-
subject.params { optional :type, values: [
|
441
|
+
subject.params { optional :type, values: %w[valid-type1 valid-type2 valid-type3], type: Symbol }
|
438
442
|
end.to raise_error Grape::Exceptions::IncompatibleOptionValues
|
439
443
|
end
|
440
444
|
|
@@ -648,9 +652,9 @@ describe Grape::Validations::ValuesValidator do
|
|
648
652
|
end
|
649
653
|
|
650
654
|
it 'accepts multiple valid values' do
|
651
|
-
get '/proc', type: [
|
655
|
+
get '/proc', type: %w[valid-type1 valid-type3]
|
652
656
|
expect(last_response.status).to eq 200
|
653
|
-
expect(last_response.body).to eq({ type: [
|
657
|
+
expect(last_response.body).to eq({ type: %w[valid-type1 valid-type3] }.to_json)
|
654
658
|
end
|
655
659
|
|
656
660
|
it 'rejects a single invalid value' do
|
@@ -660,7 +664,7 @@ describe Grape::Validations::ValuesValidator do
|
|
660
664
|
end
|
661
665
|
|
662
666
|
it 'rejects an invalid value among valid ones' do
|
663
|
-
get '/proc', type: [
|
667
|
+
get '/proc', type: %w[valid-type1 invalid-type1 valid-type3]
|
664
668
|
expect(last_response.status).to eq 400
|
665
669
|
expect(last_response.body).to eq({ error: 'type does not have a valid value' }.to_json)
|
666
670
|
end
|
@@ -494,6 +494,7 @@ describe Grape::Validations do
|
|
494
494
|
class DateRangeValidator < Grape::Validations::Base
|
495
495
|
def validate_param!(attr_name, params)
|
496
496
|
return if params[attr_name][:from] <= params[attr_name][:to]
|
497
|
+
|
497
498
|
raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: "'from' must be lower or equal to 'to'")
|
498
499
|
end
|
499
500
|
end
|
@@ -883,6 +884,283 @@ describe Grape::Validations do
|
|
883
884
|
end
|
884
885
|
expect(declared_params).to eq([items: [:key, { optional_subitems: [:value] }, { required_subitems: [:value] }]])
|
885
886
|
end
|
887
|
+
|
888
|
+
context <<~DESC do
|
889
|
+
Issue occurs whenever:
|
890
|
+
* param structure with at least three levels
|
891
|
+
* 1st level item is a required Array that has >1 entry with an optional item present and >1 entry with an optional item missing#{' '}
|
892
|
+
* 2nd level is an optional Array or Hash#{' '}
|
893
|
+
* 3rd level is a required item (can be any type)
|
894
|
+
* additional levels do not effect the issue from occuring
|
895
|
+
DESC
|
896
|
+
|
897
|
+
it 'example based off actual real world use case' do
|
898
|
+
subject.params do
|
899
|
+
requires :orders, type: Array do
|
900
|
+
requires :id, type: Integer
|
901
|
+
optional :drugs, type: Array do
|
902
|
+
requires :batches, type: Array do
|
903
|
+
requires :batch_no, type: String
|
904
|
+
end
|
905
|
+
end
|
906
|
+
end
|
907
|
+
end
|
908
|
+
|
909
|
+
subject.get '/validate_required_arrays_under_optional_arrays' do
|
910
|
+
'validate_required_arrays_under_optional_arrays works!'
|
911
|
+
end
|
912
|
+
|
913
|
+
data = {
|
914
|
+
orders: [
|
915
|
+
{ id: 77, drugs: [{ batches: [{ batch_no: 'A1234567' }] }] },
|
916
|
+
{ id: 70 }
|
917
|
+
]
|
918
|
+
}
|
919
|
+
|
920
|
+
get '/validate_required_arrays_under_optional_arrays', data
|
921
|
+
expect(last_response.body).to eq('validate_required_arrays_under_optional_arrays works!')
|
922
|
+
expect(last_response.status).to eq(200)
|
923
|
+
end
|
924
|
+
|
925
|
+
it 'simplest example using Array -> Array -> Hash -> String' do
|
926
|
+
subject.params do
|
927
|
+
requires :orders, type: Array do
|
928
|
+
requires :id, type: Integer
|
929
|
+
optional :drugs, type: Array do
|
930
|
+
requires :batch_no, type: String
|
931
|
+
end
|
932
|
+
end
|
933
|
+
end
|
934
|
+
|
935
|
+
subject.get '/validate_required_arrays_under_optional_arrays' do
|
936
|
+
'validate_required_arrays_under_optional_arrays works!'
|
937
|
+
end
|
938
|
+
|
939
|
+
data = {
|
940
|
+
orders: [
|
941
|
+
{ id: 77, drugs: [{ batch_no: 'A1234567' }] },
|
942
|
+
{ id: 70 }
|
943
|
+
]
|
944
|
+
}
|
945
|
+
|
946
|
+
get '/validate_required_arrays_under_optional_arrays', data
|
947
|
+
expect(last_response.body).to eq('validate_required_arrays_under_optional_arrays works!')
|
948
|
+
expect(last_response.status).to eq(200)
|
949
|
+
end
|
950
|
+
|
951
|
+
it 'simplest example using Array -> Hash -> String' do
|
952
|
+
subject.params do
|
953
|
+
requires :orders, type: Array do
|
954
|
+
requires :id, type: Integer
|
955
|
+
optional :drugs, type: Hash do
|
956
|
+
requires :batch_no, type: String
|
957
|
+
end
|
958
|
+
end
|
959
|
+
end
|
960
|
+
|
961
|
+
subject.get '/validate_required_arrays_under_optional_arrays' do
|
962
|
+
'validate_required_arrays_under_optional_arrays works!'
|
963
|
+
end
|
964
|
+
|
965
|
+
data = {
|
966
|
+
orders: [
|
967
|
+
{ id: 77, drugs: { batch_no: 'A1234567' } },
|
968
|
+
{ id: 70 }
|
969
|
+
]
|
970
|
+
}
|
971
|
+
|
972
|
+
get '/validate_required_arrays_under_optional_arrays', data
|
973
|
+
expect(last_response.body).to eq('validate_required_arrays_under_optional_arrays works!')
|
974
|
+
expect(last_response.status).to eq(200)
|
975
|
+
end
|
976
|
+
|
977
|
+
it 'correctly indexes invalida data' do
|
978
|
+
subject.params do
|
979
|
+
requires :orders, type: Array do
|
980
|
+
requires :id, type: Integer
|
981
|
+
optional :drugs, type: Array do
|
982
|
+
requires :batch_no, type: String
|
983
|
+
requires :quantity, type: Integer
|
984
|
+
end
|
985
|
+
end
|
986
|
+
end
|
987
|
+
|
988
|
+
subject.get '/correctly_indexes' do
|
989
|
+
'correctly_indexes works!'
|
990
|
+
end
|
991
|
+
|
992
|
+
data = {
|
993
|
+
orders: [
|
994
|
+
{ id: 70 },
|
995
|
+
{ id: 77, drugs: [{ batch_no: 'A1234567', quantity: 12 }, { batch_no: 'B222222' }] }
|
996
|
+
]
|
997
|
+
}
|
998
|
+
|
999
|
+
get '/correctly_indexes', data
|
1000
|
+
expect(last_response.body).to eq('orders[1][drugs][1][quantity] is missing')
|
1001
|
+
expect(last_response.status).to eq(400)
|
1002
|
+
end
|
1003
|
+
|
1004
|
+
context 'multiple levels of optional and requires settings' do
|
1005
|
+
before do
|
1006
|
+
subject.params do
|
1007
|
+
requires :top, type: Array do
|
1008
|
+
requires :top_id, type: Integer, allow_blank: false
|
1009
|
+
optional :middle_1, type: Array do
|
1010
|
+
requires :middle_1_id, type: Integer, allow_blank: false
|
1011
|
+
optional :middle_2, type: Array do
|
1012
|
+
requires :middle_2_id, type: String, allow_blank: false
|
1013
|
+
optional :bottom, type: Array do
|
1014
|
+
requires :bottom_id, type: Integer, allow_blank: false
|
1015
|
+
end
|
1016
|
+
end
|
1017
|
+
end
|
1018
|
+
end
|
1019
|
+
end
|
1020
|
+
|
1021
|
+
subject.get '/multi_level' do
|
1022
|
+
'multi_level works!'
|
1023
|
+
end
|
1024
|
+
end
|
1025
|
+
|
1026
|
+
it 'with valid data' do
|
1027
|
+
data = {
|
1028
|
+
top: [
|
1029
|
+
{ top_id: 1, middle_1: [
|
1030
|
+
{ middle_1_id: 11 }, { middle_1_id: 12, middle_2: [
|
1031
|
+
{ middle_2_id: 121 }, { middle_2_id: 122, bottom: [{ bottom_id: 1221 }] }
|
1032
|
+
] }
|
1033
|
+
] },
|
1034
|
+
{ top_id: 2, middle_1: [
|
1035
|
+
{ middle_1_id: 21 }, { middle_1_id: 22, middle_2: [
|
1036
|
+
{ middle_2_id: 221 }
|
1037
|
+
] }
|
1038
|
+
] },
|
1039
|
+
{ top_id: 3, middle_1: [
|
1040
|
+
{ middle_1_id: 31 }, { middle_1_id: 32 }
|
1041
|
+
] },
|
1042
|
+
{ top_id: 4 }
|
1043
|
+
]
|
1044
|
+
}
|
1045
|
+
|
1046
|
+
get '/multi_level', data
|
1047
|
+
expect(last_response.body).to eq('multi_level works!')
|
1048
|
+
expect(last_response.status).to eq(200)
|
1049
|
+
end
|
1050
|
+
|
1051
|
+
it 'with invalid data' do
|
1052
|
+
data = {
|
1053
|
+
top: [
|
1054
|
+
{ top_id: 1, middle_1: [
|
1055
|
+
{ middle_1_id: 11 }, { middle_1_id: 12, middle_2: [
|
1056
|
+
{ middle_2_id: 121 }, { middle_2_id: 122, bottom: [{ bottom_id: nil }] }
|
1057
|
+
] }
|
1058
|
+
] },
|
1059
|
+
{ top_id: 2, middle_1: [
|
1060
|
+
{ middle_1_id: 21 }, { middle_1_id: 22, middle_2: [{ middle_2_id: nil }] }
|
1061
|
+
] },
|
1062
|
+
{ top_id: 3, middle_1: [
|
1063
|
+
{ middle_1_id: nil }, { middle_1_id: 32 }
|
1064
|
+
] },
|
1065
|
+
{ top_id: nil, missing_top_id: 4 }
|
1066
|
+
]
|
1067
|
+
}
|
1068
|
+
# debugger
|
1069
|
+
get '/multi_level', data
|
1070
|
+
expect(last_response.body.split(', ')).to match_array([
|
1071
|
+
'top[3][top_id] is empty',
|
1072
|
+
'top[2][middle_1][0][middle_1_id] is empty',
|
1073
|
+
'top[1][middle_1][1][middle_2][0][middle_2_id] is empty',
|
1074
|
+
'top[0][middle_1][1][middle_2][1][bottom][0][bottom_id] is empty'
|
1075
|
+
])
|
1076
|
+
expect(last_response.status).to eq(400)
|
1077
|
+
end
|
1078
|
+
end
|
1079
|
+
end
|
1080
|
+
|
1081
|
+
it 'exactly_one_of' do
|
1082
|
+
subject.params do
|
1083
|
+
requires :orders, type: Array do
|
1084
|
+
requires :id, type: Integer
|
1085
|
+
optional :drugs, type: Hash do
|
1086
|
+
optional :batch_no, type: String
|
1087
|
+
optional :batch_id, type: String
|
1088
|
+
exactly_one_of :batch_no, :batch_id
|
1089
|
+
end
|
1090
|
+
end
|
1091
|
+
end
|
1092
|
+
|
1093
|
+
subject.get '/exactly_one_of' do
|
1094
|
+
'exactly_one_of works!'
|
1095
|
+
end
|
1096
|
+
|
1097
|
+
data = {
|
1098
|
+
orders: [
|
1099
|
+
{ id: 77, drugs: { batch_no: 'A1234567' } },
|
1100
|
+
{ id: 70 }
|
1101
|
+
]
|
1102
|
+
}
|
1103
|
+
|
1104
|
+
get '/exactly_one_of', data
|
1105
|
+
expect(last_response.body).to eq('exactly_one_of works!')
|
1106
|
+
expect(last_response.status).to eq(200)
|
1107
|
+
end
|
1108
|
+
|
1109
|
+
it 'at_least_one_of' do
|
1110
|
+
subject.params do
|
1111
|
+
requires :orders, type: Array do
|
1112
|
+
requires :id, type: Integer
|
1113
|
+
optional :drugs, type: Hash do
|
1114
|
+
optional :batch_no, type: String
|
1115
|
+
optional :batch_id, type: String
|
1116
|
+
at_least_one_of :batch_no, :batch_id
|
1117
|
+
end
|
1118
|
+
end
|
1119
|
+
end
|
1120
|
+
|
1121
|
+
subject.get '/at_least_one_of' do
|
1122
|
+
'at_least_one_of works!'
|
1123
|
+
end
|
1124
|
+
|
1125
|
+
data = {
|
1126
|
+
orders: [
|
1127
|
+
{ id: 77, drugs: { batch_no: 'A1234567' } },
|
1128
|
+
{ id: 70 }
|
1129
|
+
]
|
1130
|
+
}
|
1131
|
+
|
1132
|
+
get '/at_least_one_of', data
|
1133
|
+
expect(last_response.body).to eq('at_least_one_of works!')
|
1134
|
+
expect(last_response.status).to eq(200)
|
1135
|
+
end
|
1136
|
+
|
1137
|
+
it 'all_or_none_of' do
|
1138
|
+
subject.params do
|
1139
|
+
requires :orders, type: Array do
|
1140
|
+
requires :id, type: Integer
|
1141
|
+
optional :drugs, type: Hash do
|
1142
|
+
optional :batch_no, type: String
|
1143
|
+
optional :batch_id, type: String
|
1144
|
+
all_or_none_of :batch_no, :batch_id
|
1145
|
+
end
|
1146
|
+
end
|
1147
|
+
end
|
1148
|
+
|
1149
|
+
subject.get '/all_or_none_of' do
|
1150
|
+
'all_or_none_of works!'
|
1151
|
+
end
|
1152
|
+
|
1153
|
+
data = {
|
1154
|
+
orders: [
|
1155
|
+
{ id: 77, drugs: { batch_no: 'A1234567', batch_id: '12' } },
|
1156
|
+
{ id: 70 }
|
1157
|
+
]
|
1158
|
+
}
|
1159
|
+
|
1160
|
+
get '/all_or_none_of', data
|
1161
|
+
expect(last_response.body).to eq('all_or_none_of works!')
|
1162
|
+
expect(last_response.status).to eq(200)
|
1163
|
+
end
|
886
1164
|
end
|
887
1165
|
|
888
1166
|
context 'multiple validation errors' do
|
@@ -909,6 +1187,7 @@ describe Grape::Validations do
|
|
909
1187
|
class Customvalidator < Grape::Validations::Base
|
910
1188
|
def validate_param!(attr_name, params)
|
911
1189
|
return if params[attr_name] == 'im custom'
|
1190
|
+
|
912
1191
|
raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: 'is not custom!')
|
913
1192
|
end
|
914
1193
|
end
|
@@ -1057,6 +1336,7 @@ describe Grape::Validations do
|
|
1057
1336
|
class CustomvalidatorWithOptions < Grape::Validations::Base
|
1058
1337
|
def validate_param!(attr_name, params)
|
1059
1338
|
return if params[attr_name] == @option[:text]
|
1339
|
+
|
1060
1340
|
raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message)
|
1061
1341
|
end
|
1062
1342
|
end
|