grape 1.8.0 → 4.0.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 +517 -1
- data/CONTRIBUTING.md +19 -11
- data/README.md +841 -545
- data/UPGRADING.md +1553 -7
- data/grape.gemspec +11 -14
- data/lib/grape/api/instance.rb +77 -160
- data/lib/grape/api.rb +75 -107
- data/lib/grape/content_types.rb +67 -9
- 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 +8 -58
- data/lib/grape/dsl/declared.rb +35 -0
- data/lib/grape/dsl/desc.rb +25 -63
- data/lib/grape/dsl/entity.rb +106 -0
- data/lib/grape/dsl/headers.rb +2 -2
- data/lib/grape/dsl/helpers.rb +83 -64
- data/lib/grape/dsl/inside_route.rb +69 -296
- data/lib/grape/dsl/logger.rb +4 -9
- data/lib/grape/dsl/middleware.rb +22 -40
- data/lib/grape/dsl/parameters.rb +88 -94
- data/lib/grape/dsl/request_response.rb +143 -147
- data/lib/grape/dsl/rescue_options.rb +25 -0
- data/lib/grape/dsl/routing.rb +333 -191
- 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 +25 -0
- data/lib/grape/endpoint.rb +319 -269
- data/lib/grape/{util/env.rb → env.rb} +8 -8
- data/lib/grape/error_formatter/base.rb +57 -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 +9 -25
- data/lib/grape/exceptions/base.rb +22 -58
- data/lib/grape/exceptions/error_response.rb +48 -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_error_formatter.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 +28 -11
- 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 +5 -6
- data/lib/grape/formatter/serializable_hash.rb +7 -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 +44 -42
- data/lib/grape/middleware/auth/base.rb +11 -33
- data/lib/grape/middleware/auth/dsl.rb +12 -34
- data/lib/grape/middleware/auth/strategies.rb +1 -2
- data/lib/grape/middleware/base.rb +56 -33
- data/lib/grape/middleware/error.rb +290 -91
- data/lib/grape/middleware/formatter.rb +164 -106
- data/lib/grape/middleware/precomputed_content_types.rb +51 -0
- data/lib/grape/middleware/stack.rb +38 -40
- data/lib/grape/middleware/versioner/accept_version_header.rb +6 -33
- data/lib/grape/middleware/versioner/base.rb +66 -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 +48 -25
- data/lib/grape/middleware/versioner.rb +7 -14
- data/lib/grape/mountable.rb +22 -0
- data/lib/grape/namespace.rb +21 -14
- 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 +8 -94
- data/lib/grape/precompiled_json.rb +50 -0
- data/lib/grape/railtie.rb +9 -0
- data/lib/grape/request.rb +200 -26
- data/lib/grape/router/base_route.rb +85 -0
- data/lib/grape/router/greedy_route.rb +30 -0
- data/lib/grape/router/mustermann_pattern.rb +44 -0
- data/lib/grape/router/pattern/path.rb +78 -0
- data/lib/grape/router/pattern.rb +77 -35
- data/lib/grape/router/route.rb +79 -60
- data/lib/grape/router.rb +128 -104
- data/lib/grape/serve_stream/file_body.rb +7 -0
- data/lib/grape/serve_stream/sendfile_response.rb +3 -5
- data/lib/grape/serve_stream/stream_response.rb +7 -0
- data/lib/grape/testing.rb +33 -0
- data/lib/grape/util/api_description.rb +67 -0
- data/lib/grape/util/cache.rb +22 -5
- data/lib/grape/util/deep_freeze.rb +34 -0
- data/lib/grape/util/endpoint_configuration.rb +1 -1
- data/lib/grape/util/freeze_on_new.rb +20 -0
- data/lib/grape/util/header.rb +13 -0
- data/lib/grape/util/inheritable_setting.rb +777 -39
- 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 +74 -0
- data/lib/grape/util/path_normalizer.rb +37 -0
- data/lib/grape/util/registry.rb +37 -0
- data/lib/grape/util/shadowed_rescue_handlers.rb +49 -0
- data/lib/grape/util/stackable_values.rb +40 -16
- data/lib/grape/util/translation.rb +42 -0
- data/lib/grape/validations/attributes_iterator.rb +61 -28
- data/lib/grape/validations/coerce_options.rb +21 -0
- data/lib/grape/validations/contract_scope.rb +29 -0
- data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
- data/lib/grape/validations/oneof_collector.rb +35 -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 +223 -306
- data/lib/grape/validations/shared_options.rb +19 -0
- data/lib/grape/validations/single_attribute_iterator.rb +6 -4
- data/lib/grape/validations/types/array_coercer.rb +7 -12
- data/lib/grape/validations/types/custom_type_coercer.rb +47 -85
- data/lib/grape/validations/types/custom_type_collection_coercer.rb +1 -1
- data/lib/grape/validations/types/dry_type_coercer.rb +17 -28
- data/lib/grape/validations/types/json.rb +1 -5
- data/lib/grape/validations/types/multiple_type_coercer.rb +5 -3
- data/lib/grape/validations/types/primitive_coercer.rb +14 -35
- data/lib/grape/validations/types/set_coercer.rb +1 -4
- data/lib/grape/validations/types/variant_collection_coercer.rb +16 -3
- 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 +118 -37
- data/lib/grape/validations/validators/coerce_validator.rb +26 -38
- data/lib/grape/validations/validators/contract_scope_validator.rb +46 -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 +50 -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 +51 -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 +7 -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 +96 -288
- metadata +83 -294
- 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/base_inheritable.rb +0 -43
- data/lib/grape/util/inheritable_values.rb +0 -31
- 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/reverse_stackable_values.rb +0 -20
- 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,12 +5,14 @@ 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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
# The emptiness of the scope's params is the same answer for every
|
|
12
|
+
# attribute in it, and answering it costs a +respond_to?+, so it is
|
|
13
|
+
# asked once for the whole list rather than once per attribute.
|
|
14
|
+
empty_val = empty?(val)
|
|
15
|
+
@attrs.each { |attr_name| yield val, attr_name, empty_val }
|
|
14
16
|
end
|
|
15
17
|
|
|
16
18
|
# Primitives like Integers and Booleans don't respond to +empty?+.
|
|
@@ -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,16 +7,17 @@ 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
|
-
|
|
21
|
-
|
|
17
|
+
@coercer = strict ? DryTypes::Strict::Array : DryTypes::Params::Array
|
|
18
|
+
# Built eagerly: instances are shared across requests (and cached in
|
|
19
|
+
# Types::CoercerCache), so no lazy state may be created at call time.
|
|
20
|
+
@elem_coercer = DryTypeCoercer.coercer_instance_for(type.first, strict:)
|
|
22
21
|
end
|
|
23
22
|
|
|
24
23
|
def call(_val)
|
|
@@ -30,7 +29,7 @@ module Grape
|
|
|
30
29
|
|
|
31
30
|
protected
|
|
32
31
|
|
|
33
|
-
attr_reader :
|
|
32
|
+
attr_reader :elem_coercer
|
|
34
33
|
|
|
35
34
|
def coerce_elements(collection)
|
|
36
35
|
return if collection.nil?
|
|
@@ -53,10 +52,6 @@ module Grape
|
|
|
53
52
|
def reject?(val)
|
|
54
53
|
val.nil?
|
|
55
54
|
end
|
|
56
|
-
|
|
57
|
-
def elem_coercer
|
|
58
|
-
@elem_coercer ||= DryTypeCoercer.coercer_instance_for(subtype, strict)
|
|
59
|
-
end
|
|
60
55
|
end
|
|
61
56
|
end
|
|
62
57
|
end
|
|
@@ -33,6 +33,12 @@ module Grape
|
|
|
33
33
|
# contract as +coerced?+, and must be supplied with a coercion
|
|
34
34
|
# +method+.
|
|
35
35
|
class CustomTypeCoercer
|
|
36
|
+
extend Grape::Util::FreezeOnNew
|
|
37
|
+
|
|
38
|
+
TYPE_CHECK_METHODS = %i[coerced? parsed?].freeze
|
|
39
|
+
COLLECTION_TYPES = [Array, Set].freeze
|
|
40
|
+
private_constant :TYPE_CHECK_METHODS, :COLLECTION_TYPES
|
|
41
|
+
|
|
36
42
|
# A new coercer for the given type specification
|
|
37
43
|
# and coercion method.
|
|
38
44
|
#
|
|
@@ -41,8 +47,7 @@ module Grape
|
|
|
41
47
|
# @param method [#parse,#call]
|
|
42
48
|
# optional coercion method. See class docs.
|
|
43
49
|
def initialize(type, method = nil)
|
|
44
|
-
|
|
45
|
-
@method = enforce_symbolized_keys type, coercion_method
|
|
50
|
+
@method = build_coercion_method(type, method)
|
|
46
51
|
@type_check = infer_type_check(type)
|
|
47
52
|
end
|
|
48
53
|
|
|
@@ -66,97 +71,54 @@ module Grape
|
|
|
66
71
|
|
|
67
72
|
private
|
|
68
73
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
def build_coercion_method(type, method)
|
|
75
|
+
coercion_method = infer_coercion_method(type, method)
|
|
76
|
+
return hash_symbolizer(coercion_method) if type == Hash
|
|
77
|
+
return collection_symbolizer(coercion_method) if COLLECTION_TYPES.include?(type)
|
|
78
|
+
|
|
79
|
+
coercion_method
|
|
80
|
+
end
|
|
81
|
+
|
|
75
82
|
def infer_coercion_method(type, method)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
return type.method(:parse) unless method
|
|
84
|
+
return method unless method.respond_to?(:parse)
|
|
85
|
+
|
|
86
|
+
method.method(:parse)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def hash_symbolizer(method)
|
|
90
|
+
->(val) { method.call(val).deep_symbolize_keys }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# +dup+ before +map!+: the collection is whatever the user's coercion
|
|
94
|
+
# method returned — possibly frozen, possibly the input itself — so we
|
|
95
|
+
# must not mutate it in place. +dup+ also preserves the class, keeping a
|
|
96
|
+
# +Set+ a +Set+ (unlike +map+, which would return an +Array+).
|
|
97
|
+
def collection_symbolizer(method)
|
|
98
|
+
->(val) { method.call(val).dup.map! { |item| symbolize_if_hash(item) } }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def symbolize_if_hash(item)
|
|
102
|
+
item.is_a?(Hash) ? item.deep_symbolize_keys : item
|
|
87
103
|
end
|
|
88
104
|
|
|
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
105
|
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
|
|
106
|
+
method_name = TYPE_CHECK_METHODS.detect { |m| type.respond_to?(m) }
|
|
107
|
+
return type.method(method_name) if method_name
|
|
108
|
+
return type if type.respond_to?(:call)
|
|
109
|
+
return enumerable_type_check(type) if type.is_a?(Enumerable)
|
|
110
|
+
|
|
111
|
+
->(value) { value.is_a? type }
|
|
116
112
|
end
|
|
117
113
|
|
|
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
|
|
114
|
+
def enumerable_type_check(type)
|
|
115
|
+
->(value) { value.is_a?(Enumerable) && value.all? { |val| recursive_type_check(type.first, val) } }
|
|
124
116
|
end
|
|
125
117
|
|
|
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
|
|
118
|
+
def recursive_type_check(type, value)
|
|
119
|
+
return value.all? { |val| recursive_type_check(type.first, val) } if type.is_a?(Enumerable) && value.is_a?(Enumerable)
|
|
120
|
+
|
|
121
|
+
!type.is_a?(Enumerable) && value.is_a?(type)
|
|
160
122
|
end
|
|
161
123
|
end
|
|
162
124
|
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,22 +1,15 @@
|
|
|
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
|
|
11
|
+
extend Grape::Util::FreezeOnNew
|
|
12
|
+
|
|
20
13
|
class << self
|
|
21
14
|
# Returns a collection coercer which corresponds to a given type.
|
|
22
15
|
# Example:
|
|
@@ -24,31 +17,27 @@ module Grape
|
|
|
24
17
|
# collection_coercer_for(Array)
|
|
25
18
|
# #=> Grape::Validations::Types::ArrayCoercer
|
|
26
19
|
def collection_coercer_for(type)
|
|
27
|
-
|
|
28
|
-
|
|
20
|
+
case type
|
|
21
|
+
when Array
|
|
22
|
+
ArrayCoercer
|
|
23
|
+
when Set
|
|
24
|
+
SetCoercer
|
|
25
|
+
else
|
|
26
|
+
raise ArgumentError, "unknown type: `#{type}`"
|
|
29
27
|
end
|
|
30
28
|
end
|
|
31
29
|
|
|
32
30
|
# 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 ||= {}
|
|
31
|
+
def coercer_instance_for(type, strict: false)
|
|
32
|
+
klass = type.instance_of?(Class) ? PrimitiveCoercer : collection_coercer_for(type)
|
|
33
|
+
klass.new(type, strict:)
|
|
45
34
|
end
|
|
46
35
|
end
|
|
47
36
|
|
|
48
|
-
def initialize(type, strict
|
|
37
|
+
def initialize(type, strict: false)
|
|
49
38
|
@type = type
|
|
50
39
|
@strict = strict
|
|
51
|
-
@
|
|
40
|
+
@cache_coercer = strict ? DryTypes::StrictCache : DryTypes::ParamsCache
|
|
52
41
|
end
|
|
53
42
|
|
|
54
43
|
# Coerces the given value to a type which was specified during
|
|
@@ -59,13 +48,13 @@ module Grape
|
|
|
59
48
|
return if val.nil?
|
|
60
49
|
|
|
61
50
|
@coercer[val]
|
|
62
|
-
rescue Dry::Types::CoercionError
|
|
51
|
+
rescue Dry::Types::CoercionError
|
|
63
52
|
InvalidValue.new
|
|
64
53
|
end
|
|
65
54
|
|
|
66
55
|
protected
|
|
67
56
|
|
|
68
|
-
attr_reader :
|
|
57
|
+
attr_reader :type, :strict, :cache_coercer
|
|
69
58
|
end
|
|
70
59
|
end
|
|
71
60
|
end
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'json'
|
|
4
|
-
|
|
5
3
|
module Grape
|
|
6
4
|
module Validations
|
|
7
5
|
module Types
|
|
@@ -19,9 +17,7 @@ module Grape
|
|
|
19
17
|
# @return [Hash,Array<Hash>,nil]
|
|
20
18
|
def parse(input)
|
|
21
19
|
return input if parsed?(input)
|
|
22
|
-
|
|
23
|
-
# Allow nulls and blank strings
|
|
24
|
-
return if input.nil? || input.match?(/^\s*$/)
|
|
20
|
+
return if input.blank?
|
|
25
21
|
|
|
26
22
|
JSON.parse(input, symbolize_names: true)
|
|
27
23
|
end
|
|
@@ -13,6 +13,8 @@ module Grape
|
|
|
13
13
|
# an allowed type it should be declared last, since it will always
|
|
14
14
|
# successfully "coerce" the value.
|
|
15
15
|
class MultipleTypeCoercer
|
|
16
|
+
extend Grape::Util::FreezeOnNew
|
|
17
|
+
|
|
16
18
|
# Construct a new coercer that will attempt to coerce
|
|
17
19
|
# values to the given list of types in the given order.
|
|
18
20
|
#
|
|
@@ -28,7 +30,7 @@ module Grape
|
|
|
28
30
|
else
|
|
29
31
|
Types.build_coercer type, strict: !@method.nil?
|
|
30
32
|
end
|
|
31
|
-
end
|
|
33
|
+
end.freeze
|
|
32
34
|
end
|
|
33
35
|
|
|
34
36
|
# Coerces the given value.
|
|
@@ -39,12 +41,12 @@ module Grape
|
|
|
39
41
|
# of {InvalidValue} if the value could not be coerced.
|
|
40
42
|
def call(val)
|
|
41
43
|
# once the value is coerced by the custom method, its type should be checked
|
|
42
|
-
|
|
44
|
+
candidate = @method ? @method.call(val) : val
|
|
43
45
|
|
|
44
46
|
coerced_val = InvalidValue.new
|
|
45
47
|
|
|
46
48
|
@type_coercers.each do |coercer|
|
|
47
|
-
coerced_val = coercer.call(
|
|
49
|
+
coerced_val = coercer.call(candidate)
|
|
48
50
|
|
|
49
51
|
return coerced_val unless coerced_val.is_a?(InvalidValue)
|
|
50
52
|
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,23 @@ 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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
10
|
+
# The input classes Virtus refused for a given declared type, keyed by
|
|
11
|
+
# that type. Resolved once in #initialize rather than re-derived from
|
|
12
|
+
# +type+ on every value: the answer only ever depends on the
|
|
13
|
+
# declaration, and every coerced attribute of every request asks.
|
|
14
|
+
REJECTED_INPUTS = { String => [Array, Hash].freeze, Hash => [String].freeze }.freeze
|
|
30
15
|
|
|
31
|
-
def initialize(type, strict
|
|
16
|
+
def initialize(type, strict: false)
|
|
32
17
|
super
|
|
33
18
|
|
|
34
|
-
@
|
|
35
|
-
|
|
36
|
-
@
|
|
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
|
|
19
|
+
@coercer = cache_coercer[type]
|
|
20
|
+
@rejected_inputs = REJECTED_INPUTS[type]
|
|
21
|
+
@treat_empty_as_nil = type != String
|
|
43
22
|
end
|
|
44
23
|
|
|
45
24
|
def call(val)
|
|
46
25
|
return InvalidValue.new if reject?(val)
|
|
47
|
-
return
|
|
26
|
+
return if val.nil? || treat_as_nil?(val)
|
|
48
27
|
|
|
49
28
|
super
|
|
50
29
|
end
|
|
@@ -58,16 +37,16 @@ module Grape
|
|
|
58
37
|
# but Virtus wouldn't accept it. So, this method only exists to not introduce
|
|
59
38
|
# breaking changes.
|
|
60
39
|
def reject?(val)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
40
|
+
return false unless @rejected_inputs
|
|
41
|
+
|
|
42
|
+
@rejected_inputs.any? { |klass| val.is_a?(klass) }
|
|
64
43
|
end
|
|
65
44
|
|
|
66
45
|
# Dry-Types treats an empty string as invalid. However, Grape considers an empty string as
|
|
67
46
|
# absence of a value and coerces it into nil. See a discussion there
|
|
68
47
|
# https://github.com/ruby-grape/grape/pull/2045
|
|
69
48
|
def treat_as_nil?(val)
|
|
70
|
-
val == ''
|
|
49
|
+
@treat_empty_as_nil && val == ''
|
|
71
50
|
end
|
|
72
51
|
end
|
|
73
52
|
end
|
|
@@ -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,11 @@ 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
|
+
extend Grape::Util::FreezeOnNew
|
|
10
|
+
|
|
11
|
+
# @return [Array<Class>,Set<Class>] the member types as declared in the DSL
|
|
12
|
+
attr_reader :types
|
|
13
|
+
|
|
9
14
|
# Construct a new coercer that will attempt to coerce
|
|
10
15
|
# a list of values such that all members are of one of
|
|
11
16
|
# the given types. The container may also optionally be
|
|
@@ -26,6 +31,14 @@ module Grape
|
|
|
26
31
|
@member_coercer = MultipleTypeCoercer.new types, method
|
|
27
32
|
end
|
|
28
33
|
|
|
34
|
+
# Returns the Grape DSL notation for this coercer, e.g. "Array[Integer, String]".
|
|
35
|
+
# Distinct from the plain-array string "[Integer, String]" produced by the
|
|
36
|
+
# +types:+ keyword, which lets documentation tools tell the two apart.
|
|
37
|
+
def to_s
|
|
38
|
+
container = @types.is_a?(Set) ? 'Set' : 'Array'
|
|
39
|
+
"#{container}[#{@types.join(', ')}]"
|
|
40
|
+
end
|
|
41
|
+
|
|
29
42
|
# Coerce the given value.
|
|
30
43
|
#
|
|
31
44
|
# @param value [Array<String>] collection of values to be coerced
|
|
@@ -35,15 +48,15 @@ module Grape
|
|
|
35
48
|
def call(value)
|
|
36
49
|
return unless value.is_a? Array
|
|
37
50
|
|
|
38
|
-
|
|
51
|
+
coerced =
|
|
39
52
|
if @method
|
|
40
53
|
@method.call(value)
|
|
41
54
|
else
|
|
42
55
|
value.map { |v| @member_coercer.call(v) }
|
|
43
56
|
end
|
|
44
|
-
return Set.new
|
|
57
|
+
return Set.new coerced if @types.is_a? Set
|
|
45
58
|
|
|
46
|
-
|
|
59
|
+
coerced
|
|
47
60
|
end
|
|
48
61
|
end
|
|
49
62
|
end
|