grape 1.1.0 → 1.5.3
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 +278 -44
- data/LICENSE +1 -1
- data/README.md +514 -69
- data/UPGRADING.md +424 -17
- data/grape.gemspec +13 -2
- data/lib/grape.rb +104 -71
- data/lib/grape/api.rb +138 -175
- data/lib/grape/api/helpers.rb +2 -0
- data/lib/grape/api/instance.rb +283 -0
- data/lib/grape/config.rb +34 -0
- data/lib/grape/content_types.rb +34 -0
- data/lib/grape/cookies.rb +2 -0
- data/lib/grape/dsl/api.rb +2 -0
- data/lib/grape/dsl/callbacks.rb +22 -0
- data/lib/grape/dsl/configuration.rb +2 -0
- data/lib/grape/dsl/desc.rb +41 -7
- data/lib/grape/dsl/headers.rb +2 -0
- data/lib/grape/dsl/helpers.rb +5 -2
- data/lib/grape/dsl/inside_route.rb +92 -49
- data/lib/grape/dsl/logger.rb +2 -0
- data/lib/grape/dsl/middleware.rb +9 -0
- data/lib/grape/dsl/parameters.rb +25 -14
- data/lib/grape/dsl/request_response.rb +4 -2
- data/lib/grape/dsl/routing.rb +17 -10
- data/lib/grape/dsl/settings.rb +7 -1
- data/lib/grape/dsl/validations.rb +24 -4
- data/lib/grape/eager_load.rb +20 -0
- data/lib/grape/endpoint.rb +59 -35
- data/lib/grape/error_formatter.rb +4 -2
- data/lib/grape/error_formatter/base.rb +2 -0
- data/lib/grape/error_formatter/json.rb +2 -0
- data/lib/grape/error_formatter/txt.rb +2 -0
- data/lib/grape/error_formatter/xml.rb +2 -0
- data/lib/grape/exceptions/base.rb +20 -14
- data/lib/grape/exceptions/empty_message_body.rb +11 -0
- data/lib/grape/exceptions/incompatible_option_values.rb +2 -0
- data/lib/grape/exceptions/invalid_accept_header.rb +2 -0
- data/lib/grape/exceptions/invalid_formatter.rb +2 -0
- data/lib/grape/exceptions/invalid_message_body.rb +2 -0
- data/lib/grape/exceptions/invalid_response.rb +11 -0
- data/lib/grape/exceptions/invalid_version_header.rb +2 -0
- data/lib/grape/exceptions/invalid_versioner_option.rb +2 -0
- data/lib/grape/exceptions/invalid_with_option_for_represent.rb +2 -0
- data/lib/grape/exceptions/method_not_allowed.rb +2 -0
- data/lib/grape/exceptions/missing_group_type.rb +2 -0
- data/lib/grape/exceptions/missing_mime_type.rb +2 -0
- data/lib/grape/exceptions/missing_option.rb +2 -0
- data/lib/grape/exceptions/missing_vendor_option.rb +2 -0
- data/lib/grape/exceptions/unknown_options.rb +2 -0
- data/lib/grape/exceptions/unknown_parameter.rb +2 -0
- data/lib/grape/exceptions/unknown_validator.rb +2 -0
- data/lib/grape/exceptions/unsupported_group_type.rb +2 -0
- data/lib/grape/exceptions/validation.rb +4 -2
- data/lib/grape/exceptions/validation_array_errors.rb +2 -0
- data/lib/grape/exceptions/validation_errors.rb +16 -13
- data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +4 -3
- data/lib/grape/extensions/deep_mergeable_hash.rb +2 -0
- data/lib/grape/extensions/deep_symbolize_hash.rb +2 -0
- data/lib/grape/extensions/hash.rb +2 -0
- data/lib/grape/extensions/hashie/mash.rb +2 -0
- data/lib/grape/formatter.rb +5 -3
- data/lib/grape/formatter/json.rb +2 -0
- data/lib/grape/formatter/serializable_hash.rb +2 -0
- data/lib/grape/formatter/txt.rb +2 -0
- data/lib/grape/formatter/xml.rb +2 -0
- data/lib/grape/http/headers.rb +50 -18
- data/lib/grape/locale/en.yml +3 -1
- data/lib/grape/middleware/auth/base.rb +7 -7
- data/lib/grape/middleware/auth/dsl.rb +2 -0
- data/lib/grape/middleware/auth/strategies.rb +2 -0
- data/lib/grape/middleware/auth/strategy_info.rb +2 -0
- data/lib/grape/middleware/base.rb +10 -7
- data/lib/grape/middleware/error.rb +21 -16
- data/lib/grape/middleware/filter.rb +2 -0
- data/lib/grape/middleware/formatter.rb +8 -6
- data/lib/grape/middleware/globals.rb +2 -0
- data/lib/grape/middleware/helpers.rb +12 -0
- data/lib/grape/middleware/stack.rb +13 -3
- data/lib/grape/middleware/versioner.rb +2 -0
- data/lib/grape/middleware/versioner/accept_version_header.rb +2 -0
- data/lib/grape/middleware/versioner/header.rb +10 -8
- data/lib/grape/middleware/versioner/param.rb +3 -1
- data/lib/grape/middleware/versioner/parse_media_type_patch.rb +4 -1
- data/lib/grape/middleware/versioner/path.rb +3 -1
- data/lib/grape/namespace.rb +14 -2
- data/lib/grape/parser.rb +4 -2
- data/lib/grape/parser/json.rb +3 -1
- data/lib/grape/parser/xml.rb +3 -1
- data/lib/grape/path.rb +15 -3
- data/lib/grape/presenters/presenter.rb +2 -0
- data/lib/grape/request.rb +19 -10
- data/lib/grape/router.rb +30 -29
- data/lib/grape/router/attribute_translator.rb +41 -8
- data/lib/grape/router/pattern.rb +20 -16
- data/lib/grape/router/route.rb +14 -28
- data/lib/grape/{serve_file → serve_stream}/file_body.rb +3 -1
- data/lib/grape/{serve_file → serve_stream}/sendfile_response.rb +3 -1
- data/lib/grape/{serve_file/file_response.rb → serve_stream/stream_response.rb} +10 -8
- data/lib/grape/util/base_inheritable.rb +43 -0
- data/lib/grape/util/cache.rb +20 -0
- data/lib/grape/util/endpoint_configuration.rb +8 -0
- data/lib/grape/util/env.rb +19 -17
- data/lib/grape/util/inheritable_setting.rb +2 -0
- data/lib/grape/util/inheritable_values.rb +7 -25
- data/lib/grape/util/json.rb +2 -0
- data/lib/grape/util/lazy_block.rb +27 -0
- data/lib/grape/util/lazy_object.rb +43 -0
- data/lib/grape/util/lazy_value.rb +98 -0
- data/lib/grape/util/registrable.rb +2 -0
- data/lib/grape/util/reverse_stackable_values.rb +10 -35
- data/lib/grape/util/stackable_values.rb +21 -34
- data/lib/grape/util/strict_hash_configuration.rb +2 -0
- data/lib/grape/util/xml.rb +2 -0
- data/lib/grape/validations.rb +2 -0
- data/lib/grape/validations/attributes_iterator.rb +16 -6
- data/lib/grape/validations/multiple_attributes_iterator.rb +13 -0
- data/lib/grape/validations/params_scope.rb +51 -30
- data/lib/grape/validations/single_attribute_iterator.rb +24 -0
- data/lib/grape/validations/types.rb +13 -38
- data/lib/grape/validations/types/array_coercer.rb +65 -0
- data/lib/grape/validations/types/build_coercer.rb +47 -49
- data/lib/grape/validations/types/custom_type_coercer.rb +29 -51
- data/lib/grape/validations/types/custom_type_collection_coercer.rb +10 -25
- data/lib/grape/validations/types/dry_type_coercer.rb +76 -0
- data/lib/grape/validations/types/file.rb +22 -18
- data/lib/grape/validations/types/invalid_value.rb +24 -0
- data/lib/grape/validations/types/json.rb +46 -39
- data/lib/grape/validations/types/multiple_type_coercer.rb +14 -33
- data/lib/grape/validations/types/primitive_coercer.rb +67 -0
- data/lib/grape/validations/types/set_coercer.rb +40 -0
- data/lib/grape/validations/types/variant_collection_coercer.rb +5 -13
- data/lib/grape/validations/validator_factory.rb +8 -11
- data/lib/grape/validations/validators/all_or_none.rb +8 -13
- data/lib/grape/validations/validators/allow_blank.rb +3 -1
- data/lib/grape/validations/validators/as.rb +5 -4
- data/lib/grape/validations/validators/at_least_one_of.rb +7 -13
- data/lib/grape/validations/validators/base.rb +20 -16
- data/lib/grape/validations/validators/coerce.rb +46 -29
- data/lib/grape/validations/validators/default.rb +6 -6
- data/lib/grape/validations/validators/exactly_one_of.rb +10 -23
- data/lib/grape/validations/validators/except_values.rb +4 -2
- data/lib/grape/validations/validators/multiple_params_base.rb +17 -10
- data/lib/grape/validations/validators/mutual_exclusion.rb +8 -18
- data/lib/grape/validations/validators/presence.rb +3 -1
- data/lib/grape/validations/validators/regexp.rb +4 -2
- data/lib/grape/validations/validators/same_as.rb +26 -0
- data/lib/grape/validations/validators/values.rb +18 -6
- data/lib/grape/version.rb +3 -1
- data/spec/grape/api/custom_validations_spec.rb +5 -3
- data/spec/grape/api/deeply_included_options_spec.rb +2 -0
- data/spec/grape/api/defines_boolean_in_params_spec.rb +39 -0
- data/spec/grape/api/inherited_helpers_spec.rb +2 -0
- data/spec/grape/api/instance_spec.rb +104 -0
- data/spec/grape/api/invalid_format_spec.rb +2 -0
- data/spec/grape/api/namespace_parameters_in_route_spec.rb +2 -0
- data/spec/grape/api/nested_helpers_spec.rb +2 -0
- data/spec/grape/api/optional_parameters_in_route_spec.rb +2 -0
- data/spec/grape/api/parameters_modification_spec.rb +3 -1
- data/spec/grape/api/patch_method_helpers_spec.rb +2 -0
- data/spec/grape/api/recognize_path_spec.rb +2 -0
- data/spec/grape/api/required_parameters_in_route_spec.rb +2 -0
- data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +2 -0
- data/spec/grape/api/routes_with_requirements_spec.rb +61 -0
- data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +2 -0
- data/spec/grape/api/shared_helpers_spec.rb +2 -0
- data/spec/grape/api_remount_spec.rb +473 -0
- data/spec/grape/api_spec.rb +565 -12
- data/spec/grape/config_spec.rb +19 -0
- data/spec/grape/dsl/callbacks_spec.rb +2 -0
- data/spec/grape/dsl/configuration_spec.rb +2 -0
- data/spec/grape/dsl/desc_spec.rb +42 -16
- data/spec/grape/dsl/headers_spec.rb +2 -0
- data/spec/grape/dsl/helpers_spec.rb +4 -2
- data/spec/grape/dsl/inside_route_spec.rb +184 -33
- data/spec/grape/dsl/logger_spec.rb +2 -0
- data/spec/grape/dsl/middleware_spec.rb +10 -0
- data/spec/grape/dsl/parameters_spec.rb +2 -0
- data/spec/grape/dsl/request_response_spec.rb +2 -0
- data/spec/grape/dsl/routing_spec.rb +12 -0
- data/spec/grape/dsl/settings_spec.rb +2 -0
- data/spec/grape/dsl/validations_spec.rb +2 -0
- data/spec/grape/endpoint/declared_spec.rb +601 -0
- data/spec/grape/endpoint_spec.rb +53 -523
- data/spec/grape/entity_spec.rb +9 -1
- data/spec/grape/exceptions/base_spec.rb +67 -0
- data/spec/grape/exceptions/body_parse_errors_spec.rb +2 -0
- data/spec/grape/exceptions/invalid_accept_header_spec.rb +2 -0
- data/spec/grape/exceptions/invalid_formatter_spec.rb +2 -0
- data/spec/grape/exceptions/invalid_response_spec.rb +13 -0
- data/spec/grape/exceptions/invalid_versioner_option_spec.rb +2 -0
- data/spec/grape/exceptions/missing_mime_type_spec.rb +2 -0
- data/spec/grape/exceptions/missing_option_spec.rb +2 -0
- data/spec/grape/exceptions/unknown_options_spec.rb +2 -0
- data/spec/grape/exceptions/unknown_validator_spec.rb +2 -0
- data/spec/grape/exceptions/validation_errors_spec.rb +8 -4
- data/spec/grape/exceptions/validation_spec.rb +3 -1
- data/spec/grape/extensions/param_builders/hash_spec.rb +2 -0
- data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +2 -0
- data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +2 -0
- data/spec/grape/integration/global_namespace_function_spec.rb +2 -0
- data/spec/grape/integration/rack_sendfile_spec.rb +14 -8
- data/spec/grape/integration/rack_spec.rb +25 -7
- data/spec/grape/loading_spec.rb +2 -0
- data/spec/grape/middleware/auth/base_spec.rb +2 -0
- data/spec/grape/middleware/auth/dsl_spec.rb +5 -3
- data/spec/grape/middleware/auth/strategies_spec.rb +3 -1
- data/spec/grape/middleware/base_spec.rb +10 -0
- data/spec/grape/middleware/error_spec.rb +3 -1
- data/spec/grape/middleware/exception_spec.rb +4 -2
- data/spec/grape/middleware/formatter_spec.rb +33 -16
- data/spec/grape/middleware/globals_spec.rb +2 -0
- data/spec/grape/middleware/stack_spec.rb +12 -0
- data/spec/grape/middleware/versioner/accept_version_header_spec.rb +3 -1
- data/spec/grape/middleware/versioner/header_spec.rb +9 -1
- data/spec/grape/middleware/versioner/param_spec.rb +3 -1
- data/spec/grape/middleware/versioner/path_spec.rb +3 -1
- data/spec/grape/middleware/versioner_spec.rb +2 -0
- data/spec/grape/named_api_spec.rb +21 -0
- data/spec/grape/parser_spec.rb +7 -5
- data/spec/grape/path_spec.rb +6 -4
- data/spec/grape/presenters/presenter_spec.rb +2 -0
- data/spec/grape/request_spec.rb +26 -0
- data/spec/grape/util/inheritable_setting_spec.rb +2 -0
- data/spec/grape/util/inheritable_values_spec.rb +2 -0
- data/spec/grape/util/reverse_stackable_values_spec.rb +2 -0
- data/spec/grape/util/stackable_values_spec.rb +3 -1
- data/spec/grape/util/strict_hash_configuration_spec.rb +2 -0
- data/spec/grape/validations/attributes_iterator_spec.rb +2 -0
- data/spec/grape/validations/instance_behaivour_spec.rb +5 -3
- data/spec/grape/validations/multiple_attributes_iterator_spec.rb +41 -0
- data/spec/grape/validations/params_scope_spec.rb +213 -9
- data/spec/grape/validations/single_attribute_iterator_spec.rb +58 -0
- data/spec/grape/validations/types/array_coercer_spec.rb +35 -0
- data/spec/grape/validations/types/primitive_coercer_spec.rb +135 -0
- data/spec/grape/validations/types/set_coercer_spec.rb +34 -0
- data/spec/grape/validations/types_spec.rb +9 -36
- data/spec/grape/validations/validators/all_or_none_spec.rb +140 -30
- data/spec/grape/validations/validators/allow_blank_spec.rb +2 -0
- data/spec/grape/validations/validators/at_least_one_of_spec.rb +175 -29
- data/spec/grape/validations/validators/coerce_spec.rb +476 -135
- data/spec/grape/validations/validators/default_spec.rb +172 -0
- data/spec/grape/validations/validators/exactly_one_of_spec.rb +204 -38
- data/spec/grape/validations/validators/except_values_spec.rb +4 -1
- data/spec/grape/validations/validators/mutual_exclusion_spec.rb +186 -27
- data/spec/grape/validations/validators/presence_spec.rb +30 -0
- data/spec/grape/validations/validators/regexp_spec.rb +2 -0
- data/spec/grape/validations/validators/same_as_spec.rb +65 -0
- data/spec/grape/validations/validators/values_spec.rb +30 -5
- data/spec/grape/validations_spec.rb +388 -50
- data/spec/integration/eager_load/eager_load_spec.rb +15 -0
- data/spec/integration/multi_json/json_spec.rb +2 -0
- data/spec/integration/multi_xml/xml_spec.rb +2 -0
- data/spec/shared/versioning_examples.rb +22 -20
- data/spec/spec_helper.rb +12 -1
- data/spec/support/basic_auth_encode_helpers.rb +2 -0
- data/spec/support/chunks.rb +14 -0
- data/spec/support/content_type_helpers.rb +2 -0
- data/spec/support/eager_load.rb +19 -0
- data/spec/support/endpoint_faker.rb +2 -0
- data/spec/support/file_streamer.rb +2 -0
- data/spec/support/integer_helpers.rb +2 -0
- data/spec/support/versioned_helpers.rb +8 -8
- metadata +86 -48
- data/Appraisals +0 -32
- data/Dangerfile +0 -2
- data/Gemfile +0 -33
- data/Gemfile.lock +0 -231
- data/Guardfile +0 -10
- data/RELEASING.md +0 -111
- data/Rakefile +0 -25
- data/benchmark/simple.rb +0 -27
- data/benchmark/simple_with_type_coercer.rb +0 -22
- data/gemfiles/multi_json.gemfile +0 -35
- data/gemfiles/multi_xml.gemfile +0 -35
- data/gemfiles/rack_1.5.2.gemfile +0 -35
- data/gemfiles/rack_edge.gemfile +0 -35
- data/gemfiles/rails_3.gemfile +0 -36
- data/gemfiles/rails_4.gemfile +0 -35
- data/gemfiles/rails_5.gemfile +0 -35
- data/gemfiles/rails_edge.gemfile +0 -35
- data/lib/grape/extensions/deep_hash_with_indifferent_access.rb +0 -18
- data/lib/grape/util/content_types.rb +0 -26
- data/lib/grape/validations/types/virtus_collection_patch.rb +0 -16
- data/pkg/grape-0.17.0.gem +0 -0
- data/pkg/grape-0.19.0.gem +0 -0
@@ -1,66 +1,212 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe Grape::Validations::AtLeastOneOfValidator do
|
4
6
|
describe '#validate!' do
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
subject(:validate) { post path, params }
|
8
|
+
|
9
|
+
module ValidationsSpec
|
10
|
+
module AtLeastOneOfValidatorSpec
|
11
|
+
class API < Grape::API
|
12
|
+
rescue_from Grape::Exceptions::ValidationErrors do |e|
|
13
|
+
error!(e.errors.transform_keys! { |key| key.join(',') }, 400)
|
14
|
+
end
|
15
|
+
|
16
|
+
params do
|
17
|
+
optional :beer, :wine, :grapefruit
|
18
|
+
at_least_one_of :beer, :wine, :grapefruit
|
19
|
+
end
|
20
|
+
post do
|
21
|
+
end
|
22
|
+
|
23
|
+
params do
|
24
|
+
optional :beer, :wine, :grapefruit, :other
|
25
|
+
at_least_one_of :beer, :wine, :grapefruit
|
26
|
+
end
|
27
|
+
post 'mixed-params' do
|
28
|
+
end
|
29
|
+
|
30
|
+
params do
|
31
|
+
optional :beer, :wine, :grapefruit
|
32
|
+
at_least_one_of :beer, :wine, :grapefruit, message: 'you should choose something'
|
33
|
+
end
|
34
|
+
post '/custom-message' do
|
35
|
+
end
|
10
36
|
|
11
|
-
|
37
|
+
params do
|
38
|
+
requires :item, type: Hash do
|
39
|
+
optional :beer, :wine, :grapefruit
|
40
|
+
at_least_one_of :beer, :wine, :grapefruit, message: 'fail'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
post '/nested-hash' do
|
44
|
+
end
|
45
|
+
|
46
|
+
params do
|
47
|
+
requires :items, type: Array do
|
48
|
+
optional :beer, :wine, :grapefruit
|
49
|
+
at_least_one_of :beer, :wine, :grapefruit, message: 'fail'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
post '/nested-array' do
|
53
|
+
end
|
54
|
+
|
55
|
+
params do
|
56
|
+
requires :items, type: Array do
|
57
|
+
requires :nested_items, type: Array do
|
58
|
+
optional :beer, :wine, :grapefruit
|
59
|
+
at_least_one_of :beer, :wine, :grapefruit, message: 'fail'
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
post '/deeply-nested-array' do
|
64
|
+
end
|
65
|
+
end
|
12
66
|
end
|
13
67
|
end
|
14
|
-
|
15
|
-
|
68
|
+
|
69
|
+
def app
|
70
|
+
ValidationsSpec::AtLeastOneOfValidatorSpec::API
|
71
|
+
end
|
16
72
|
|
17
73
|
context 'when all restricted params are present' do
|
74
|
+
let(:path) { '/' }
|
18
75
|
let(:params) { { beer: true, wine: true, grapefruit: true } }
|
19
76
|
|
20
|
-
it 'does not
|
21
|
-
|
77
|
+
it 'does not return a validation error' do
|
78
|
+
validate
|
79
|
+
expect(last_response.status).to eq 201
|
22
80
|
end
|
23
81
|
|
24
82
|
context 'mixed with other params' do
|
25
|
-
let(:
|
83
|
+
let(:path) { '/mixed-params' }
|
84
|
+
let(:params) { { beer: true, wine: true, grapefruit: true, other: true } }
|
26
85
|
|
27
|
-
it 'does not
|
28
|
-
|
86
|
+
it 'does not return a validation error' do
|
87
|
+
validate
|
88
|
+
expect(last_response.status).to eq 201
|
29
89
|
end
|
30
90
|
end
|
31
91
|
end
|
32
92
|
|
33
93
|
context 'when a subset of restricted params are present' do
|
94
|
+
let(:path) { '/' }
|
34
95
|
let(:params) { { beer: true, grapefruit: true } }
|
35
96
|
|
36
|
-
it 'does not
|
37
|
-
|
97
|
+
it 'does not return a validation error' do
|
98
|
+
validate
|
99
|
+
expect(last_response.status).to eq 201
|
38
100
|
end
|
39
101
|
end
|
40
102
|
|
41
|
-
context 'when
|
42
|
-
let(:
|
103
|
+
context 'when none of the restricted params is selected' do
|
104
|
+
let(:path) { '/' }
|
105
|
+
let(:params) { { other: true } }
|
43
106
|
|
44
|
-
it '
|
45
|
-
|
107
|
+
it 'returns a validation error' do
|
108
|
+
validate
|
109
|
+
expect(last_response.status).to eq 400
|
110
|
+
expect(JSON.parse(last_response.body)).to eq(
|
111
|
+
'beer,wine,grapefruit' => ['are missing, at least one parameter must be provided']
|
112
|
+
)
|
46
113
|
end
|
47
|
-
end
|
48
114
|
|
49
|
-
|
50
|
-
|
115
|
+
context 'when custom message is specified' do
|
116
|
+
let(:path) { '/custom-message' }
|
51
117
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
118
|
+
it 'returns a validation error' do
|
119
|
+
validate
|
120
|
+
expect(last_response.status).to eq 400
|
121
|
+
expect(JSON.parse(last_response.body)).to eq(
|
122
|
+
'beer,wine,grapefruit' => ['you should choose something']
|
123
|
+
)
|
124
|
+
end
|
56
125
|
end
|
57
126
|
end
|
58
127
|
|
59
128
|
context 'when exactly one of the restricted params is selected' do
|
60
|
-
let(:
|
129
|
+
let(:path) { '/' }
|
130
|
+
let(:params) { { beer: true } }
|
131
|
+
|
132
|
+
it 'does not return a validation error' do
|
133
|
+
validate
|
134
|
+
expect(last_response.status).to eq 201
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
context 'when restricted params are nested inside hash' do
|
139
|
+
let(:path) { '/nested-hash' }
|
140
|
+
|
141
|
+
context 'when at least one of them is present' do
|
142
|
+
let(:params) { { item: { beer: true, wine: true } } }
|
143
|
+
|
144
|
+
it 'does not return a validation error' do
|
145
|
+
validate
|
146
|
+
expect(last_response.status).to eq 201
|
147
|
+
end
|
148
|
+
end
|
61
149
|
|
62
|
-
|
63
|
-
|
150
|
+
context 'when none of them are present' do
|
151
|
+
let(:params) { { item: { other: true } } }
|
152
|
+
|
153
|
+
it 'returns a validation error with full names of the params' do
|
154
|
+
validate
|
155
|
+
expect(last_response.status).to eq 400
|
156
|
+
expect(JSON.parse(last_response.body)).to eq(
|
157
|
+
'item[beer],item[wine],item[grapefruit]' => ['fail']
|
158
|
+
)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
context 'when restricted params are nested inside array' do
|
164
|
+
let(:path) { '/nested-array' }
|
165
|
+
|
166
|
+
context 'when at least one of them is present' do
|
167
|
+
let(:params) { { items: [{ beer: true, wine: true }, { grapefruit: true }] } }
|
168
|
+
|
169
|
+
it 'does not return a validation error' do
|
170
|
+
validate
|
171
|
+
expect(last_response.status).to eq 201
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
context 'when none of them are present' do
|
176
|
+
let(:params) { { items: [{ beer: true, other: true }, { other: true }] } }
|
177
|
+
|
178
|
+
it 'returns a validation error with full names of the params' do
|
179
|
+
validate
|
180
|
+
expect(last_response.status).to eq 400
|
181
|
+
expect(JSON.parse(last_response.body)).to eq(
|
182
|
+
'items[1][beer],items[1][wine],items[1][grapefruit]' => ['fail']
|
183
|
+
)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
context 'when restricted params are deeply nested' do
|
189
|
+
let(:path) { '/deeply-nested-array' }
|
190
|
+
|
191
|
+
context 'when at least one of them is present' do
|
192
|
+
let(:params) { { items: [{ nested_items: [{ wine: true }] }] } }
|
193
|
+
|
194
|
+
it 'does not return a validation error' do
|
195
|
+
validate
|
196
|
+
expect(last_response.status).to eq 201
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
context 'when none of them are present' do
|
201
|
+
let(:params) { { items: [{ nested_items: [{ other: true }] }] } }
|
202
|
+
|
203
|
+
it 'returns a validation error with full names of the params' do
|
204
|
+
validate
|
205
|
+
expect(last_response.status).to eq 400
|
206
|
+
expect(JSON.parse(last_response.body)).to eq(
|
207
|
+
'items[0][nested_items][0][beer],items[0][nested_items][0][wine],items[0][nested_items][0][grapefruit]' => ['fail']
|
208
|
+
)
|
209
|
+
end
|
64
210
|
end
|
65
211
|
end
|
66
212
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe Grape::Validations::CoerceValidator do
|
@@ -10,20 +12,25 @@ describe Grape::Validations::CoerceValidator do
|
|
10
12
|
end
|
11
13
|
|
12
14
|
describe 'coerce' do
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
class SecureURIOnly
|
16
|
+
def self.parse(value)
|
17
|
+
URI.parse(value)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.parsed?(value)
|
21
|
+
value.is_a? URI::HTTPS
|
18
22
|
end
|
19
23
|
end
|
20
24
|
|
21
25
|
context 'i18n' do
|
22
26
|
after :each do
|
27
|
+
I18n.available_locales = %i[en]
|
23
28
|
I18n.locale = :en
|
29
|
+
I18n.default_locale = :en
|
24
30
|
end
|
25
31
|
|
26
32
|
it 'i18n error on malformed input' do
|
33
|
+
I18n.available_locales = %i[en zh-CN]
|
27
34
|
I18n.load_path << File.expand_path('../zh-CN.yml', __FILE__)
|
28
35
|
I18n.reload!
|
29
36
|
I18n.locale = 'zh-CN'.to_sym
|
@@ -40,6 +47,7 @@ describe Grape::Validations::CoerceValidator do
|
|
40
47
|
end
|
41
48
|
|
42
49
|
it 'gives an english fallback error when default locale message is blank' do
|
50
|
+
I18n.available_locales = %i[en pt-BR]
|
43
51
|
I18n.locale = 'pt-BR'.to_sym
|
44
52
|
subject.params do
|
45
53
|
requires :age, type: Integer
|
@@ -92,6 +100,7 @@ describe Grape::Validations::CoerceValidator do
|
|
92
100
|
|
93
101
|
it 'respects :coerce_with' do
|
94
102
|
get '/', a: 'yup'
|
103
|
+
|
95
104
|
expect(last_response.status).to eq(200)
|
96
105
|
expect(last_response.body).to eq('TrueClass')
|
97
106
|
end
|
@@ -144,26 +153,50 @@ describe Grape::Validations::CoerceValidator do
|
|
144
153
|
expect(last_response.body).to eq('array int works')
|
145
154
|
end
|
146
155
|
|
147
|
-
context '
|
148
|
-
|
149
|
-
|
150
|
-
|
156
|
+
context 'coerces' do
|
157
|
+
context 'json' do
|
158
|
+
let(:headers) { { 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json' } }
|
159
|
+
|
160
|
+
it 'BigDecimal' do
|
161
|
+
subject.params do
|
162
|
+
requires :bigdecimal, type: BigDecimal
|
163
|
+
end
|
164
|
+
subject.post '/bigdecimal' do
|
165
|
+
"#{params[:bigdecimal].class} #{params[:bigdecimal].to_f}"
|
166
|
+
end
|
167
|
+
|
168
|
+
post '/bigdecimal', { bigdecimal: 45.1 }.to_json, headers
|
169
|
+
expect(last_response.status).to eq(201)
|
170
|
+
expect(last_response.body).to eq('BigDecimal 45.1')
|
151
171
|
end
|
152
|
-
|
153
|
-
|
172
|
+
|
173
|
+
it 'Boolean' do
|
174
|
+
subject.params do
|
175
|
+
requires :boolean, type: Boolean
|
176
|
+
end
|
177
|
+
subject.post '/boolean' do
|
178
|
+
params[:boolean]
|
179
|
+
end
|
180
|
+
|
181
|
+
post '/boolean', { boolean: 'true' }.to_json, headers
|
182
|
+
expect(last_response.status).to eq(201)
|
183
|
+
expect(last_response.body).to eq('true')
|
154
184
|
end
|
185
|
+
end
|
155
186
|
|
156
|
-
|
157
|
-
|
158
|
-
|
187
|
+
it 'BigDecimal' do
|
188
|
+
subject.params do
|
189
|
+
requires :bigdecimal, coerce: BigDecimal
|
190
|
+
end
|
191
|
+
subject.get '/bigdecimal' do
|
192
|
+
params[:bigdecimal].class
|
193
|
+
end
|
159
194
|
|
160
|
-
get '/
|
195
|
+
get '/bigdecimal', bigdecimal: '45'
|
161
196
|
expect(last_response.status).to eq(200)
|
162
|
-
expect(last_response.body).to eq('
|
197
|
+
expect(last_response.body).to eq('BigDecimal')
|
163
198
|
end
|
164
|
-
end
|
165
199
|
|
166
|
-
context 'coerces' do
|
167
200
|
it 'Integer' do
|
168
201
|
subject.params do
|
169
202
|
requires :int, coerce: Integer
|
@@ -177,6 +210,70 @@ describe Grape::Validations::CoerceValidator do
|
|
177
210
|
expect(last_response.body).to eq(integer_class_name)
|
178
211
|
end
|
179
212
|
|
213
|
+
it 'String' do
|
214
|
+
subject.params do
|
215
|
+
requires :string, coerce: String
|
216
|
+
end
|
217
|
+
subject.get '/string' do
|
218
|
+
params[:string].class
|
219
|
+
end
|
220
|
+
|
221
|
+
get '/string', string: 45
|
222
|
+
expect(last_response.status).to eq(200)
|
223
|
+
expect(last_response.body).to eq('String')
|
224
|
+
|
225
|
+
get '/string', string: nil
|
226
|
+
expect(last_response.status).to eq(200)
|
227
|
+
expect(last_response.body).to eq('NilClass')
|
228
|
+
end
|
229
|
+
|
230
|
+
context 'a custom type' do
|
231
|
+
it 'coerces the given value' do
|
232
|
+
subject.params do
|
233
|
+
requires :uri, coerce: SecureURIOnly
|
234
|
+
end
|
235
|
+
subject.get '/secure_uri' do
|
236
|
+
params[:uri].class
|
237
|
+
end
|
238
|
+
|
239
|
+
get 'secure_uri', uri: 'https://www.example.com'
|
240
|
+
|
241
|
+
expect(last_response.status).to eq(200)
|
242
|
+
expect(last_response.body).to eq('URI::HTTPS')
|
243
|
+
|
244
|
+
get 'secure_uri', uri: 'http://www.example.com'
|
245
|
+
|
246
|
+
expect(last_response.status).to eq(400)
|
247
|
+
expect(last_response.body).to eq('uri is invalid')
|
248
|
+
end
|
249
|
+
|
250
|
+
context 'returning the InvalidValue instance when invalid' do
|
251
|
+
let(:custom_type) do
|
252
|
+
Class.new do
|
253
|
+
def self.parse(_val)
|
254
|
+
Grape::Types::InvalidValue.new('must be unique')
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
it 'uses a custom message added to the invalid value' do
|
260
|
+
type = custom_type
|
261
|
+
|
262
|
+
subject.params do
|
263
|
+
requires :name, type: type
|
264
|
+
end
|
265
|
+
subject.get '/whatever' do
|
266
|
+
params[:name].class
|
267
|
+
end
|
268
|
+
|
269
|
+
get 'whatever', name: 'Bob'
|
270
|
+
|
271
|
+
expect(last_response.status).to eq(400)
|
272
|
+
expect(last_response.body).to eq('name must be unique')
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
180
277
|
context 'Array' do
|
181
278
|
it 'Array of Integers' do
|
182
279
|
subject.params do
|
@@ -193,7 +290,7 @@ describe Grape::Validations::CoerceValidator do
|
|
193
290
|
|
194
291
|
it 'Array of Bools' do
|
195
292
|
subject.params do
|
196
|
-
requires :arry, coerce: Array[
|
293
|
+
requires :arry, coerce: Array[Grape::API::Boolean]
|
197
294
|
end
|
198
295
|
subject.get '/array' do
|
199
296
|
params[:arry][0].class
|
@@ -204,27 +301,6 @@ describe Grape::Validations::CoerceValidator do
|
|
204
301
|
expect(last_response.body).to eq('TrueClass')
|
205
302
|
end
|
206
303
|
|
207
|
-
it 'Array of Complex' do
|
208
|
-
subject.params do
|
209
|
-
requires :arry, coerce: Array[CoerceValidatorSpec::User]
|
210
|
-
end
|
211
|
-
subject.get '/array' do
|
212
|
-
params[:arry].size
|
213
|
-
end
|
214
|
-
|
215
|
-
get 'array', arry: [31]
|
216
|
-
expect(last_response.status).to eq(400)
|
217
|
-
expect(last_response.body).to eq('arry is invalid')
|
218
|
-
|
219
|
-
get 'array', arry: { id: 31, name: 'Alice' }
|
220
|
-
expect(last_response.status).to eq(400)
|
221
|
-
expect(last_response.body).to eq('arry is invalid')
|
222
|
-
|
223
|
-
get 'array', arry: [{ id: 31, name: 'Alice' }]
|
224
|
-
expect(last_response.status).to eq(200)
|
225
|
-
expect(last_response.body).to eq('1')
|
226
|
-
end
|
227
|
-
|
228
304
|
it 'Array of type implementing parse' do
|
229
305
|
subject.params do
|
230
306
|
requires :uri, type: Array[URI]
|
@@ -249,17 +325,7 @@ describe Grape::Validations::CoerceValidator do
|
|
249
325
|
expect(last_response.body).to eq('Set,URI::HTTP,1')
|
250
326
|
end
|
251
327
|
|
252
|
-
it 'Array of
|
253
|
-
class SecureURIOnly
|
254
|
-
def self.parse(value)
|
255
|
-
URI.parse(value)
|
256
|
-
end
|
257
|
-
|
258
|
-
def self.parsed?(value)
|
259
|
-
value.is_a? URI::HTTPS
|
260
|
-
end
|
261
|
-
end
|
262
|
-
|
328
|
+
it 'Array of a custom type' do
|
263
329
|
subject.params do
|
264
330
|
requires :uri, type: Array[SecureURIOnly]
|
265
331
|
end
|
@@ -291,7 +357,7 @@ describe Grape::Validations::CoerceValidator do
|
|
291
357
|
|
292
358
|
it 'Set of Bools' do
|
293
359
|
subject.params do
|
294
|
-
requires :set, coerce: Set[
|
360
|
+
requires :set, coerce: Set[Grape::API::Boolean]
|
295
361
|
end
|
296
362
|
subject.get '/set' do
|
297
363
|
params[:set].first.class
|
@@ -303,100 +369,73 @@ describe Grape::Validations::CoerceValidator do
|
|
303
369
|
end
|
304
370
|
end
|
305
371
|
|
306
|
-
it 'Bool' do
|
307
|
-
subject.params do
|
308
|
-
requires :bool, coerce: Virtus::Attribute::Boolean
|
309
|
-
end
|
310
|
-
subject.get '/bool' do
|
311
|
-
params[:bool].class
|
312
|
-
end
|
313
|
-
|
314
|
-
get '/bool', bool: 1
|
315
|
-
expect(last_response.status).to eq(200)
|
316
|
-
expect(last_response.body).to eq('TrueClass')
|
317
|
-
|
318
|
-
get '/bool', bool: 0
|
319
|
-
expect(last_response.status).to eq(200)
|
320
|
-
expect(last_response.body).to eq('FalseClass')
|
321
|
-
|
322
|
-
get '/bool', bool: 'false'
|
323
|
-
expect(last_response.status).to eq(200)
|
324
|
-
expect(last_response.body).to eq('FalseClass')
|
325
|
-
|
326
|
-
get '/bool', bool: 'true'
|
327
|
-
expect(last_response.status).to eq(200)
|
328
|
-
expect(last_response.body).to eq('TrueClass')
|
329
|
-
end
|
330
|
-
|
331
372
|
it 'Boolean' do
|
332
373
|
subject.params do
|
333
|
-
|
374
|
+
requires :boolean, type: Boolean
|
334
375
|
end
|
335
376
|
subject.get '/boolean' do
|
336
377
|
params[:boolean].class
|
337
378
|
end
|
338
379
|
|
339
|
-
get '/boolean'
|
340
|
-
expect(last_response.status).to eq(200)
|
341
|
-
expect(last_response.body).to eq('TrueClass')
|
342
|
-
|
343
|
-
get '/boolean', boolean: true
|
344
|
-
expect(last_response.status).to eq(200)
|
345
|
-
expect(last_response.body).to eq('TrueClass')
|
346
|
-
|
347
|
-
get '/boolean', boolean: false
|
348
|
-
expect(last_response.status).to eq(200)
|
349
|
-
expect(last_response.body).to eq('FalseClass')
|
350
|
-
|
351
|
-
get '/boolean', boolean: 'true'
|
380
|
+
get '/boolean', boolean: 1
|
352
381
|
expect(last_response.status).to eq(200)
|
353
382
|
expect(last_response.body).to eq('TrueClass')
|
383
|
+
end
|
354
384
|
|
355
|
-
|
356
|
-
|
357
|
-
|
385
|
+
context 'File' do
|
386
|
+
let(:file) { Rack::Test::UploadedFile.new(__FILE__) }
|
387
|
+
let(:filename) { File.basename(__FILE__).to_s }
|
358
388
|
|
359
|
-
|
360
|
-
|
361
|
-
|
389
|
+
it 'Rack::Multipart::UploadedFile' do
|
390
|
+
subject.params do
|
391
|
+
requires :file, type: Rack::Multipart::UploadedFile
|
392
|
+
end
|
393
|
+
subject.post '/upload' do
|
394
|
+
params[:file][:filename]
|
395
|
+
end
|
362
396
|
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
end
|
397
|
+
post '/upload', file: file
|
398
|
+
expect(last_response.status).to eq(201)
|
399
|
+
expect(last_response.body).to eq(filename)
|
367
400
|
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
end
|
372
|
-
subject.post '/upload' do
|
373
|
-
params[:file][:filename]
|
401
|
+
post '/upload', file: 'not a file'
|
402
|
+
expect(last_response.status).to eq(400)
|
403
|
+
expect(last_response.body).to eq('file is invalid')
|
374
404
|
end
|
375
405
|
|
376
|
-
|
377
|
-
|
378
|
-
|
406
|
+
it 'File' do
|
407
|
+
subject.params do
|
408
|
+
requires :file, coerce: File
|
409
|
+
end
|
410
|
+
subject.post '/upload' do
|
411
|
+
params[:file][:filename]
|
412
|
+
end
|
379
413
|
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
end
|
414
|
+
post '/upload', file: file
|
415
|
+
expect(last_response.status).to eq(201)
|
416
|
+
expect(last_response.body).to eq(filename)
|
384
417
|
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
418
|
+
post '/upload', file: 'not a file'
|
419
|
+
expect(last_response.status).to eq(400)
|
420
|
+
expect(last_response.body).to eq('file is invalid')
|
421
|
+
|
422
|
+
post '/upload', file: { filename: 'fake file', tempfile: '/etc/passwd' }
|
423
|
+
expect(last_response.status).to eq(400)
|
424
|
+
expect(last_response.body).to eq('file is invalid')
|
391
425
|
end
|
392
426
|
|
393
|
-
|
394
|
-
|
395
|
-
|
427
|
+
it 'collection' do
|
428
|
+
subject.params do
|
429
|
+
requires :files, type: Array[File]
|
430
|
+
end
|
431
|
+
subject.post '/upload' do
|
432
|
+
params[:files].first[:filename]
|
433
|
+
end
|
396
434
|
|
397
|
-
|
398
|
-
|
399
|
-
|
435
|
+
post '/upload', files: [file]
|
436
|
+
expect(last_response.status).to eq(201)
|
437
|
+
expect(last_response.body).to eq(filename)
|
438
|
+
end
|
400
439
|
end
|
401
440
|
|
402
441
|
it 'Nests integers' do
|
@@ -413,6 +452,165 @@ describe Grape::Validations::CoerceValidator do
|
|
413
452
|
expect(last_response.status).to eq(200)
|
414
453
|
expect(last_response.body).to eq(integer_class_name)
|
415
454
|
end
|
455
|
+
|
456
|
+
context 'nil values' do
|
457
|
+
context 'primitive types' do
|
458
|
+
Grape::Validations::Types::PRIMITIVES.each do |type|
|
459
|
+
it 'respects the nil value' do
|
460
|
+
subject.params do
|
461
|
+
requires :param, type: type
|
462
|
+
end
|
463
|
+
subject.get '/nil_value' do
|
464
|
+
params[:param].class
|
465
|
+
end
|
466
|
+
|
467
|
+
get '/nil_value', param: nil
|
468
|
+
expect(last_response.status).to eq(200)
|
469
|
+
expect(last_response.body).to eq('NilClass')
|
470
|
+
end
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|
474
|
+
context 'structures types' do
|
475
|
+
Grape::Validations::Types::STRUCTURES.each do |type|
|
476
|
+
it 'respects the nil value' do
|
477
|
+
subject.params do
|
478
|
+
requires :param, type: type
|
479
|
+
end
|
480
|
+
subject.get '/nil_value' do
|
481
|
+
params[:param].class
|
482
|
+
end
|
483
|
+
|
484
|
+
get '/nil_value', param: nil
|
485
|
+
expect(last_response.status).to eq(200)
|
486
|
+
expect(last_response.body).to eq('NilClass')
|
487
|
+
end
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
context 'special types' do
|
492
|
+
Grape::Validations::Types::SPECIAL.each_key do |type|
|
493
|
+
it 'respects the nil value' do
|
494
|
+
subject.params do
|
495
|
+
requires :param, type: type
|
496
|
+
end
|
497
|
+
subject.get '/nil_value' do
|
498
|
+
params[:param].class
|
499
|
+
end
|
500
|
+
|
501
|
+
get '/nil_value', param: nil
|
502
|
+
expect(last_response.status).to eq(200)
|
503
|
+
expect(last_response.body).to eq('NilClass')
|
504
|
+
end
|
505
|
+
end
|
506
|
+
|
507
|
+
context 'variant-member-type collections' do
|
508
|
+
[
|
509
|
+
Array[Integer, String],
|
510
|
+
[Integer, String, Array[Integer, String]]
|
511
|
+
].each do |type|
|
512
|
+
it 'respects the nil value' do
|
513
|
+
subject.params do
|
514
|
+
requires :param, type: type
|
515
|
+
end
|
516
|
+
subject.get '/nil_value' do
|
517
|
+
params[:param].class
|
518
|
+
end
|
519
|
+
|
520
|
+
get '/nil_value', param: nil
|
521
|
+
expect(last_response.status).to eq(200)
|
522
|
+
expect(last_response.body).to eq('NilClass')
|
523
|
+
end
|
524
|
+
end
|
525
|
+
end
|
526
|
+
end
|
527
|
+
end
|
528
|
+
|
529
|
+
context 'empty string' do
|
530
|
+
context 'primitive types' do
|
531
|
+
(Grape::Validations::Types::PRIMITIVES - [String]).each do |type|
|
532
|
+
it "is coerced to nil for type #{type}" do
|
533
|
+
subject.params do
|
534
|
+
requires :param, type: type
|
535
|
+
end
|
536
|
+
subject.get '/empty_string' do
|
537
|
+
params[:param].class
|
538
|
+
end
|
539
|
+
|
540
|
+
get '/empty_string', param: ''
|
541
|
+
expect(last_response.status).to eq(200)
|
542
|
+
expect(last_response.body).to eq('NilClass')
|
543
|
+
end
|
544
|
+
end
|
545
|
+
|
546
|
+
it 'is not coerced to nil for type String' do
|
547
|
+
subject.params do
|
548
|
+
requires :param, type: String
|
549
|
+
end
|
550
|
+
subject.get '/empty_string' do
|
551
|
+
params[:param].class
|
552
|
+
end
|
553
|
+
|
554
|
+
get '/empty_string', param: ''
|
555
|
+
expect(last_response.status).to eq(200)
|
556
|
+
expect(last_response.body).to eq('String')
|
557
|
+
end
|
558
|
+
end
|
559
|
+
|
560
|
+
context 'structures types' do
|
561
|
+
(Grape::Validations::Types::STRUCTURES - [Hash]).each do |type|
|
562
|
+
it "is coerced to nil for type #{type}" do
|
563
|
+
subject.params do
|
564
|
+
requires :param, type: type
|
565
|
+
end
|
566
|
+
subject.get '/empty_string' do
|
567
|
+
params[:param].class
|
568
|
+
end
|
569
|
+
|
570
|
+
get '/empty_string', param: ''
|
571
|
+
expect(last_response.status).to eq(200)
|
572
|
+
expect(last_response.body).to eq('NilClass')
|
573
|
+
end
|
574
|
+
end
|
575
|
+
end
|
576
|
+
|
577
|
+
context 'special types' do
|
578
|
+
(Grape::Validations::Types::SPECIAL.keys - [File, Rack::Multipart::UploadedFile]).each do |type|
|
579
|
+
it "is coerced to nil for type #{type}" do
|
580
|
+
subject.params do
|
581
|
+
requires :param, type: type
|
582
|
+
end
|
583
|
+
subject.get '/empty_string' do
|
584
|
+
params[:param].class
|
585
|
+
end
|
586
|
+
|
587
|
+
get '/empty_string', param: ''
|
588
|
+
expect(last_response.status).to eq(200)
|
589
|
+
expect(last_response.body).to eq('NilClass')
|
590
|
+
end
|
591
|
+
end
|
592
|
+
|
593
|
+
context 'variant-member-type collections' do
|
594
|
+
[
|
595
|
+
Array[Integer, String],
|
596
|
+
[Integer, String, Array[Integer, String]]
|
597
|
+
].each do |type|
|
598
|
+
it "is coerced to nil for type #{type}" do
|
599
|
+
subject.params do
|
600
|
+
requires :param, type: type
|
601
|
+
end
|
602
|
+
subject.get '/empty_string' do
|
603
|
+
params[:param].class
|
604
|
+
end
|
605
|
+
|
606
|
+
get '/empty_string', param: ''
|
607
|
+
expect(last_response.status).to eq(200)
|
608
|
+
expect(last_response.body).to eq('NilClass')
|
609
|
+
end
|
610
|
+
end
|
611
|
+
end
|
612
|
+
end
|
613
|
+
end
|
416
614
|
end
|
417
615
|
|
418
616
|
context 'using coerce_with' do
|
@@ -435,19 +633,43 @@ describe Grape::Validations::CoerceValidator do
|
|
435
633
|
|
436
634
|
it 'parses parameters with Array[String] type' do
|
437
635
|
subject.params do
|
438
|
-
requires :values, type: Array[String], coerce_with: ->(val) { val.split(/\s+/)
|
636
|
+
requires :values, type: Array[String], coerce_with: ->(val) { val.split(/\s+/) }
|
439
637
|
end
|
440
|
-
subject.get '/
|
638
|
+
subject.get '/strings' do
|
441
639
|
params[:values]
|
442
640
|
end
|
443
641
|
|
444
|
-
get '/
|
642
|
+
get '/strings', values: '1 2 3 4'
|
445
643
|
expect(last_response.status).to eq(200)
|
446
644
|
expect(JSON.parse(last_response.body)).to eq(%w[1 2 3 4])
|
447
645
|
|
448
|
-
get '/
|
646
|
+
get '/strings', values: 'a b c d'
|
449
647
|
expect(last_response.status).to eq(200)
|
450
|
-
expect(JSON.parse(last_response.body)).to eq(%w[
|
648
|
+
expect(JSON.parse(last_response.body)).to eq(%w[a b c d])
|
649
|
+
end
|
650
|
+
|
651
|
+
it 'parses parameters with Array[Array[String]] type and coerce_with' do
|
652
|
+
subject.params do
|
653
|
+
requires :values, type: Array[Array[String]], coerce_with: ->(val) { val.is_a?(String) ? [val.split(/,/).map(&:strip)] : val }
|
654
|
+
end
|
655
|
+
subject.post '/coerce_nested_strings' do
|
656
|
+
params[:values]
|
657
|
+
end
|
658
|
+
|
659
|
+
post '/coerce_nested_strings', ::Grape::Json.dump(values: 'a,b,c,d'), 'CONTENT_TYPE' => 'application/json'
|
660
|
+
expect(last_response.status).to eq(201)
|
661
|
+
expect(JSON.parse(last_response.body)).to eq([%w[a b c d]])
|
662
|
+
|
663
|
+
post '/coerce_nested_strings', ::Grape::Json.dump(values: [%w[a c], %w[b]]), 'CONTENT_TYPE' => 'application/json'
|
664
|
+
expect(last_response.status).to eq(201)
|
665
|
+
expect(JSON.parse(last_response.body)).to eq([%w[a c], %w[b]])
|
666
|
+
|
667
|
+
post '/coerce_nested_strings', ::Grape::Json.dump(values: [[]]), 'CONTENT_TYPE' => 'application/json'
|
668
|
+
expect(last_response.status).to eq(201)
|
669
|
+
expect(JSON.parse(last_response.body)).to eq([[]])
|
670
|
+
|
671
|
+
post '/coerce_nested_strings', ::Grape::Json.dump(values: [['a', { bar: 0 }], ['b']]), 'CONTENT_TYPE' => 'application/json'
|
672
|
+
expect(last_response.status).to eq(400)
|
451
673
|
end
|
452
674
|
|
453
675
|
it 'parses parameters with Array[Integer] type' do
|
@@ -484,6 +706,44 @@ describe Grape::Validations::CoerceValidator do
|
|
484
706
|
expect(JSON.parse(last_response.body)).to eq([1, 1, 1, 1])
|
485
707
|
end
|
486
708
|
|
709
|
+
context 'Array type and coerce_with should' do
|
710
|
+
before do
|
711
|
+
subject.params do
|
712
|
+
optional :arr, type: Array, coerce_with: (lambda do |val|
|
713
|
+
if val.nil?
|
714
|
+
[]
|
715
|
+
else
|
716
|
+
val
|
717
|
+
end
|
718
|
+
end)
|
719
|
+
end
|
720
|
+
subject.get '/' do
|
721
|
+
params[:arr].class.to_s
|
722
|
+
end
|
723
|
+
end
|
724
|
+
|
725
|
+
it 'coerce nil value to array' do
|
726
|
+
get '/', arr: nil
|
727
|
+
|
728
|
+
expect(last_response.status).to eq(200)
|
729
|
+
expect(last_response.body).to eq('Array')
|
730
|
+
end
|
731
|
+
|
732
|
+
it 'not coerce missing field' do
|
733
|
+
get '/'
|
734
|
+
|
735
|
+
expect(last_response.status).to eq(200)
|
736
|
+
expect(last_response.body).to eq('NilClass')
|
737
|
+
end
|
738
|
+
|
739
|
+
it 'coerce array as array' do
|
740
|
+
get '/', arr: []
|
741
|
+
|
742
|
+
expect(last_response.status).to eq(200)
|
743
|
+
expect(last_response.body).to eq('Array')
|
744
|
+
end
|
745
|
+
end
|
746
|
+
|
487
747
|
it 'uses parse where available' do
|
488
748
|
subject.params do
|
489
749
|
requires :ints, type: Array, coerce_with: JSON do
|
@@ -532,6 +792,84 @@ describe Grape::Validations::CoerceValidator do
|
|
532
792
|
expect(last_response.body).to eq('3')
|
533
793
|
end
|
534
794
|
|
795
|
+
context 'Integer type and coerce_with should' do
|
796
|
+
before do
|
797
|
+
subject.params do
|
798
|
+
optional :int, type: Integer, coerce_with: (lambda do |val|
|
799
|
+
if val.nil?
|
800
|
+
0
|
801
|
+
else
|
802
|
+
val.to_i
|
803
|
+
end
|
804
|
+
end)
|
805
|
+
end
|
806
|
+
subject.get '/' do
|
807
|
+
params[:int].class.to_s
|
808
|
+
end
|
809
|
+
end
|
810
|
+
|
811
|
+
it 'coerce nil value to integer' do
|
812
|
+
get '/', int: nil
|
813
|
+
|
814
|
+
expect(last_response.status).to eq(200)
|
815
|
+
expect(last_response.body).to eq('Integer')
|
816
|
+
end
|
817
|
+
|
818
|
+
it 'not coerce missing field' do
|
819
|
+
get '/'
|
820
|
+
|
821
|
+
expect(last_response.status).to eq(200)
|
822
|
+
expect(last_response.body).to eq('NilClass')
|
823
|
+
end
|
824
|
+
|
825
|
+
it 'coerce integer as integer' do
|
826
|
+
get '/', int: 1
|
827
|
+
|
828
|
+
expect(last_response.status).to eq(200)
|
829
|
+
expect(last_response.body).to eq('Integer')
|
830
|
+
end
|
831
|
+
end
|
832
|
+
|
833
|
+
context 'Integer type and coerce_with potentially returning nil' do
|
834
|
+
before do
|
835
|
+
subject.params do
|
836
|
+
requires :int, type: Integer, coerce_with: (lambda do |val|
|
837
|
+
if val == '0'
|
838
|
+
nil
|
839
|
+
elsif val.match?(/^-?\d+$/)
|
840
|
+
val.to_i
|
841
|
+
else
|
842
|
+
val
|
843
|
+
end
|
844
|
+
end)
|
845
|
+
end
|
846
|
+
subject.get '/' do
|
847
|
+
params[:int].class.to_s
|
848
|
+
end
|
849
|
+
end
|
850
|
+
|
851
|
+
it 'accepts value that coerces to nil' do
|
852
|
+
get '/', int: '0'
|
853
|
+
|
854
|
+
expect(last_response.status).to eq(200)
|
855
|
+
expect(last_response.body).to eq('NilClass')
|
856
|
+
end
|
857
|
+
|
858
|
+
it 'coerces to Integer' do
|
859
|
+
get '/', int: '1'
|
860
|
+
|
861
|
+
expect(last_response.status).to eq(200)
|
862
|
+
expect(last_response.body).to eq('Integer')
|
863
|
+
end
|
864
|
+
|
865
|
+
it 'returns invalid value if coercion returns a wrong type' do
|
866
|
+
get '/', int: 'lol'
|
867
|
+
|
868
|
+
expect(last_response.status).to eq(400)
|
869
|
+
expect(last_response.body).to eq('int is invalid')
|
870
|
+
end
|
871
|
+
end
|
872
|
+
|
535
873
|
it 'must be supplied with :type or :coerce' do
|
536
874
|
expect do
|
537
875
|
subject.params do
|
@@ -572,7 +910,7 @@ describe Grape::Validations::CoerceValidator do
|
|
572
910
|
expect(last_response.status).to eq(200)
|
573
911
|
expect(last_response.body).to eq('arrays work')
|
574
912
|
|
575
|
-
get '/', splines: [{ x: 2, ints: [] }, { x: 3, ints: [4], obj: { y: 'quack' } }]
|
913
|
+
get '/', splines: [{ x: 2, ints: [5] }, { x: 3, ints: [4], obj: { y: 'quack' } }]
|
576
914
|
expect(last_response.status).to eq(200)
|
577
915
|
expect(last_response.body).to eq('arrays work')
|
578
916
|
|
@@ -588,7 +926,7 @@ describe Grape::Validations::CoerceValidator do
|
|
588
926
|
expect(last_response.status).to eq(400)
|
589
927
|
expect(last_response.body).to eq('splines[x] does not have a valid value')
|
590
928
|
|
591
|
-
get '/', splines: [{ x: 1, ints: [] }, { x: 4, ints: [] }]
|
929
|
+
get '/', splines: [{ x: 1, ints: [5] }, { x: 4, ints: [6] }]
|
592
930
|
expect(last_response.status).to eq(400)
|
593
931
|
expect(last_response.body).to eq('splines[x] does not have a valid value')
|
594
932
|
end
|
@@ -906,14 +1244,17 @@ describe Grape::Validations::CoerceValidator do
|
|
906
1244
|
end
|
907
1245
|
|
908
1246
|
context 'converter' do
|
909
|
-
it 'does not build
|
1247
|
+
it 'does not build a coercer multiple times' do
|
910
1248
|
subject.params do
|
911
1249
|
requires :something, type: Array[String]
|
912
1250
|
end
|
913
1251
|
subject.get do
|
914
1252
|
end
|
915
1253
|
|
916
|
-
expect(
|
1254
|
+
expect(Grape::Validations::Types::ArrayCoercer).to(
|
1255
|
+
receive(:new).at_most(:once).and_call_original
|
1256
|
+
)
|
1257
|
+
|
917
1258
|
10.times { get '/' }
|
918
1259
|
end
|
919
1260
|
end
|