grape 1.8.0 → 3.3.5
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 +391 -1
- data/CONTRIBUTING.md +3 -11
- data/README.md +648 -552
- data/UPGRADING.md +803 -7
- data/grape.gemspec +11 -14
- data/lib/grape/api/instance.rb +68 -158
- data/lib/grape/api.rb +71 -107
- data/lib/grape/content_types.rb +23 -11
- data/lib/grape/cookies.rb +28 -25
- data/lib/grape/declared_params_handler.rb +116 -0
- data/lib/grape/dry_types.rb +48 -6
- data/lib/grape/dsl/callbacks.rb +14 -58
- data/lib/grape/dsl/declared.rb +35 -0
- data/lib/grape/dsl/desc.rb +15 -63
- data/lib/grape/dsl/entity.rb +88 -0
- data/lib/grape/dsl/headers.rb +2 -2
- data/lib/grape/dsl/helpers.rb +83 -64
- data/lib/grape/dsl/inside_route.rb +56 -295
- data/lib/grape/dsl/logger.rb +4 -9
- data/lib/grape/dsl/middleware.rb +22 -40
- data/lib/grape/dsl/parameters.rb +50 -82
- data/lib/grape/dsl/request_response.rb +144 -146
- data/lib/grape/dsl/rescue_options.rb +24 -0
- data/lib/grape/dsl/routing.rb +261 -186
- data/lib/grape/dsl/settings.rb +29 -135
- data/lib/grape/dsl/validations.rb +39 -32
- data/lib/grape/dsl/version_options.rb +23 -0
- data/lib/grape/endpoint/options.rb +19 -0
- data/lib/grape/endpoint.rb +277 -250
- data/lib/grape/{util/env.rb → env.rb} +1 -8
- data/lib/grape/error_formatter/base.rb +56 -21
- data/lib/grape/error_formatter/json.rb +13 -15
- data/lib/grape/error_formatter/serializable_hash.rb +7 -0
- data/lib/grape/error_formatter/txt.rb +12 -18
- data/lib/grape/error_formatter/xml.rb +3 -13
- data/lib/grape/error_formatter.rb +5 -25
- data/lib/grape/exceptions/base.rb +22 -58
- data/lib/grape/exceptions/error_response.rb +45 -0
- data/lib/grape/exceptions/incompatible_option_values.rb +1 -1
- data/lib/grape/exceptions/internal_server_error.rb +16 -0
- data/lib/grape/exceptions/invalid_accept_header.rb +1 -1
- data/lib/grape/exceptions/invalid_formatter.rb +1 -1
- data/lib/grape/exceptions/invalid_message_body.rb +1 -1
- data/lib/grape/exceptions/invalid_version_header.rb +1 -1
- data/lib/grape/exceptions/invalid_versioner_option.rb +1 -1
- data/lib/grape/exceptions/method_not_allowed.rb +1 -1
- data/lib/grape/exceptions/missing_group_type.rb +0 -2
- data/lib/grape/exceptions/missing_mime_type.rb +1 -1
- data/lib/grape/exceptions/request_error.rb +11 -0
- data/lib/grape/exceptions/unknown_auth_strategy.rb +11 -0
- data/lib/grape/exceptions/unknown_parameter.rb +1 -1
- data/lib/grape/exceptions/unknown_params_builder.rb +11 -0
- data/lib/grape/exceptions/unknown_validator.rb +1 -1
- data/lib/grape/exceptions/unsupported_group_type.rb +0 -2
- data/lib/grape/exceptions/validation.rb +25 -9
- data/lib/grape/exceptions/validation_array_errors.rb +5 -0
- data/lib/grape/exceptions/validation_errors.rb +22 -26
- data/lib/grape/formatter/base.rb +16 -0
- data/lib/grape/formatter/json.rb +4 -6
- data/lib/grape/formatter/serializable_hash.rb +6 -10
- data/lib/grape/formatter/txt.rb +3 -5
- data/lib/grape/formatter/xml.rb +4 -6
- data/lib/grape/formatter.rb +7 -25
- data/lib/grape/json.rb +46 -0
- data/lib/grape/locale/en.yml +43 -42
- data/lib/grape/middleware/auth/base.rb +11 -33
- data/lib/grape/middleware/auth/dsl.rb +37 -31
- data/lib/grape/middleware/auth/strategies.rb +1 -2
- data/lib/grape/middleware/base.rb +56 -33
- data/lib/grape/middleware/deprecated_options_hash_access.rb +19 -0
- data/lib/grape/middleware/error.rb +183 -91
- data/lib/grape/middleware/formatter.rb +94 -105
- data/lib/grape/middleware/precomputed_content_types.rb +46 -0
- data/lib/grape/middleware/stack.rb +27 -38
- data/lib/grape/middleware/versioner/accept_version_header.rb +6 -33
- data/lib/grape/middleware/versioner/base.rb +72 -0
- data/lib/grape/middleware/versioner/header.rb +44 -129
- data/lib/grape/middleware/versioner/param.rb +4 -25
- data/lib/grape/middleware/versioner/path.rb +13 -32
- data/lib/grape/middleware/versioner.rb +7 -14
- data/lib/grape/mountable.rb +22 -0
- data/lib/grape/namespace.rb +18 -12
- data/lib/grape/params_builder/base.rb +20 -0
- data/lib/grape/params_builder/hash.rb +11 -0
- data/lib/grape/params_builder/hash_with_indifferent_access.rb +11 -0
- data/lib/grape/params_builder/hashie_mash.rb +11 -0
- data/lib/grape/params_builder.rb +15 -0
- data/lib/grape/parser/base.rb +16 -0
- data/lib/grape/parser/json.rb +6 -8
- data/lib/grape/parser/xml.rb +6 -8
- data/lib/grape/parser.rb +5 -23
- data/lib/grape/path.rb +38 -63
- data/lib/grape/railtie.rb +9 -0
- data/lib/grape/request.rb +168 -26
- data/lib/grape/router/base_route.rb +48 -0
- data/lib/grape/router/greedy_route.rb +26 -0
- data/lib/grape/router/mustermann_pattern.rb +44 -0
- data/lib/grape/router/pattern.rb +30 -34
- data/lib/grape/router/route.rb +28 -61
- data/lib/grape/router.rb +71 -86
- data/lib/grape/serve_stream/file_body.rb +1 -0
- data/lib/grape/serve_stream/sendfile_response.rb +3 -5
- data/lib/grape/serve_stream/stream_response.rb +1 -0
- data/lib/grape/testing.rb +33 -0
- data/lib/grape/util/api_description.rb +58 -0
- data/lib/grape/util/base_inheritable.rb +14 -14
- data/lib/grape/util/cache.rb +1 -3
- data/lib/grape/util/deep_freeze.rb +35 -0
- data/lib/grape/util/endpoint_configuration.rb +1 -1
- data/lib/grape/util/header.rb +13 -0
- data/lib/grape/util/inheritable_setting.rb +51 -27
- data/lib/grape/util/inheritable_values.rb +7 -5
- data/lib/grape/util/lazy/base.rb +16 -0
- data/lib/grape/util/lazy/block.rb +22 -0
- data/lib/grape/util/lazy/value.rb +31 -0
- data/lib/grape/util/lazy/value_array.rb +21 -0
- data/lib/grape/util/lazy/value_enumerable.rb +31 -0
- data/lib/grape/util/lazy/value_hash.rb +21 -0
- data/lib/grape/util/media_type.rb +67 -0
- data/lib/grape/util/path_normalizer.rb +34 -0
- data/lib/grape/util/registry.rb +27 -0
- data/lib/grape/util/reverse_stackable_values.rb +1 -6
- data/lib/grape/util/stackable_values.rb +12 -14
- data/lib/grape/util/translation.rb +42 -0
- data/lib/grape/validations/attributes_iterator.rb +43 -27
- data/lib/grape/validations/coerce_options.rb +21 -0
- data/lib/grape/validations/contract_scope.rb +28 -0
- data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
- data/lib/grape/validations/oneof_collector.rb +39 -0
- data/lib/grape/validations/param_scope_tracker.rb +62 -0
- data/lib/grape/validations/params_documentation.rb +52 -0
- data/lib/grape/validations/params_scope.rb +180 -303
- data/lib/grape/validations/shared_options.rb +19 -0
- data/lib/grape/validations/single_attribute_iterator.rb +2 -2
- data/lib/grape/validations/types/array_coercer.rb +4 -7
- data/lib/grape/validations/types/custom_type_coercer.rb +41 -85
- data/lib/grape/validations/types/custom_type_collection_coercer.rb +1 -1
- data/lib/grape/validations/types/dry_type_coercer.rb +15 -28
- data/lib/grape/validations/types/json.rb +0 -2
- data/lib/grape/validations/types/primitive_coercer.rb +11 -35
- data/lib/grape/validations/types/set_coercer.rb +1 -4
- data/lib/grape/validations/types/variant_collection_coercer.rb +11 -0
- data/lib/grape/validations/types.rb +29 -54
- data/lib/grape/validations/validations_spec.rb +164 -0
- data/lib/grape/validations/validators/all_or_none_of_validator.rb +6 -3
- data/lib/grape/validations/validators/allow_blank_validator.rb +10 -5
- data/lib/grape/validations/validators/at_least_one_of_validator.rb +5 -2
- data/lib/grape/validations/validators/base.rb +120 -36
- data/lib/grape/validations/validators/coerce_validator.rb +18 -36
- data/lib/grape/validations/validators/contract_scope_validator.rb +45 -0
- data/lib/grape/validations/validators/default_validator.rb +13 -16
- data/lib/grape/validations/validators/exactly_one_of_validator.rb +10 -3
- data/lib/grape/validations/validators/except_values_validator.rb +15 -5
- data/lib/grape/validations/validators/length_validator.rb +48 -0
- data/lib/grape/validations/validators/multiple_params_base.rb +12 -9
- data/lib/grape/validations/validators/{mutual_exclusion_validator.rb → mutually_exclusive_validator.rb} +4 -2
- data/lib/grape/validations/validators/oneof_validator.rb +49 -0
- data/lib/grape/validations/validators/presence_validator.rb +4 -2
- data/lib/grape/validations/validators/regexp_validator.rb +11 -3
- data/lib/grape/validations/validators/same_as_validator.rb +6 -15
- data/lib/grape/validations/validators/values_validator.rb +36 -65
- data/lib/grape/validations.rb +8 -21
- data/lib/grape/version.rb +1 -2
- data/lib/grape/xml.rb +17 -0
- data/lib/grape.rb +75 -288
- metadata +79 -291
- data/lib/grape/api/helpers.rb +0 -9
- data/lib/grape/dsl/api.rb +0 -19
- data/lib/grape/dsl/configuration.rb +0 -15
- data/lib/grape/eager_load.rb +0 -20
- data/lib/grape/exceptions/empty_message_body.rb +0 -11
- data/lib/grape/exceptions/missing_option.rb +0 -11
- data/lib/grape/exceptions/too_many_multipart_files.rb +0 -11
- data/lib/grape/exceptions/unknown_options.rb +0 -11
- data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +0 -27
- data/lib/grape/extensions/hash.rb +0 -22
- data/lib/grape/extensions/hashie/mash.rb +0 -26
- data/lib/grape/http/headers.rb +0 -61
- data/lib/grape/middleware/globals.rb +0 -16
- data/lib/grape/middleware/helpers.rb +0 -12
- data/lib/grape/middleware/versioner/parse_media_type_patch.rb +0 -24
- data/lib/grape/router/attribute_translator.rb +0 -63
- data/lib/grape/types/invalid_value.rb +0 -8
- data/lib/grape/util/json.rb +0 -12
- data/lib/grape/util/lazy_block.rb +0 -27
- data/lib/grape/util/lazy_object.rb +0 -43
- data/lib/grape/util/lazy_value.rb +0 -91
- data/lib/grape/util/registrable.rb +0 -15
- data/lib/grape/util/strict_hash_configuration.rb +0 -108
- data/lib/grape/util/xml.rb +0 -10
- data/lib/grape/validations/attributes_doc.rb +0 -58
- data/lib/grape/validations/types/build_coercer.rb +0 -94
- data/lib/grape/validations/validator_factory.rb +0 -15
- data/spec/grape/api/custom_validations_spec.rb +0 -213
- data/spec/grape/api/deeply_included_options_spec.rb +0 -56
- data/spec/grape/api/defines_boolean_in_params_spec.rb +0 -38
- data/spec/grape/api/documentation_spec.rb +0 -59
- data/spec/grape/api/inherited_helpers_spec.rb +0 -114
- data/spec/grape/api/instance_spec.rb +0 -103
- data/spec/grape/api/invalid_format_spec.rb +0 -45
- data/spec/grape/api/namespace_parameters_in_route_spec.rb +0 -38
- data/spec/grape/api/nested_helpers_spec.rb +0 -50
- data/spec/grape/api/optional_parameters_in_route_spec.rb +0 -43
- data/spec/grape/api/parameters_modification_spec.rb +0 -41
- data/spec/grape/api/patch_method_helpers_spec.rb +0 -79
- data/spec/grape/api/recognize_path_spec.rb +0 -21
- data/spec/grape/api/required_parameters_in_route_spec.rb +0 -37
- data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +0 -26
- data/spec/grape/api/routes_with_requirements_spec.rb +0 -59
- data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +0 -41
- data/spec/grape/api/shared_helpers_spec.rb +0 -36
- data/spec/grape/api_remount_spec.rb +0 -509
- data/spec/grape/api_spec.rb +0 -4356
- data/spec/grape/dsl/callbacks_spec.rb +0 -45
- data/spec/grape/dsl/desc_spec.rb +0 -98
- data/spec/grape/dsl/headers_spec.rb +0 -62
- data/spec/grape/dsl/helpers_spec.rb +0 -100
- data/spec/grape/dsl/inside_route_spec.rb +0 -531
- data/spec/grape/dsl/logger_spec.rb +0 -24
- data/spec/grape/dsl/middleware_spec.rb +0 -60
- data/spec/grape/dsl/parameters_spec.rb +0 -180
- data/spec/grape/dsl/request_response_spec.rb +0 -225
- data/spec/grape/dsl/routing_spec.rb +0 -275
- data/spec/grape/dsl/settings_spec.rb +0 -261
- data/spec/grape/dsl/validations_spec.rb +0 -55
- data/spec/grape/endpoint/declared_spec.rb +0 -846
- data/spec/grape/endpoint_spec.rb +0 -1085
- data/spec/grape/entity_spec.rb +0 -336
- data/spec/grape/exceptions/base_spec.rb +0 -81
- data/spec/grape/exceptions/body_parse_errors_spec.rb +0 -185
- data/spec/grape/exceptions/invalid_accept_header_spec.rb +0 -358
- data/spec/grape/exceptions/invalid_formatter_spec.rb +0 -15
- data/spec/grape/exceptions/invalid_response_spec.rb +0 -11
- data/spec/grape/exceptions/invalid_versioner_option_spec.rb +0 -15
- data/spec/grape/exceptions/missing_group_type_spec.rb +0 -17
- data/spec/grape/exceptions/missing_mime_type_spec.rb +0 -17
- data/spec/grape/exceptions/missing_option_spec.rb +0 -15
- data/spec/grape/exceptions/unknown_options_spec.rb +0 -15
- data/spec/grape/exceptions/unknown_validator_spec.rb +0 -15
- data/spec/grape/exceptions/unsupported_group_type_spec.rb +0 -19
- data/spec/grape/exceptions/validation_errors_spec.rb +0 -92
- data/spec/grape/exceptions/validation_spec.rb +0 -19
- data/spec/grape/extensions/param_builders/hash_spec.rb +0 -83
- data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +0 -105
- data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +0 -79
- data/spec/grape/grape_spec.rb +0 -9
- data/spec/grape/integration/global_namespace_function_spec.rb +0 -29
- data/spec/grape/integration/rack_sendfile_spec.rb +0 -48
- data/spec/grape/integration/rack_spec.rb +0 -51
- data/spec/grape/loading_spec.rb +0 -44
- data/spec/grape/middleware/auth/base_spec.rb +0 -31
- data/spec/grape/middleware/auth/dsl_spec.rb +0 -60
- data/spec/grape/middleware/auth/strategies_spec.rb +0 -120
- data/spec/grape/middleware/base_spec.rb +0 -221
- data/spec/grape/middleware/error_spec.rb +0 -85
- data/spec/grape/middleware/exception_spec.rb +0 -294
- data/spec/grape/middleware/formatter_spec.rb +0 -461
- data/spec/grape/middleware/globals_spec.rb +0 -30
- data/spec/grape/middleware/stack_spec.rb +0 -155
- data/spec/grape/middleware/versioner/accept_version_header_spec.rb +0 -122
- data/spec/grape/middleware/versioner/header_spec.rb +0 -345
- data/spec/grape/middleware/versioner/param_spec.rb +0 -171
- data/spec/grape/middleware/versioner/path_spec.rb +0 -62
- data/spec/grape/middleware/versioner_spec.rb +0 -21
- data/spec/grape/named_api_spec.rb +0 -19
- data/spec/grape/parser_spec.rb +0 -86
- data/spec/grape/path_spec.rb +0 -252
- data/spec/grape/presenters/presenter_spec.rb +0 -71
- data/spec/grape/request_spec.rb +0 -126
- data/spec/grape/util/inheritable_setting_spec.rb +0 -242
- data/spec/grape/util/inheritable_values_spec.rb +0 -79
- data/spec/grape/util/reverse_stackable_values_spec.rb +0 -134
- data/spec/grape/util/stackable_values_spec.rb +0 -128
- data/spec/grape/util/strict_hash_configuration_spec.rb +0 -38
- data/spec/grape/validations/attributes_doc_spec.rb +0 -153
- data/spec/grape/validations/instance_behaivour_spec.rb +0 -43
- data/spec/grape/validations/multiple_attributes_iterator_spec.rb +0 -38
- data/spec/grape/validations/params_scope_spec.rb +0 -1420
- data/spec/grape/validations/single_attribute_iterator_spec.rb +0 -56
- data/spec/grape/validations/types/array_coercer_spec.rb +0 -33
- data/spec/grape/validations/types/primitive_coercer_spec.rb +0 -150
- data/spec/grape/validations/types/set_coercer_spec.rb +0 -32
- data/spec/grape/validations/types_spec.rb +0 -111
- data/spec/grape/validations/validators/all_or_none_spec.rb +0 -162
- data/spec/grape/validations/validators/allow_blank_spec.rb +0 -575
- data/spec/grape/validations/validators/at_least_one_of_spec.rb +0 -205
- data/spec/grape/validations/validators/base_spec.rb +0 -38
- data/spec/grape/validations/validators/coerce_spec.rb +0 -1261
- data/spec/grape/validations/validators/default_spec.rb +0 -463
- data/spec/grape/validations/validators/exactly_one_of_spec.rb +0 -233
- data/spec/grape/validations/validators/except_values_spec.rb +0 -192
- data/spec/grape/validations/validators/mutual_exclusion_spec.rb +0 -214
- data/spec/grape/validations/validators/presence_spec.rb +0 -315
- data/spec/grape/validations/validators/regexp_spec.rb +0 -161
- data/spec/grape/validations/validators/same_as_spec.rb +0 -57
- data/spec/grape/validations/validators/values_spec.rb +0 -733
- data/spec/grape/validations/validators/zh-CN.yml +0 -10
- data/spec/grape/validations_spec.rb +0 -2030
- data/spec/integration/eager_load/eager_load_spec.rb +0 -15
- data/spec/integration/multi_json/json_spec.rb +0 -7
- data/spec/integration/multi_xml/xml_spec.rb +0 -7
- data/spec/shared/deprecated_class_examples.rb +0 -16
- data/spec/shared/versioning_examples.rb +0 -215
- data/spec/spec_helper.rb +0 -52
- data/spec/support/basic_auth_encode_helpers.rb +0 -11
- data/spec/support/chunks.rb +0 -14
- data/spec/support/content_type_helpers.rb +0 -15
- data/spec/support/endpoint_faker.rb +0 -25
- data/spec/support/file_streamer.rb +0 -13
- data/spec/support/integer_helpers.rb +0 -13
- data/spec/support/versioned_helpers.rb +0 -55
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Validations
|
|
5
|
+
# Immutable value object holding the two options every validator reads at
|
|
6
|
+
# construction time: +allow_blank+ and +fail_fast+. Internal to
|
|
7
|
+
# {Validators::Base}, which builds it from the +opts+ Hash so the public
|
|
8
|
+
# 5th-argument contract stays a plain Hash — not part of any wire contract.
|
|
9
|
+
#
|
|
10
|
+
# Defaults mirror the prior +opts.values_at+ behaviour: +allow_blank+ is
|
|
11
|
+
# +nil+ when the declaration didn't supply it (validators treat nil as
|
|
12
|
+
# "not set"), +fail_fast+ defaults to +false+.
|
|
13
|
+
SharedOptions = Data.define(:allow_blank, :fail_fast) do
|
|
14
|
+
def initialize(allow_blank: nil, fail_fast: false)
|
|
15
|
+
super
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -5,10 +5,10 @@ module Grape
|
|
|
5
5
|
class SingleAttributeIterator < AttributesIterator
|
|
6
6
|
private
|
|
7
7
|
|
|
8
|
-
def yield_attributes(val
|
|
8
|
+
def yield_attributes(val)
|
|
9
9
|
return if skip?(val)
|
|
10
10
|
|
|
11
|
-
attrs.each do |attr_name|
|
|
11
|
+
@attrs.each do |attr_name|
|
|
12
12
|
yield val, attr_name, empty?(val)
|
|
13
13
|
end
|
|
14
14
|
end
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative 'dry_type_coercer'
|
|
4
|
-
|
|
5
3
|
module Grape
|
|
6
4
|
module Validations
|
|
7
5
|
module Types
|
|
@@ -9,15 +7,14 @@ module Grape
|
|
|
9
7
|
# an array of arrays of integers.
|
|
10
8
|
#
|
|
11
9
|
# It could've been possible to use an +of+
|
|
12
|
-
# method (https://dry-rb.org/gems/dry-types/
|
|
10
|
+
# method (https://dry-rb.org/gems/dry-types/main/array-with-member/)
|
|
13
11
|
# provided by dry-types. Unfortunately, it doesn't work for Grape because of
|
|
14
12
|
# behavior of Virtus which was used earlier, a `Grape::Validations::Types::PrimitiveCoercer`
|
|
15
13
|
# maintains Virtus behavior in coercing.
|
|
16
14
|
class ArrayCoercer < DryTypeCoercer
|
|
17
|
-
def initialize(type, strict
|
|
15
|
+
def initialize(type, strict: false)
|
|
18
16
|
super
|
|
19
|
-
|
|
20
|
-
@coercer = scope::Array
|
|
17
|
+
@coercer = strict ? DryTypes::Strict::Array : DryTypes::Params::Array
|
|
21
18
|
@subtype = type.first
|
|
22
19
|
end
|
|
23
20
|
|
|
@@ -55,7 +52,7 @@ module Grape
|
|
|
55
52
|
end
|
|
56
53
|
|
|
57
54
|
def elem_coercer
|
|
58
|
-
@elem_coercer ||= DryTypeCoercer.coercer_instance_for(subtype, strict)
|
|
55
|
+
@elem_coercer ||= DryTypeCoercer.coercer_instance_for(subtype, strict:)
|
|
59
56
|
end
|
|
60
57
|
end
|
|
61
58
|
end
|
|
@@ -33,6 +33,10 @@ module Grape
|
|
|
33
33
|
# contract as +coerced?+, and must be supplied with a coercion
|
|
34
34
|
# +method+.
|
|
35
35
|
class CustomTypeCoercer
|
|
36
|
+
TYPE_CHECK_METHODS = %i[coerced? parsed?].freeze
|
|
37
|
+
COLLECTION_TYPES = [Array, Set].freeze
|
|
38
|
+
private_constant :TYPE_CHECK_METHODS, :COLLECTION_TYPES
|
|
39
|
+
|
|
36
40
|
# A new coercer for the given type specification
|
|
37
41
|
# and coercion method.
|
|
38
42
|
#
|
|
@@ -41,8 +45,7 @@ module Grape
|
|
|
41
45
|
# @param method [#parse,#call]
|
|
42
46
|
# optional coercion method. See class docs.
|
|
43
47
|
def initialize(type, method = nil)
|
|
44
|
-
|
|
45
|
-
@method = enforce_symbolized_keys type, coercion_method
|
|
48
|
+
@method = build_coercion_method(type, method)
|
|
46
49
|
@type_check = infer_type_check(type)
|
|
47
50
|
end
|
|
48
51
|
|
|
@@ -66,97 +69,50 @@ module Grape
|
|
|
66
69
|
|
|
67
70
|
private
|
|
68
71
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
def build_coercion_method(type, method)
|
|
73
|
+
coercion_method = infer_coercion_method(type, method)
|
|
74
|
+
return hash_symbolizer(coercion_method) if type == Hash
|
|
75
|
+
return collection_symbolizer(coercion_method) if COLLECTION_TYPES.include?(type)
|
|
76
|
+
|
|
77
|
+
coercion_method
|
|
78
|
+
end
|
|
79
|
+
|
|
75
80
|
def infer_coercion_method(type, method)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
81
|
+
return type.method(:parse) unless method
|
|
82
|
+
return method unless method.respond_to?(:parse)
|
|
83
|
+
|
|
84
|
+
method.method(:parse)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def hash_symbolizer(method)
|
|
88
|
+
->(val) { method.call(val).deep_symbolize_keys }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def collection_symbolizer(method)
|
|
92
|
+
->(val) { method.call(val).map! { |item| symbolize_if_hash(item) } }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def symbolize_if_hash(item)
|
|
96
|
+
item.is_a?(Hash) ? item.deep_symbolize_keys : item
|
|
87
97
|
end
|
|
88
98
|
|
|
89
|
-
# Determine how the type validity of a coerced
|
|
90
|
-
# value should be decided.
|
|
91
|
-
#
|
|
92
|
-
# @param type see #new
|
|
93
|
-
# @return [#call] a procedure which accepts a single parameter
|
|
94
|
-
# and returns +true+ if the passed object is of the correct type.
|
|
95
99
|
def infer_type_check(type)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
# Arbitrary proc passed for type validation.
|
|
103
|
-
# Note that this will fail unless a method is also
|
|
104
|
-
# passed, or if the type also implements a parse() method.
|
|
105
|
-
type
|
|
106
|
-
elsif type.is_a?(Enumerable)
|
|
107
|
-
lambda do |value|
|
|
108
|
-
value.is_a?(Enumerable) && value.all? do |val|
|
|
109
|
-
recursive_type_check(type.first, val)
|
|
110
|
-
end
|
|
111
|
-
end
|
|
112
|
-
else
|
|
113
|
-
# By default, do a simple type check
|
|
114
|
-
->(value) { value.is_a? type }
|
|
115
|
-
end
|
|
100
|
+
method_name = TYPE_CHECK_METHODS.detect { |m| type.respond_to?(m) }
|
|
101
|
+
return type.method(method_name) if method_name
|
|
102
|
+
return type if type.respond_to?(:call)
|
|
103
|
+
return enumerable_type_check(type) if type.is_a?(Enumerable)
|
|
104
|
+
|
|
105
|
+
->(value) { value.is_a? type }
|
|
116
106
|
end
|
|
117
107
|
|
|
118
|
-
def
|
|
119
|
-
|
|
120
|
-
value.all? { |val| recursive_type_check(type.first, val) }
|
|
121
|
-
else
|
|
122
|
-
!type.is_a?(Enumerable) && value.is_a?(type)
|
|
123
|
-
end
|
|
108
|
+
def enumerable_type_check(type)
|
|
109
|
+
->(value) { value.is_a?(Enumerable) && value.all? { |val| recursive_type_check(type.first, val) } }
|
|
124
110
|
end
|
|
125
111
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
# This helps common libs such as JSON to work easily.
|
|
131
|
-
#
|
|
132
|
-
# @param type see #new
|
|
133
|
-
# @param method see #infer_coercion_method
|
|
134
|
-
# @return [#call] +method+ wrapped in an additional
|
|
135
|
-
# key-conversion step, or just returns +method+
|
|
136
|
-
# itself if no conversion is deemed to be
|
|
137
|
-
# necessary.
|
|
138
|
-
def enforce_symbolized_keys(type, method)
|
|
139
|
-
# Collections have all values processed individually
|
|
140
|
-
if [Array, Set].include?(type)
|
|
141
|
-
lambda do |val|
|
|
142
|
-
method.call(val).tap do |new_val|
|
|
143
|
-
new_val.map do |item|
|
|
144
|
-
item.is_a?(Hash) ? item.deep_symbolize_keys : item
|
|
145
|
-
end
|
|
146
|
-
end
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
# Hash objects are processed directly
|
|
150
|
-
elsif type == Hash
|
|
151
|
-
lambda do |val|
|
|
152
|
-
method.call(val).deep_symbolize_keys
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
# Simple types are not processed.
|
|
156
|
-
# This includes Array<primitive> types.
|
|
157
|
-
else
|
|
158
|
-
method
|
|
159
|
-
end
|
|
112
|
+
def recursive_type_check(type, value)
|
|
113
|
+
return value.all? { |val| recursive_type_check(type.first, val) } if type.is_a?(Enumerable) && value.is_a?(Enumerable)
|
|
114
|
+
|
|
115
|
+
!type.is_a?(Enumerable) && value.is_a?(type)
|
|
160
116
|
end
|
|
161
117
|
end
|
|
162
118
|
end
|
|
@@ -29,7 +29,7 @@ module Grape
|
|
|
29
29
|
# @param set [Boolean]
|
|
30
30
|
# when true, a +Set+ will be returned by {#call} instead
|
|
31
31
|
# of an +Array+ and duplicate items will be discarded.
|
|
32
|
-
def initialize(type, set
|
|
32
|
+
def initialize(type, set: false)
|
|
33
33
|
super(type)
|
|
34
34
|
@set = set
|
|
35
35
|
end
|
|
@@ -1,21 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
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
3
|
module Grape
|
|
13
4
|
module Validations
|
|
14
5
|
module Types
|
|
15
6
|
# A base class for classes which must identify a coercer to be used.
|
|
16
7
|
# If the +strict+ argument is true, it won't coerce the given value
|
|
17
8
|
# but check its type. More information there
|
|
18
|
-
# https://dry-rb.org/gems/dry-types/
|
|
9
|
+
# https://dry-rb.org/gems/dry-types/main/built-in-types/
|
|
19
10
|
class DryTypeCoercer
|
|
20
11
|
class << self
|
|
21
12
|
# Returns a collection coercer which corresponds to a given type.
|
|
@@ -24,31 +15,27 @@ module Grape
|
|
|
24
15
|
# collection_coercer_for(Array)
|
|
25
16
|
# #=> Grape::Validations::Types::ArrayCoercer
|
|
26
17
|
def collection_coercer_for(type)
|
|
27
|
-
|
|
28
|
-
|
|
18
|
+
case type
|
|
19
|
+
when Array
|
|
20
|
+
ArrayCoercer
|
|
21
|
+
when Set
|
|
22
|
+
SetCoercer
|
|
23
|
+
else
|
|
24
|
+
raise ArgumentError, "Unknown type: #{type}"
|
|
29
25
|
end
|
|
30
26
|
end
|
|
31
27
|
|
|
32
28
|
# Returns an instance of a coercer for a given type
|
|
33
|
-
def coercer_instance_for(type, strict
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
# in case of a collection (Array[Integer]) the type is an instance of a collection,
|
|
37
|
-
# so we need to figure out the actual type
|
|
38
|
-
collection_coercer_for(type.class).new(type, strict)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
protected
|
|
42
|
-
|
|
43
|
-
def collection_coercers
|
|
44
|
-
@collection_coercers ||= {}
|
|
29
|
+
def coercer_instance_for(type, strict: false)
|
|
30
|
+
klass = type.instance_of?(Class) ? PrimitiveCoercer : collection_coercer_for(type)
|
|
31
|
+
klass.new(type, strict:)
|
|
45
32
|
end
|
|
46
33
|
end
|
|
47
34
|
|
|
48
|
-
def initialize(type, strict
|
|
35
|
+
def initialize(type, strict: false)
|
|
49
36
|
@type = type
|
|
50
37
|
@strict = strict
|
|
51
|
-
@
|
|
38
|
+
@cache_coercer = strict ? DryTypes::StrictCache : DryTypes::ParamsCache
|
|
52
39
|
end
|
|
53
40
|
|
|
54
41
|
# Coerces the given value to a type which was specified during
|
|
@@ -59,13 +46,13 @@ module Grape
|
|
|
59
46
|
return if val.nil?
|
|
60
47
|
|
|
61
48
|
@coercer[val]
|
|
62
|
-
rescue Dry::Types::CoercionError
|
|
49
|
+
rescue Dry::Types::CoercionError
|
|
63
50
|
InvalidValue.new
|
|
64
51
|
end
|
|
65
52
|
|
|
66
53
|
protected
|
|
67
54
|
|
|
68
|
-
attr_reader :
|
|
55
|
+
attr_reader :type, :strict, :cache_coercer
|
|
69
56
|
end
|
|
70
57
|
end
|
|
71
58
|
end
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative 'dry_type_coercer'
|
|
4
|
-
|
|
5
3
|
module Grape
|
|
6
4
|
module Validations
|
|
7
5
|
module Types
|
|
@@ -9,42 +7,15 @@ module Grape
|
|
|
9
7
|
# initialization. When +strict+ is true, it doesn't coerce a value but check
|
|
10
8
|
# that it has the proper type.
|
|
11
9
|
class PrimitiveCoercer < DryTypeCoercer
|
|
12
|
-
|
|
13
|
-
Grape::API::Boolean => DryTypes::Params::Bool,
|
|
14
|
-
BigDecimal => DryTypes::Params::Decimal,
|
|
15
|
-
Numeric => DryTypes::Params::Integer | DryTypes::Params::Float | DryTypes::Params::Decimal,
|
|
16
|
-
TrueClass => DryTypes::Params::Bool.constrained(eql: true),
|
|
17
|
-
FalseClass => DryTypes::Params::Bool.constrained(eql: false),
|
|
18
|
-
|
|
19
|
-
# unfortunately, a +Params+ scope doesn't contain String
|
|
20
|
-
String => DryTypes::Coercible::String
|
|
21
|
-
}.freeze
|
|
22
|
-
|
|
23
|
-
STRICT_MAPPING = {
|
|
24
|
-
Grape::API::Boolean => DryTypes::Strict::Bool,
|
|
25
|
-
BigDecimal => DryTypes::Strict::Decimal,
|
|
26
|
-
Numeric => DryTypes::Strict::Integer | DryTypes::Strict::Float | DryTypes::Strict::Decimal,
|
|
27
|
-
TrueClass => DryTypes::Strict::Bool.constrained(eql: true),
|
|
28
|
-
FalseClass => DryTypes::Strict::Bool.constrained(eql: false)
|
|
29
|
-
}.freeze
|
|
30
|
-
|
|
31
|
-
def initialize(type, strict = false)
|
|
10
|
+
def initialize(type, strict: false)
|
|
32
11
|
super
|
|
33
12
|
|
|
34
|
-
@
|
|
35
|
-
|
|
36
|
-
@coercer = (strict ? STRICT_MAPPING : MAPPING).fetch(type) do
|
|
37
|
-
scope.const_get(type.name, false)
|
|
38
|
-
rescue NameError
|
|
39
|
-
raise ArgumentError, "type #{type} should support coercion via `[]`" unless type.respond_to?(:[])
|
|
40
|
-
|
|
41
|
-
type
|
|
42
|
-
end
|
|
13
|
+
@coercer = cache_coercer[type]
|
|
43
14
|
end
|
|
44
15
|
|
|
45
16
|
def call(val)
|
|
46
17
|
return InvalidValue.new if reject?(val)
|
|
47
|
-
return
|
|
18
|
+
return if val.nil? || treat_as_nil?(val)
|
|
48
19
|
|
|
49
20
|
super
|
|
50
21
|
end
|
|
@@ -58,9 +29,14 @@ module Grape
|
|
|
58
29
|
# but Virtus wouldn't accept it. So, this method only exists to not introduce
|
|
59
30
|
# breaking changes.
|
|
60
31
|
def reject?(val)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
32
|
+
case val
|
|
33
|
+
when Array, Hash
|
|
34
|
+
type == String
|
|
35
|
+
when String
|
|
36
|
+
type == Hash
|
|
37
|
+
else
|
|
38
|
+
false
|
|
39
|
+
end
|
|
64
40
|
end
|
|
65
41
|
|
|
66
42
|
# Dry-Types treats an empty string as invalid. However, Grape considers an empty string as
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'set'
|
|
4
|
-
require_relative 'array_coercer'
|
|
5
|
-
|
|
6
3
|
module Grape
|
|
7
4
|
module Validations
|
|
8
5
|
module Types
|
|
9
6
|
# Takes the given array and converts it to a set. Every element of the set
|
|
10
7
|
# is also coerced.
|
|
11
8
|
class SetCoercer < ArrayCoercer
|
|
12
|
-
def initialize(type, strict
|
|
9
|
+
def initialize(type, strict: false)
|
|
13
10
|
super
|
|
14
11
|
|
|
15
12
|
@coercer = nil
|
|
@@ -6,6 +6,9 @@ module Grape
|
|
|
6
6
|
# This class wraps {MultipleTypeCoercer}, for use with collections
|
|
7
7
|
# that allow members of more than one type.
|
|
8
8
|
class VariantCollectionCoercer
|
|
9
|
+
# @return [Array<Class>,Set<Class>] the member types as declared in the DSL
|
|
10
|
+
attr_reader :types
|
|
11
|
+
|
|
9
12
|
# Construct a new coercer that will attempt to coerce
|
|
10
13
|
# a list of values such that all members are of one of
|
|
11
14
|
# the given types. The container may also optionally be
|
|
@@ -26,6 +29,14 @@ module Grape
|
|
|
26
29
|
@member_coercer = MultipleTypeCoercer.new types, method
|
|
27
30
|
end
|
|
28
31
|
|
|
32
|
+
# Returns the Grape DSL notation for this coercer, e.g. "Array[Integer, String]".
|
|
33
|
+
# Distinct from the plain-array string "[Integer, String]" produced by the
|
|
34
|
+
# +types:+ keyword, which lets documentation tools tell the two apart.
|
|
35
|
+
def to_s
|
|
36
|
+
container = @types.is_a?(Set) ? 'Set' : 'Array'
|
|
37
|
+
"#{container}[#{@types.join(', ')}]"
|
|
38
|
+
end
|
|
39
|
+
|
|
29
40
|
# Coerce the given value.
|
|
30
41
|
#
|
|
31
42
|
# @param value [Array<String>] collection of values to be coerced
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'grape/validations/types/json'
|
|
4
|
-
require 'grape/validations/types/file'
|
|
5
|
-
|
|
6
3
|
module Grape
|
|
7
4
|
module Validations
|
|
8
5
|
# Module for code related to grape's system for
|
|
@@ -78,7 +75,7 @@ module Grape
|
|
|
78
75
|
# @return [Boolean] +true+ if the given value will be treated as
|
|
79
76
|
# a list of types.
|
|
80
77
|
def multiple?(type)
|
|
81
|
-
|
|
78
|
+
array_or_set?(type) && type.size > 1
|
|
82
79
|
end
|
|
83
80
|
|
|
84
81
|
# Does Grape provide special coercion and validation
|
|
@@ -101,17 +98,18 @@ module Grape
|
|
|
101
98
|
GROUPS.include? type
|
|
102
99
|
end
|
|
103
100
|
|
|
101
|
+
def array_or_set?(type)
|
|
102
|
+
type.is_a?(Array) || type.is_a?(Set)
|
|
103
|
+
end
|
|
104
|
+
|
|
104
105
|
# A valid custom type must implement a class-level `parse` method, taking
|
|
105
106
|
# one String argument and returning the parsed value in its correct type.
|
|
106
107
|
#
|
|
107
108
|
# @param type [Class] type to check
|
|
108
109
|
# @return [Boolean] whether or not the type can be used as a custom type
|
|
109
110
|
def custom?(type)
|
|
110
|
-
!primitive?(type) &&
|
|
111
|
-
|
|
112
|
-
!multiple?(type) &&
|
|
113
|
-
type.respond_to?(:parse) &&
|
|
114
|
-
type.method(:parse).arity == 1
|
|
111
|
+
!(primitive?(type) || structure?(type) || multiple?(type)) &&
|
|
112
|
+
type.respond_to?(:parse) && type.method(:parse).arity == 1
|
|
115
113
|
end
|
|
116
114
|
|
|
117
115
|
# Is the declared type an +Array+ or +Set+ of a {#custom?} type?
|
|
@@ -120,9 +118,7 @@ module Grape
|
|
|
120
118
|
# @return [Boolean] true if +type+ is a collection of a type that implements
|
|
121
119
|
# its own +#parse+ method.
|
|
122
120
|
def collection_of_custom?(type)
|
|
123
|
-
(type.
|
|
124
|
-
type.length == 1 &&
|
|
125
|
-
(custom?(type.first) || special?(type.first))
|
|
121
|
+
array_or_set?(type) && type.length == 1 && (custom?(type.first) || special?(type.first))
|
|
126
122
|
end
|
|
127
123
|
|
|
128
124
|
def map_special(type)
|
|
@@ -158,59 +154,38 @@ module Grape
|
|
|
158
154
|
# @return [Object] object to be used
|
|
159
155
|
# for coercion and type validation
|
|
160
156
|
def build_coercer(type, method: nil, strict: false)
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
end
|
|
164
|
-
end
|
|
157
|
+
# no cache since unique
|
|
158
|
+
return create_coercer_instance(type, method, strict) if method.respond_to?(:call)
|
|
165
159
|
|
|
166
|
-
|
|
167
|
-
# Maps a custom type provided by Grape, it doesn't map types wrapped by collections!!!
|
|
168
|
-
type = Types.map_special(type)
|
|
169
|
-
|
|
170
|
-
# Use a special coercer for multiply-typed parameters.
|
|
171
|
-
if Types.multiple?(type)
|
|
172
|
-
MultipleTypeCoercer.new(type, method)
|
|
173
|
-
|
|
174
|
-
# Use a special coercer for custom types and coercion methods.
|
|
175
|
-
elsif method || Types.custom?(type)
|
|
176
|
-
CustomTypeCoercer.new(type, method)
|
|
177
|
-
|
|
178
|
-
# Special coercer for collections of types that implement a parse method.
|
|
179
|
-
# CustomTypeCoercer (above) already handles such types when an explicit coercion
|
|
180
|
-
# method is supplied.
|
|
181
|
-
elsif Types.collection_of_custom?(type)
|
|
182
|
-
Types::CustomTypeCollectionCoercer.new(
|
|
183
|
-
Types.map_special(type.first), type.is_a?(Set)
|
|
184
|
-
)
|
|
185
|
-
else
|
|
186
|
-
DryTypeCoercer.coercer_instance_for(type, strict)
|
|
187
|
-
end
|
|
160
|
+
CoercerCache[[type, method, strict]]
|
|
188
161
|
end
|
|
189
162
|
|
|
190
|
-
def
|
|
191
|
-
|
|
163
|
+
def create_coercer_instance(type, method, strict)
|
|
164
|
+
# map_special doesn't recurse into collections — applied only to the top-level type here.
|
|
165
|
+
type = map_special(type)
|
|
192
166
|
|
|
193
|
-
|
|
167
|
+
# Multiply-typed parameters, e.g. types: [Integer, String].
|
|
168
|
+
return MultipleTypeCoercer.new(type, method) if multiple?(type)
|
|
194
169
|
|
|
195
|
-
|
|
170
|
+
# User-supplied coercion method, or a custom type with its own #parse.
|
|
171
|
+
return CustomTypeCoercer.new(type, method) if method || custom?(type)
|
|
196
172
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
173
|
+
# Array/Set of a custom type — CustomTypeCoercer already handles single
|
|
174
|
+
# custom types when an explicit coercion method is supplied.
|
|
175
|
+
return CustomTypeCollectionCoercer.new(map_special(type.first), set: type.is_a?(Set)) if collection_of_custom?(type)
|
|
200
176
|
|
|
201
|
-
|
|
177
|
+
# Fallback: let dry-types handle primitives, structures, and known specials.
|
|
178
|
+
DryTypeCoercer.coercer_instance_for(type, strict:)
|
|
202
179
|
end
|
|
203
180
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
181
|
+
class CoercerCache < Grape::Util::Cache
|
|
182
|
+
def initialize
|
|
183
|
+
super
|
|
184
|
+
@cache = Hash.new do |h, (type, method, strict)|
|
|
185
|
+
h[[type, method, strict]] = Grape::Validations::Types.create_coercer_instance(type, method, strict)
|
|
186
|
+
end
|
|
209
187
|
end
|
|
210
188
|
end
|
|
211
|
-
|
|
212
|
-
instance_variable_set(:@__cache, {})
|
|
213
|
-
instance_variable_set(:@__cache_write_lock, Mutex.new)
|
|
214
189
|
end
|
|
215
190
|
end
|
|
216
191
|
end
|