grape 1.2.5 → 1.3.1
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 +45 -0
- data/README.md +7 -6
- data/UPGRADING.md +43 -0
- data/grape.gemspec +10 -1
- data/lib/grape/api/helpers.rb +2 -0
- data/lib/grape/api/instance.rb +8 -6
- data/lib/grape/api.rb +4 -2
- data/lib/grape/config.rb +2 -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 +2 -0
- data/lib/grape/dsl/configuration.rb +2 -0
- data/lib/grape/dsl/desc.rb +2 -0
- data/lib/grape/dsl/headers.rb +2 -0
- data/lib/grape/dsl/helpers.rb +4 -2
- data/lib/grape/dsl/inside_route.rb +15 -11
- data/lib/grape/dsl/logger.rb +2 -0
- data/lib/grape/dsl/middleware.rb +2 -0
- data/lib/grape/dsl/parameters.rb +8 -6
- data/lib/grape/dsl/request_response.rb +4 -2
- data/lib/grape/dsl/routing.rb +9 -5
- data/lib/grape/dsl/settings.rb +7 -1
- data/lib/grape/dsl/validations.rb +2 -0
- data/lib/grape/eager_load.rb +2 -0
- data/lib/grape/endpoint.rb +13 -7
- 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/error_formatter.rb +3 -1
- data/lib/grape/exceptions/base.rb +11 -13
- 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 +2 -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 +3 -1
- data/lib/grape/exceptions/validation_array_errors.rb +2 -0
- data/lib/grape/exceptions/validation_errors.rb +13 -12
- 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/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/formatter.rb +5 -3
- data/lib/grape/http/headers.rb +49 -18
- data/lib/grape/middleware/auth/base.rb +2 -0
- 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 +5 -5
- data/lib/grape/middleware/error.rb +3 -1
- data/lib/grape/middleware/filter.rb +2 -0
- data/lib/grape/middleware/formatter.rb +5 -3
- data/lib/grape/middleware/globals.rb +2 -0
- data/lib/grape/middleware/helpers.rb +2 -0
- data/lib/grape/middleware/stack.rb +4 -1
- data/lib/grape/middleware/versioner/accept_version_header.rb +2 -0
- data/lib/grape/middleware/versioner/header.rb +5 -3
- data/lib/grape/middleware/versioner/param.rb +3 -1
- data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -0
- data/lib/grape/middleware/versioner/path.rb +3 -1
- data/lib/grape/middleware/versioner.rb +2 -0
- data/lib/grape/namespace.rb +14 -2
- data/lib/grape/parser/json.rb +2 -0
- data/lib/grape/parser/xml.rb +2 -0
- data/lib/grape/parser.rb +3 -1
- data/lib/grape/path.rb +13 -1
- data/lib/grape/presenters/presenter.rb +2 -0
- data/lib/grape/request.rb +15 -8
- data/lib/grape/router/attribute_translator.rb +18 -8
- data/lib/grape/router/pattern.rb +20 -16
- data/lib/grape/router/route.rb +9 -4
- data/lib/grape/router.rb +26 -12
- data/lib/grape/serve_file/file_body.rb +2 -0
- data/lib/grape/serve_file/file_response.rb +2 -0
- data/lib/grape/serve_file/sendfile_response.rb +2 -0
- data/lib/grape/util/base_inheritable.rb +6 -0
- data/lib/grape/util/cache.rb +20 -0
- data/lib/grape/util/endpoint_configuration.rb +2 -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 +2 -0
- data/lib/grape/util/json.rb +2 -0
- data/lib/grape/util/lazy_block.rb +2 -0
- data/lib/grape/util/lazy_object.rb +43 -0
- data/lib/grape/util/lazy_value.rb +2 -0
- data/lib/grape/util/registrable.rb +2 -0
- data/lib/grape/util/reverse_stackable_values.rb +3 -1
- data/lib/grape/util/stackable_values.rb +9 -21
- data/lib/grape/util/strict_hash_configuration.rb +2 -0
- data/lib/grape/util/xml.rb +2 -0
- data/lib/grape/validations/attributes_iterator.rb +3 -3
- data/lib/grape/validations/multiple_attributes_iterator.rb +2 -0
- data/lib/grape/validations/params_scope.rb +24 -11
- data/lib/grape/validations/single_attribute_iterator.rb +13 -2
- data/lib/grape/validations/types/array_coercer.rb +56 -0
- data/lib/grape/validations/types/build_coercer.rb +49 -48
- data/lib/grape/validations/types/custom_type_coercer.rb +15 -49
- data/lib/grape/validations/types/custom_type_collection_coercer.rb +10 -25
- data/lib/grape/validations/types/dry_type_coercer.rb +41 -0
- data/lib/grape/validations/types/file.rb +11 -9
- data/lib/grape/validations/types/json.rb +11 -8
- data/lib/grape/validations/types/multiple_type_coercer.rb +14 -33
- data/lib/grape/validations/types/primitive_coercer.rb +61 -0
- data/lib/grape/validations/types/set_coercer.rb +38 -0
- data/lib/grape/validations/types/variant_collection_coercer.rb +4 -12
- data/lib/grape/validations/types.rb +7 -30
- data/lib/grape/validations/validator_factory.rb +2 -0
- data/lib/grape/validations/validators/all_or_none.rb +3 -1
- data/lib/grape/validations/validators/allow_blank.rb +3 -1
- data/lib/grape/validations/validators/as.rb +2 -0
- data/lib/grape/validations/validators/at_least_one_of.rb +3 -1
- data/lib/grape/validations/validators/base.rb +8 -5
- data/lib/grape/validations/validators/coerce.rb +44 -27
- data/lib/grape/validations/validators/default.rb +2 -0
- data/lib/grape/validations/validators/exactly_one_of.rb +6 -2
- data/lib/grape/validations/validators/except_values.rb +3 -1
- data/lib/grape/validations/validators/multiple_params_base.rb +2 -0
- data/lib/grape/validations/validators/mutual_exclusion.rb +3 -1
- data/lib/grape/validations/validators/presence.rb +3 -1
- data/lib/grape/validations/validators/regexp.rb +3 -1
- data/lib/grape/validations/validators/same_as.rb +6 -3
- data/lib/grape/validations/validators/values.rb +17 -5
- data/lib/grape/validations.rb +2 -0
- data/lib/grape/version.rb +3 -1
- data/lib/grape.rb +4 -5
- 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 +5 -3
- data/spec/grape/api/inherited_helpers_spec.rb +2 -0
- data/spec/grape/api/instance_spec.rb +54 -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 +2 -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 +2 -0
- data/spec/grape/api_spec.rb +34 -11
- data/spec/grape/config_spec.rb +2 -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 +2 -0
- 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 +2 -0
- data/spec/grape/dsl/logger_spec.rb +2 -0
- data/spec/grape/dsl/middleware_spec.rb +2 -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 +2 -0
- data/spec/grape/dsl/settings_spec.rb +2 -0
- data/spec/grape/dsl/validations_spec.rb +2 -0
- data/spec/grape/endpoint_spec.rb +3 -1
- data/spec/grape/entity_spec.rb +2 -0
- data/spec/grape/exceptions/base_spec.rb +3 -1
- 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 +2 -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 +4 -2
- 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 +2 -0
- data/spec/grape/integration/rack_spec.rb +3 -1
- 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 +2 -0
- data/spec/grape/middleware/auth/strategies_spec.rb +2 -0
- data/spec/grape/middleware/base_spec.rb +2 -0
- data/spec/grape/middleware/error_spec.rb +2 -0
- data/spec/grape/middleware/exception_spec.rb +3 -1
- data/spec/grape/middleware/formatter_spec.rb +19 -12
- data/spec/grape/middleware/globals_spec.rb +2 -0
- data/spec/grape/middleware/stack_spec.rb +11 -0
- data/spec/grape/middleware/versioner/accept_version_header_spec.rb +3 -1
- data/spec/grape/middleware/versioner/header_spec.rb +3 -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 +2 -0
- data/spec/grape/parser_spec.rb +7 -5
- data/spec/grape/path_spec.rb +2 -0
- data/spec/grape/presenters/presenter_spec.rb +2 -0
- data/spec/grape/request_spec.rb +2 -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 +2 -0
- data/spec/grape/validations/params_scope_spec.rb +3 -1
- data/spec/grape/validations/single_attribute_iterator_spec.rb +18 -4
- data/spec/grape/validations/types/primitive_coercer_spec.rb +75 -0
- data/spec/grape/validations/types_spec.rb +8 -35
- data/spec/grape/validations/validators/all_or_none_spec.rb +2 -0
- data/spec/grape/validations/validators/allow_blank_spec.rb +2 -0
- data/spec/grape/validations/validators/at_least_one_of_spec.rb +2 -0
- data/spec/grape/validations/validators/coerce_spec.rb +51 -110
- data/spec/grape/validations/validators/default_spec.rb +2 -0
- data/spec/grape/validations/validators/exactly_one_of_spec.rb +14 -12
- data/spec/grape/validations/validators/except_values_spec.rb +3 -1
- data/spec/grape/validations/validators/mutual_exclusion_spec.rb +2 -0
- 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 +2 -0
- data/spec/grape/validations/validators/values_spec.rb +29 -4
- data/spec/grape/validations_spec.rb +69 -15
- 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 +2 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/support/basic_auth_encode_helpers.rb +2 -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 +4 -2
- metadata +126 -112
- 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
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'stackable_values'
|
2
4
|
|
3
5
|
module Grape
|
@@ -7,7 +9,7 @@ module Grape
|
|
7
9
|
|
8
10
|
def concat_values(inherited_value, new_value)
|
9
11
|
[].tap do |value|
|
10
|
-
value.concat(new_value)
|
12
|
+
value.concat(new_value) if new_value
|
11
13
|
value.concat(inherited_value)
|
12
14
|
end
|
13
15
|
end
|
@@ -1,31 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'base_inheritable'
|
2
4
|
|
3
5
|
module Grape
|
4
6
|
module Util
|
5
7
|
class StackableValues < BaseInheritable
|
6
|
-
|
7
|
-
|
8
|
-
def initialize(*_args)
|
9
|
-
super
|
10
|
-
|
11
|
-
@frozen_values = {}
|
12
|
-
end
|
13
|
-
|
8
|
+
# Even if there is no value, an empty array will be returned.
|
14
9
|
def [](name)
|
15
|
-
|
16
|
-
|
17
|
-
inherited_value = @inherited_values[name]
|
18
|
-
new_value = @new_values[name] || []
|
10
|
+
inherited_value = inherited_values[name]
|
11
|
+
new_value = new_values[name]
|
19
12
|
|
20
|
-
return new_value unless inherited_value
|
13
|
+
return new_value || [] unless inherited_value
|
21
14
|
|
22
15
|
concat_values(inherited_value, new_value)
|
23
16
|
end
|
24
17
|
|
25
18
|
def []=(name, value)
|
26
|
-
|
27
|
-
|
28
|
-
@new_values[name].push value
|
19
|
+
new_values[name] ||= []
|
20
|
+
new_values[name].push value
|
29
21
|
end
|
30
22
|
|
31
23
|
def to_hash
|
@@ -34,16 +26,12 @@ module Grape
|
|
34
26
|
end
|
35
27
|
end
|
36
28
|
|
37
|
-
def freeze_value(key)
|
38
|
-
@frozen_values[key] = self[key].freeze
|
39
|
-
end
|
40
|
-
|
41
29
|
protected
|
42
30
|
|
43
31
|
def concat_values(inherited_value, new_value)
|
44
32
|
[].tap do |value|
|
45
33
|
value.concat(inherited_value)
|
46
|
-
value.concat(new_value)
|
34
|
+
value.concat(new_value) if new_value
|
47
35
|
end
|
48
36
|
end
|
49
37
|
end
|
data/lib/grape/util/xml.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Grape
|
2
4
|
module Validations
|
3
5
|
class AttributesIterator
|
@@ -29,9 +31,7 @@ module Grape
|
|
29
31
|
|
30
32
|
if @scope.type == Array
|
31
33
|
next unless @original_params.is_a?(Array) # do not validate content of array if it isn't array
|
32
|
-
|
33
|
-
end
|
34
|
-
if inside_array
|
34
|
+
|
35
35
|
# fill current and parent scopes with correct array indicies
|
36
36
|
parent_scope = @scope.parent
|
37
37
|
parent_indicies.each do |parent_index|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Grape
|
2
4
|
module Validations
|
3
5
|
class ParamsScope
|
@@ -43,8 +45,10 @@ module Grape
|
|
43
45
|
# @return [Boolean] whether or not this entire scope needs to be
|
44
46
|
# validated
|
45
47
|
def should_validate?(parameters)
|
46
|
-
|
47
|
-
|
48
|
+
scoped_params = params(parameters)
|
49
|
+
|
50
|
+
return false if @optional && (scoped_params.blank? || all_element_blank?(scoped_params))
|
51
|
+
return false unless meets_dependency?(scoped_params, parameters)
|
48
52
|
return true if parent.nil?
|
49
53
|
parent.should_validate?(parameters)
|
50
54
|
end
|
@@ -121,7 +125,7 @@ module Grape
|
|
121
125
|
# @param attrs [Array] (see Grape::DSL::Parameters#requires)
|
122
126
|
def push_declared_params(attrs, **opts)
|
123
127
|
if lateral?
|
124
|
-
@parent.push_declared_params(attrs, opts)
|
128
|
+
@parent.push_declared_params(attrs, **opts)
|
125
129
|
else
|
126
130
|
if opts && opts[:as]
|
127
131
|
@api.route_setting(:renamed_params, @api.route_setting(:renamed_params) || [])
|
@@ -240,7 +244,7 @@ module Grape
|
|
240
244
|
end
|
241
245
|
|
242
246
|
def validates(attrs, validations)
|
243
|
-
doc_attrs = { required: validations.
|
247
|
+
doc_attrs = { required: validations.key?(:presence) }
|
244
248
|
|
245
249
|
coerce_type = infer_coercion(validations)
|
246
250
|
|
@@ -280,9 +284,7 @@ module Grape
|
|
280
284
|
full_attrs = attrs.collect { |name| { name: name, full_name: full_name(name) } }
|
281
285
|
@api.document_attribute(full_attrs, doc_attrs)
|
282
286
|
|
283
|
-
|
284
|
-
opts = {}
|
285
|
-
opts[:fail_fast] = validations.delete(:fail_fast) || false
|
287
|
+
opts = derive_validator_options(validations)
|
286
288
|
|
287
289
|
# Validate for presence before any other validators
|
288
290
|
if validations.key?(:presence) && validations[:presence]
|
@@ -427,8 +429,8 @@ module Grape
|
|
427
429
|
values_list.each do |values|
|
428
430
|
next if !values || values.is_a?(Proc)
|
429
431
|
value_types = values.is_a?(Range) ? [values.begin, values.end] : values
|
430
|
-
if coerce_type ==
|
431
|
-
value_types = value_types.map { |type|
|
432
|
+
if coerce_type == Grape::API::Boolean
|
433
|
+
value_types = value_types.map { |type| Grape::API::Boolean.build(type) }
|
432
434
|
end
|
433
435
|
unless value_types.all? { |v| v.is_a? coerce_type }
|
434
436
|
raise Grape::Exceptions::IncompatibleOptionValues.new(:type, coerce_type, :values, values)
|
@@ -446,8 +448,19 @@ module Grape
|
|
446
448
|
validations[type].respond_to?(:key?) && validations[type].key?(key) && !validations[type][key].nil?
|
447
449
|
end
|
448
450
|
|
449
|
-
def all_element_blank?(
|
450
|
-
|
451
|
+
def all_element_blank?(scoped_params)
|
452
|
+
scoped_params.respond_to?(:all?) && scoped_params.all?(&:blank?)
|
453
|
+
end
|
454
|
+
|
455
|
+
# Validators don't have access to each other and they don't need, however,
|
456
|
+
# some validators might influence others, so their options should be shared
|
457
|
+
def derive_validator_options(validations)
|
458
|
+
allow_blank = validations[:allow_blank]
|
459
|
+
|
460
|
+
{
|
461
|
+
allow_blank: allow_blank.is_a?(Hash) ? allow_blank[:value] : allow_blank,
|
462
|
+
fail_fast: validations.delete(:fail_fast) || false
|
463
|
+
}
|
451
464
|
end
|
452
465
|
end
|
453
466
|
end
|
@@ -1,13 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Grape
|
2
4
|
module Validations
|
3
5
|
class SingleAttributeIterator < AttributesIterator
|
4
6
|
private
|
5
7
|
|
6
|
-
def yield_attributes(
|
8
|
+
def yield_attributes(val, attrs)
|
7
9
|
attrs.each do |attr_name|
|
8
|
-
yield
|
10
|
+
yield val, attr_name, empty?(val)
|
9
11
|
end
|
10
12
|
end
|
13
|
+
|
14
|
+
# Primitives like Integers and Booleans don't respond to +empty?+.
|
15
|
+
# It could be possible to use +blank?+ instead, but
|
16
|
+
#
|
17
|
+
# false.blank?
|
18
|
+
# => true
|
19
|
+
def empty?(val)
|
20
|
+
val.respond_to?(:empty?) ? val.empty? : val.nil?
|
21
|
+
end
|
11
22
|
end
|
12
23
|
end
|
13
24
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'dry_type_coercer'
|
4
|
+
|
5
|
+
module Grape
|
6
|
+
module Validations
|
7
|
+
module Types
|
8
|
+
# Coerces elements in an array. It might be an array of strings or integers or
|
9
|
+
# anything else.
|
10
|
+
#
|
11
|
+
# It could've been possible to use an +of+
|
12
|
+
# method (https://dry-rb.org/gems/dry-types/1.2/array-with-member/)
|
13
|
+
# provided by dry-types. Unfortunately, it doesn't work for Grape because of
|
14
|
+
# behavior of Virtus which was used earlier, a `Grape::Validations::Types::PrimitiveCoercer`
|
15
|
+
# maintains Virtus behavior in coercing.
|
16
|
+
class ArrayCoercer < DryTypeCoercer
|
17
|
+
def initialize(type, strict = false)
|
18
|
+
super
|
19
|
+
|
20
|
+
@coercer = scope::Array
|
21
|
+
@elem_coercer = PrimitiveCoercer.new(type.first, strict)
|
22
|
+
end
|
23
|
+
|
24
|
+
def call(_val)
|
25
|
+
collection = super
|
26
|
+
|
27
|
+
return collection if collection.is_a?(InvalidValue)
|
28
|
+
|
29
|
+
coerce_elements collection
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
|
34
|
+
def coerce_elements(collection)
|
35
|
+
collection.each_with_index do |elem, index|
|
36
|
+
return InvalidValue.new if reject?(elem)
|
37
|
+
|
38
|
+
coerced_elem = @elem_coercer.call(elem)
|
39
|
+
|
40
|
+
return coerced_elem if coerced_elem.is_a?(InvalidValue)
|
41
|
+
|
42
|
+
collection[index] = coerced_elem
|
43
|
+
end
|
44
|
+
|
45
|
+
collection
|
46
|
+
end
|
47
|
+
|
48
|
+
# This method maintaine logic which was defined by Virtus for arrays.
|
49
|
+
# Virtus doesn't allow nil in arrays.
|
50
|
+
def reject?(val)
|
51
|
+
val.nil?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -1,78 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'array_coercer'
|
4
|
+
require_relative 'set_coercer'
|
5
|
+
require_relative 'primitive_coercer'
|
6
|
+
|
1
7
|
module Grape
|
2
8
|
module Validations
|
3
9
|
module Types
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
10
|
+
# Chooses the best coercer for the given type. For example, if the type
|
11
|
+
# is Integer, it will return a coercer which will be able to coerce a value
|
12
|
+
# to the integer.
|
13
|
+
#
|
14
|
+
# There are a few very special coercers which might be returned.
|
8
15
|
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
16
|
+
# +Grape::Types::MultipleTypeCoercer+ is a coercer which is returned when
|
17
|
+
# the given type implies values in an array with different types.
|
18
|
+
# For example, +[Integer, String]+ allows integer and string values in
|
19
|
+
# an array.
|
13
20
|
#
|
14
|
-
#
|
15
|
-
#
|
21
|
+
# +Grape::Types::CustomTypeCoercer+ is a coercer which is returned when
|
22
|
+
# a method is specified by a user with +coerce_with+ option or the user
|
23
|
+
# specifies a custom type which implements requirments of
|
24
|
+
# +Grape::Types::CustomTypeCoercer+.
|
25
|
+
#
|
26
|
+
# +Grape::Types::CustomTypeCollectionCoercer+ is a very similar to the
|
27
|
+
# previous one, but it expects an array or set of values having a custom
|
28
|
+
# type implemented by the user.
|
29
|
+
#
|
30
|
+
# There is also a group of custom types implemented by Grape, check
|
31
|
+
# +Grape::Validations::Types::SPECIAL+ to get the full list.
|
16
32
|
#
|
17
33
|
# @param type [Class] the type to which input strings
|
18
34
|
# should be coerced
|
19
35
|
# @param method [Class,#call] the coercion method to use
|
20
|
-
# @return [
|
36
|
+
# @return [Object] object to be used
|
21
37
|
# for coercion and type validation
|
22
|
-
def self.build_coercer(type, method
|
23
|
-
cache_instance(type, method) do
|
24
|
-
create_coercer_instance(type, method)
|
38
|
+
def self.build_coercer(type, method: nil, strict: false)
|
39
|
+
cache_instance(type, method, strict) do
|
40
|
+
create_coercer_instance(type, method, strict)
|
25
41
|
end
|
26
42
|
end
|
27
43
|
|
28
|
-
def self.create_coercer_instance(type, method
|
29
|
-
# Accept pre-rolled virtus attributes without interference
|
30
|
-
return type if type.is_a? Virtus::Attribute
|
31
|
-
|
32
|
-
converter_options = {
|
33
|
-
nullify_blank: true
|
34
|
-
}
|
35
|
-
conversion_type = if method == JSON
|
36
|
-
Object
|
37
|
-
# because we want just parsed JSON content:
|
38
|
-
# if type is Array and data is `"{}"`
|
39
|
-
# result will be [] because Virtus converts hashes
|
40
|
-
# to arrays
|
41
|
-
else
|
42
|
-
type
|
43
|
-
end
|
44
|
-
|
44
|
+
def self.create_coercer_instance(type, method, strict)
|
45
45
|
# Use a special coercer for multiply-typed parameters.
|
46
46
|
if Types.multiple?(type)
|
47
|
-
|
48
|
-
conversion_type = Object
|
47
|
+
MultipleTypeCoercer.new(type, method)
|
49
48
|
|
50
49
|
# Use a special coercer for custom types and coercion methods.
|
51
50
|
elsif method || Types.custom?(type)
|
52
|
-
|
51
|
+
CustomTypeCoercer.new(type, method)
|
53
52
|
|
54
53
|
# Special coercer for collections of types that implement a parse method.
|
55
54
|
# CustomTypeCoercer (above) already handles such types when an explicit coercion
|
56
55
|
# method is supplied.
|
57
56
|
elsif Types.collection_of_custom?(type)
|
58
|
-
|
57
|
+
Types::CustomTypeCollectionCoercer.new(
|
59
58
|
type.first, type.is_a?(Set)
|
60
59
|
)
|
61
|
-
|
62
|
-
# Grape swaps in its own Virtus::Attribute implementations
|
63
|
-
# for certain special types that merit first-class support
|
64
|
-
# (but not if a custom coercion method has been supplied).
|
65
60
|
elsif Types.special?(type)
|
66
|
-
|
61
|
+
Types::SPECIAL[type].new
|
62
|
+
elsif type.is_a?(Array)
|
63
|
+
ArrayCoercer.new type, strict
|
64
|
+
elsif type.is_a?(Set)
|
65
|
+
SetCoercer.new type, strict
|
66
|
+
else
|
67
|
+
PrimitiveCoercer.new type, strict
|
67
68
|
end
|
68
|
-
|
69
|
-
# Virtus will infer coercion and validation rules
|
70
|
-
# for many common ruby types.
|
71
|
-
Virtus::Attribute.build(conversion_type, converter_options)
|
72
69
|
end
|
73
70
|
|
74
|
-
def self.cache_instance(type, method, &_block)
|
75
|
-
key = cache_key(type, method)
|
71
|
+
def self.cache_instance(type, method, strict, &_block)
|
72
|
+
key = cache_key(type, method, strict)
|
76
73
|
|
77
74
|
return @__cache[key] if @__cache.key?(key)
|
78
75
|
|
@@ -85,8 +82,12 @@ module Grape
|
|
85
82
|
instance
|
86
83
|
end
|
87
84
|
|
88
|
-
def self.cache_key(type, method)
|
89
|
-
[type, method].
|
85
|
+
def self.cache_key(type, method, strict)
|
86
|
+
[type, method, strict].each_with_object(+'_') do |val, memo|
|
87
|
+
next if val.nil?
|
88
|
+
|
89
|
+
memo << '_' << val.to_s
|
90
|
+
end
|
90
91
|
end
|
91
92
|
|
92
93
|
instance_variable_set(:@__cache, {})
|
@@ -1,21 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Grape
|
2
4
|
module Validations
|
3
5
|
module Types
|
4
|
-
# Instances of this class may be passed to
|
5
|
-
# +Virtus::Attribute.build+ as the +:coercer+
|
6
|
-
# option for custom types that do not otherwise
|
7
|
-
# satisfy the requirements for +Virtus::Attribute::coerce+
|
8
|
-
# and +Virtus::Attribute::value_coerced?+ to work
|
9
|
-
# as expected.
|
10
|
-
#
|
11
|
-
# Subclasses of +Virtus::Attribute+ or +Axiom::Types::Type+
|
12
|
-
# (or for which an axiom type can be inferred, i.e. the
|
13
|
-
# primitives, +Date+, +Time+, etc.) do not need any such
|
14
|
-
# coercer to be passed with them.
|
15
|
-
#
|
16
|
-
# Coercion
|
17
|
-
# --------
|
18
|
-
#
|
19
6
|
# This class will detect type classes that implement
|
20
7
|
# a class-level +parse+ method. The method should accept one
|
21
8
|
# +String+ argument and should return the value coerced to
|
@@ -30,14 +17,14 @@ module Grape
|
|
30
17
|
# Type Checking
|
31
18
|
# -------------
|
32
19
|
#
|
33
|
-
# Calls to +
|
20
|
+
# Calls to +coerced?+ will consult this class to check
|
34
21
|
# that the coerced value produced above is in fact of the
|
35
22
|
# expected type. By default this class performs a basic check
|
36
23
|
# against the type supplied, but this behaviour will be
|
37
24
|
# overridden if the class implements a class-level
|
38
25
|
# +coerced?+ or +parsed?+ method. This method
|
39
26
|
# will receive a single parameter that is the coerced value
|
40
|
-
# and should return +true+
|
27
|
+
# and should return +true+ if the value meets type expectations.
|
41
28
|
# Arbitrary assertions may be made here but the grape validation
|
42
29
|
# system should be preferred.
|
43
30
|
#
|
@@ -46,15 +33,6 @@ module Grape
|
|
46
33
|
# contract as +coerced?+, and must be supplied with a coercion
|
47
34
|
# +method+.
|
48
35
|
class CustomTypeCoercer
|
49
|
-
# Uses +Virtus::Attribute.build+ to build a new
|
50
|
-
# attribute that makes use of this class for
|
51
|
-
# coercion and type validation logic.
|
52
|
-
#
|
53
|
-
# @return [Virtus::Attribute]
|
54
|
-
def self.build(type, method = nil)
|
55
|
-
Virtus::Attribute.build(type, coercer: new(type, method))
|
56
|
-
end
|
57
|
-
|
58
36
|
# A new coercer for the given type specification
|
59
37
|
# and coercion method.
|
60
38
|
#
|
@@ -64,37 +42,25 @@ module Grape
|
|
64
42
|
# optional coercion method. See class docs.
|
65
43
|
def initialize(type, method = nil)
|
66
44
|
coercion_method = infer_coercion_method type, method
|
67
|
-
|
68
45
|
@method = enforce_symbolized_keys type, coercion_method
|
69
|
-
|
70
46
|
@type_check = infer_type_check(type)
|
71
47
|
end
|
72
48
|
|
73
|
-
#
|
74
|
-
# +Virtus::Attribute::coerce+ in order to coerce
|
75
|
-
# the given value.
|
49
|
+
# Coerces the given value.
|
76
50
|
#
|
77
51
|
# @param value [String] value to be coerced, in grape
|
78
52
|
# this should always be a string.
|
79
53
|
# @return [Object] the coerced result
|
80
|
-
def call(
|
81
|
-
|
54
|
+
def call(val)
|
55
|
+
return if val.nil?
|
56
|
+
|
57
|
+
coerced_val = @method.call(val)
|
58
|
+
return InvalidValue.new unless coerced?(coerced_val)
|
59
|
+
coerced_val
|
82
60
|
end
|
83
61
|
|
84
|
-
|
85
|
-
|
86
|
-
# assert that the value has been coerced successfully.
|
87
|
-
#
|
88
|
-
# @param _primitive [Axiom::Types::Type] primitive type
|
89
|
-
# for the coercion as detected by axiom-types' inference
|
90
|
-
# system. For custom types this is typically not much use
|
91
|
-
# (i.e. it is +Axiom::Types::Object+) unless special
|
92
|
-
# inference rules have been declared for the type.
|
93
|
-
# @param value [Object] a coerced result returned from {#call}
|
94
|
-
# @return [true,false] whether or not the coerced value
|
95
|
-
# satisfies type requirements.
|
96
|
-
def success?(_primitive, value)
|
97
|
-
@type_check.call value
|
62
|
+
def coerced?(val)
|
63
|
+
@type_check.call val
|
98
64
|
end
|
99
65
|
|
100
66
|
private
|
@@ -160,8 +126,8 @@ module Grape
|
|
160
126
|
# Collections have all values processed individually
|
161
127
|
if [Array, Set].include?(type)
|
162
128
|
lambda do |val|
|
163
|
-
method.call(val).tap do |
|
164
|
-
|
129
|
+
method.call(val).tap do |new_val|
|
130
|
+
new_val.map do |item|
|
165
131
|
item.is_a?(Hash) ? symbolize_keys(item) : item
|
166
132
|
end
|
167
133
|
end
|
@@ -1,12 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Grape
|
2
4
|
module Validations
|
3
5
|
module Types
|
4
|
-
# Instances of this class may be passed to
|
5
|
-
# +Virtus::Attribute.build+ as the +:coercer+
|
6
|
-
# option, to handle collections of types that
|
7
|
-
# provide their own parsing (and optionally,
|
8
|
-
# type-checking) functionality.
|
9
|
-
#
|
10
6
|
# See {CustomTypeCoercer} for details on types
|
11
7
|
# that will be supported by this by this coercer.
|
12
8
|
# This coercer works in the same way as +CustomTypeCoercer+
|
@@ -38,32 +34,21 @@ module Grape
|
|
38
34
|
@set = set
|
39
35
|
end
|
40
36
|
|
41
|
-
#
|
42
|
-
# +Virtus::Attribute::coerce+ in order to coerce
|
43
|
-
# the given value.
|
37
|
+
# Coerces the given value.
|
44
38
|
#
|
45
39
|
# @param value [Array<String>] an array of values to be coerced
|
46
40
|
# @return [Array,Set] the coerced result. May be an +Array+ or a
|
47
41
|
# +Set+ depending on the setting given to the constructor
|
48
42
|
def call(value)
|
49
|
-
coerced = value.map
|
43
|
+
coerced = value.map do |item|
|
44
|
+
coerced_item = super(item)
|
50
45
|
|
51
|
-
|
52
|
-
end
|
46
|
+
return coerced_item if coerced_item.is_a?(InvalidValue)
|
53
47
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
#
|
59
|
-
# @param primitive [Axiom::Types::Type] primitive type for
|
60
|
-
# the coercion as deteced by axiom-types' inference system.
|
61
|
-
# @param value [Enumerable] a coerced result returned from {#call}
|
62
|
-
# @return [true,false] whether or not all of the coerced values in
|
63
|
-
# the collection satisfy type requirements.
|
64
|
-
def success?(primitive, value)
|
65
|
-
value.is_a?(@set ? Set : Array) &&
|
66
|
-
value.all? { |item| super(primitive, item) }
|
48
|
+
coerced_item
|
49
|
+
end
|
50
|
+
|
51
|
+
@set ? Set.new(coerced) : coerced
|
67
52
|
end
|
68
53
|
end
|
69
54
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry-types'
|
4
|
+
|
5
|
+
module DryTypes
|
6
|
+
# Call +Dry.Types()+ to add all registered types to +DryTypes+ which is
|
7
|
+
# a container in this case. Check documentation for more information
|
8
|
+
# https://dry-rb.org/gems/dry-types/1.2/getting-started/
|
9
|
+
include Dry.Types()
|
10
|
+
end
|
11
|
+
|
12
|
+
module Grape
|
13
|
+
module Validations
|
14
|
+
module Types
|
15
|
+
# A base class for classes which must identify a coercer to be used.
|
16
|
+
# If the +strict+ argument is true, it won't coerce the given value
|
17
|
+
# but check its type. More information there
|
18
|
+
# https://dry-rb.org/gems/dry-types/1.2/built-in-types/
|
19
|
+
class DryTypeCoercer
|
20
|
+
def initialize(type, strict = false)
|
21
|
+
@type = type
|
22
|
+
@scope = strict ? DryTypes::Strict : DryTypes::Params
|
23
|
+
end
|
24
|
+
|
25
|
+
# Coerces the given value to a type which was specified during
|
26
|
+
# initialization as a type argument.
|
27
|
+
#
|
28
|
+
# @param val [Object]
|
29
|
+
def call(val)
|
30
|
+
@coercer[val]
|
31
|
+
rescue Dry::Types::CoercionError => _e
|
32
|
+
InvalidValue.new
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
attr_reader :scope, :type
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -1,21 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Grape
|
2
4
|
module Validations
|
3
5
|
module Types
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
6
|
+
# Implementation for parameters that are multipart file objects.
|
7
|
+
# Actual handling of these objects is provided by +Rack::Request+;
|
8
|
+
# this class is here only to assert that rack's handling has succeeded.
|
9
|
+
class File
|
10
|
+
def call(input)
|
11
|
+
return if input.nil?
|
12
|
+
return InvalidValue.new unless coerced?(input)
|
13
|
+
|
12
14
|
# Processing of multipart file objects
|
13
15
|
# is already taken care of by Rack::Request.
|
14
16
|
# Nothing to do here.
|
15
17
|
input
|
16
18
|
end
|
17
19
|
|
18
|
-
def
|
20
|
+
def coerced?(value)
|
19
21
|
# Rack::Request creates a Hash with filename,
|
20
22
|
# content type and an IO object. Do a bit of basic
|
21
23
|
# duck-typing.
|