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
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative 'attributes_doc'
|
|
4
|
-
|
|
5
3
|
module Grape
|
|
6
4
|
module Validations
|
|
7
5
|
class ParamsScope
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
attr_reader :parent, :type, :nearest_array_ancestor, :array_depth, :full_path
|
|
7
|
+
|
|
8
|
+
def qualifying_params
|
|
9
|
+
ParamScopeTracker.current&.qualifying_params(self)
|
|
10
|
+
end
|
|
10
11
|
|
|
11
12
|
include Grape::DSL::Parameters
|
|
13
|
+
include Grape::Validations::ParamsDocumentation
|
|
12
14
|
|
|
13
15
|
# There are a number of documentation options on entities that don't have
|
|
14
16
|
# corresponding validators. Since there is nowhere that enumerates them all,
|
|
15
17
|
# we maintain a list of them here and skip looking up validators for them.
|
|
16
18
|
RESERVED_DOCUMENTATION_KEYWORDS = %i[as required param_type is_array format example].freeze
|
|
17
19
|
|
|
20
|
+
SPECIAL_JSON = [JSON, Array[JSON]].freeze
|
|
21
|
+
|
|
18
22
|
class Attr
|
|
19
|
-
|
|
23
|
+
attr_reader :key, :scope
|
|
20
24
|
|
|
21
25
|
# Open up a new ParamsScope::Attr
|
|
22
26
|
# @param key [Hash, Symbol] key of attr
|
|
@@ -34,9 +38,10 @@ module Grape
|
|
|
34
38
|
end
|
|
35
39
|
|
|
36
40
|
def self.attr_key(declared_param_attr)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
case declared_param_attr
|
|
42
|
+
when self
|
|
43
|
+
attr_key(declared_param_attr.key)
|
|
44
|
+
when Hash
|
|
40
45
|
declared_param_attr.transform_values { |value| attrs_keys(value) }
|
|
41
46
|
else
|
|
42
47
|
declared_param_attr
|
|
@@ -46,39 +51,47 @@ module Grape
|
|
|
46
51
|
|
|
47
52
|
# Open up a new ParamsScope, allowing parameter definitions per
|
|
48
53
|
# Grape::DSL::Params.
|
|
49
|
-
# @param
|
|
50
|
-
# @
|
|
51
|
-
# this to be relevant,
|
|
52
|
-
# @
|
|
54
|
+
# @param api [API] the API endpoint to modify
|
|
55
|
+
# @param element [Symbol] the element that contains this scope; for
|
|
56
|
+
# this to be relevant, parent must be set
|
|
57
|
+
# @param element_renamed [Symbol, nil] whenever this scope should
|
|
53
58
|
# be renamed and to what, given +nil+ no renaming is done
|
|
54
|
-
# @
|
|
55
|
-
# @
|
|
56
|
-
# @option opts :optional [Boolean] whether or not this scope needs to have
|
|
59
|
+
# @param parent [ParamsScope] the scope containing this scope
|
|
60
|
+
# @param optional [Boolean] whether or not this scope needs to have
|
|
57
61
|
# any parameters set or not
|
|
58
|
-
# @
|
|
59
|
-
# @
|
|
60
|
-
# @
|
|
62
|
+
# @param type [Class] a type meant to govern this scope (deprecated)
|
|
63
|
+
# @param type [Hash] group options for this scope
|
|
64
|
+
# @param dependent_on [Symbol] if present, this scope should only
|
|
61
65
|
# validate if this param is present in the parent scope
|
|
62
66
|
# @yield the instance context, open for parameter definitions
|
|
63
|
-
def initialize(
|
|
64
|
-
@element =
|
|
65
|
-
@element_renamed =
|
|
66
|
-
@parent =
|
|
67
|
-
@api =
|
|
68
|
-
@optional =
|
|
69
|
-
@type =
|
|
70
|
-
@group =
|
|
71
|
-
@dependent_on =
|
|
67
|
+
def initialize(api:, element: nil, element_renamed: nil, parent: nil, optional: false, type: nil, group: nil, dependent_on: nil, &block)
|
|
68
|
+
@element = element
|
|
69
|
+
@element_renamed = element_renamed
|
|
70
|
+
@parent = parent
|
|
71
|
+
@api = api
|
|
72
|
+
@optional = optional
|
|
73
|
+
@type = type
|
|
74
|
+
@group = group
|
|
75
|
+
@dependent_on = dependent_on
|
|
76
|
+
# Must be an ivar: push_declared_params is dispatched on self during
|
|
77
|
+
# instance_eval, so local variables from initialize are unreachable.
|
|
78
|
+
# configure_declared_params consumes it and clears @declared_params to nil.
|
|
72
79
|
@declared_params = []
|
|
73
|
-
@
|
|
80
|
+
@full_path = build_full_path
|
|
81
|
+
# Read by the validators instantiated from the block below, so it has to
|
|
82
|
+
# be settled before the instance_eval.
|
|
83
|
+
@array_depth = find_array_depth
|
|
74
84
|
|
|
75
85
|
instance_eval(&block) if block
|
|
76
86
|
|
|
77
87
|
configure_declared_params
|
|
88
|
+
@nearest_array_ancestor = find_nearest_array_ancestor
|
|
89
|
+
freeze
|
|
78
90
|
end
|
|
79
91
|
|
|
80
92
|
def configuration
|
|
81
|
-
|
|
93
|
+
config = @api.configuration
|
|
94
|
+
config.is_a?(Grape::Util::Lazy::Base) ? config.evaluate : config
|
|
82
95
|
end
|
|
83
96
|
|
|
84
97
|
# @return [Boolean] whether or not this entire scope needs to be
|
|
@@ -88,24 +101,26 @@ module Grape
|
|
|
88
101
|
|
|
89
102
|
return false if @optional && (scoped_params.blank? || all_element_blank?(scoped_params))
|
|
90
103
|
return false unless meets_dependency?(scoped_params, parameters)
|
|
91
|
-
return true if parent.nil?
|
|
104
|
+
return true if @parent.nil?
|
|
92
105
|
|
|
93
|
-
parent.should_validate?(parameters)
|
|
106
|
+
@parent.should_validate?(parameters)
|
|
94
107
|
end
|
|
95
108
|
|
|
96
109
|
def meets_dependency?(params, request_params)
|
|
97
110
|
return true unless @dependent_on
|
|
98
|
-
|
|
99
111
|
return false if @parent.present? && !@parent.meets_dependency?(@parent.params(request_params), request_params)
|
|
100
112
|
|
|
101
|
-
|
|
113
|
+
if params.is_a?(Array)
|
|
114
|
+
filtered = params.flatten.filter { |param| meets_dependency?(param, request_params) }
|
|
115
|
+
ParamScopeTracker.current&.store_qualifying_params(self, filtered)
|
|
116
|
+
return filtered.present?
|
|
117
|
+
end
|
|
102
118
|
|
|
103
119
|
meets_hash_dependency?(params)
|
|
104
120
|
end
|
|
105
121
|
|
|
106
122
|
def attr_meets_dependency?(params)
|
|
107
123
|
return true unless @dependent_on
|
|
108
|
-
|
|
109
124
|
return false if @parent.present? && !@parent.attr_meets_dependency?(params)
|
|
110
125
|
|
|
111
126
|
meets_hash_dependency?(params)
|
|
@@ -115,28 +130,27 @@ module Grape
|
|
|
115
130
|
# params might be anything what looks like a hash, so it must implement a `key?` method
|
|
116
131
|
return false unless params.respond_to?(:key?)
|
|
117
132
|
|
|
118
|
-
@dependent_on.
|
|
133
|
+
@dependent_on.all? do |dependency|
|
|
119
134
|
if dependency.is_a?(Hash)
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
return false
|
|
135
|
+
key, callable = dependency.first
|
|
136
|
+
callable.call(params[key])
|
|
137
|
+
else
|
|
138
|
+
params[dependency].present?
|
|
125
139
|
end
|
|
126
140
|
end
|
|
127
|
-
|
|
128
|
-
true
|
|
129
141
|
end
|
|
130
142
|
|
|
131
143
|
# @return [String] the proper attribute name, with nesting considered.
|
|
132
144
|
def full_name(name, index: nil)
|
|
145
|
+
tracker = ParamScopeTracker.current
|
|
133
146
|
if nested?
|
|
134
147
|
# Find our containing element's name, and append ours.
|
|
135
|
-
|
|
148
|
+
resolved_index = index || tracker&.index_for(self)
|
|
149
|
+
"#{@parent.full_name(@element)}#{brackets(resolved_index)}#{brackets(name)}"
|
|
136
150
|
elsif lateral?
|
|
137
151
|
# Find the name of the element as if it was at the same nesting level
|
|
138
152
|
# as our parent. We need to forward our index upward to achieve this.
|
|
139
|
-
@parent.full_name(name, index:
|
|
153
|
+
@parent.full_name(name, index: tracker&.index_for(self))
|
|
140
154
|
else
|
|
141
155
|
# We must be the root scope, so no prefix needed.
|
|
142
156
|
name.to_s
|
|
@@ -158,6 +172,17 @@ module Grape
|
|
|
158
172
|
@parent && @element
|
|
159
173
|
end
|
|
160
174
|
|
|
175
|
+
# Whether this scope's params resolve to one entry per element, which is
|
|
176
|
+
# what makes both an element index and a nesting level meaningful.
|
|
177
|
+
#
|
|
178
|
+
# +type: Array[JSON]+ counts as much as +type: Array+ does. It is easy to
|
|
179
|
+
# miss because it evaluates to the Array *instance* +[JSON]+ rather than
|
|
180
|
+
# the Array class, so an +== Array+ test quietly excluded it.
|
|
181
|
+
# @return [Boolean]
|
|
182
|
+
def iterates_elements?
|
|
183
|
+
@type == Array || @type == SPECIAL_JSON.last
|
|
184
|
+
end
|
|
185
|
+
|
|
161
186
|
# A lateral scope is subordinate to its parent, but its keys are at the
|
|
162
187
|
# same level as its parent and thus is not contained within an element.
|
|
163
188
|
# @return [Boolean] whether or not this scope is lateral
|
|
@@ -175,27 +200,27 @@ module Grape
|
|
|
175
200
|
|
|
176
201
|
# Adds a parameter declaration to our list of validations.
|
|
177
202
|
# @param attrs [Array] (see Grape::DSL::Parameters#requires)
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
203
|
+
# +declared_params_scope+ is the scope an attribute is recorded against.
|
|
204
|
+
# It defaults to the receiver and is forwarded unchanged when a lateral
|
|
205
|
+
# scope hands the push to its parent, so an attribute declared inside a
|
|
206
|
+
# +given+ block stays attributed to the lateral scope that declared it
|
|
207
|
+
# rather than to the parent that stores it.
|
|
208
|
+
def push_declared_params(attrs, as: nil, declared_params_scope: self)
|
|
209
|
+
return @parent.push_declared_params(attrs, as:, declared_params_scope:) if lateral?
|
|
185
210
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
# Get the full path of the parameter scope in the hierarchy.
|
|
191
|
-
#
|
|
192
|
-
# @return [Array<Symbol>] the nesting/path of the current parameter scope
|
|
193
|
-
def full_path
|
|
194
|
-
nested? ? @parent.full_path + [@element] : []
|
|
211
|
+
push_renamed_param(full_path + [attrs.first], as) if as
|
|
212
|
+
@declared_params.concat(attrs.map { |attr| ::Grape::Validations::ParamsScope::Attr.new(attr, declared_params_scope) })
|
|
195
213
|
end
|
|
196
214
|
|
|
197
215
|
private
|
|
198
216
|
|
|
217
|
+
def build_full_path
|
|
218
|
+
return @parent.full_path + [@element] if nested?
|
|
219
|
+
return @parent.full_path if lateral?
|
|
220
|
+
|
|
221
|
+
[]
|
|
222
|
+
end
|
|
223
|
+
|
|
199
224
|
# Add a new parameter which should be renamed when using the +#declared+
|
|
200
225
|
# method.
|
|
201
226
|
#
|
|
@@ -204,331 +229,223 @@ module Grape
|
|
|
204
229
|
# @param new_name [String, Symbol] the new name of the parameter (the
|
|
205
230
|
# renamed name, with the +as: ...+ semantic)
|
|
206
231
|
def push_renamed_param(path, new_name)
|
|
207
|
-
|
|
208
|
-
base[Array(path).map(&:to_s)] = new_name.to_s
|
|
209
|
-
@api.route_setting(:renamed_params, base)
|
|
232
|
+
@api.inheritable_setting.add_route_renamed_param(Array(path).map(&:to_s), new_name.to_s)
|
|
210
233
|
end
|
|
211
234
|
|
|
212
|
-
def require_required_and_optional_fields(context,
|
|
235
|
+
def require_required_and_optional_fields(context, using:, except: nil)
|
|
236
|
+
except_fields = Array.wrap(except)
|
|
237
|
+
using_fields = using.keys.delete_if { |f| except_fields.include?(f) }
|
|
238
|
+
|
|
213
239
|
if context == :all
|
|
214
|
-
optional_fields =
|
|
215
|
-
required_fields =
|
|
240
|
+
optional_fields = except_fields
|
|
241
|
+
required_fields = using_fields
|
|
216
242
|
else # context == :none
|
|
217
|
-
required_fields =
|
|
218
|
-
optional_fields =
|
|
243
|
+
required_fields = except_fields
|
|
244
|
+
optional_fields = using_fields
|
|
219
245
|
end
|
|
220
246
|
required_fields.each do |field|
|
|
221
|
-
field_opts =
|
|
222
|
-
raise ArgumentError, "required field not exist:
|
|
247
|
+
field_opts = using[field]
|
|
248
|
+
raise ArgumentError, "required field not exist: `#{field}`" unless field_opts
|
|
223
249
|
|
|
224
|
-
requires(field, field_opts)
|
|
250
|
+
requires(field, **field_opts)
|
|
225
251
|
end
|
|
226
252
|
optional_fields.each do |field|
|
|
227
|
-
field_opts =
|
|
228
|
-
optional(field, field_opts) if field_opts
|
|
253
|
+
field_opts = using[field]
|
|
254
|
+
optional(field, **field_opts) if field_opts
|
|
229
255
|
end
|
|
230
256
|
end
|
|
231
257
|
|
|
232
|
-
def require_optional_fields(context,
|
|
233
|
-
optional_fields =
|
|
234
|
-
|
|
258
|
+
def require_optional_fields(context, using:, except: nil)
|
|
259
|
+
optional_fields = using.keys
|
|
260
|
+
unless context == :all
|
|
261
|
+
except_fields = Array.wrap(except)
|
|
262
|
+
optional_fields.delete_if { |f| except_fields.include?(f) }
|
|
263
|
+
end
|
|
235
264
|
optional_fields.each do |field|
|
|
236
|
-
field_opts =
|
|
237
|
-
optional(field, field_opts) if field_opts
|
|
265
|
+
field_opts = using[field]
|
|
266
|
+
optional(field, **field_opts) if field_opts
|
|
238
267
|
end
|
|
239
268
|
end
|
|
240
269
|
|
|
241
|
-
def validate_attributes(attrs, opts, &block)
|
|
242
|
-
validations = opts.clone
|
|
243
|
-
validations[:type] ||= Array if block
|
|
244
|
-
validates(attrs, validations)
|
|
245
|
-
end
|
|
246
|
-
|
|
247
270
|
# Returns a new parameter scope, subordinate to the current one and nested
|
|
248
|
-
# under the
|
|
249
|
-
# @param
|
|
250
|
-
#
|
|
251
|
-
# @param
|
|
271
|
+
# under the given element.
|
|
272
|
+
# @param element [Symbol] the parameter name under which this scope is nested
|
|
273
|
+
# @param type [Class] the type governing this scope
|
|
274
|
+
# @param as [Symbol, nil] optional renamed name for the element
|
|
275
|
+
# @param optional [Boolean] whether the parameter this scope is nested under
|
|
252
276
|
# is optional or not (and hence, whether this block's params will be).
|
|
253
277
|
# @yield parameter scope
|
|
254
|
-
def new_scope(
|
|
255
|
-
#
|
|
256
|
-
|
|
257
|
-
|
|
278
|
+
def new_scope(element, type:, as:, optional: false, &)
|
|
279
|
+
# A group needs a type: it says whether the nested params sit under one
|
|
280
|
+
# object or repeat in a list, and without it the `type || Array` below
|
|
281
|
+
# would quietly pick one. Checked here for `requires` and `optional`
|
|
282
|
+
# alike — `new_scope` is only reached from their block branch.
|
|
283
|
+
if element
|
|
258
284
|
raise Grape::Exceptions::MissingGroupType if type.nil?
|
|
259
285
|
raise Grape::Exceptions::UnsupportedGroupType unless Grape::Validations::Types.group?(type)
|
|
260
286
|
end
|
|
261
287
|
|
|
262
288
|
self.class.new(
|
|
263
289
|
api: @api,
|
|
264
|
-
element
|
|
265
|
-
element_renamed:
|
|
290
|
+
element:,
|
|
291
|
+
element_renamed: as,
|
|
266
292
|
parent: self,
|
|
267
|
-
optional
|
|
293
|
+
optional:,
|
|
268
294
|
type: type || Array,
|
|
269
|
-
|
|
295
|
+
group: @group,
|
|
296
|
+
&
|
|
270
297
|
)
|
|
271
298
|
end
|
|
272
299
|
|
|
273
300
|
# Returns a new parameter scope, not nested under any current-level param
|
|
274
301
|
# but instead at the same level as the current scope.
|
|
275
|
-
# @param
|
|
276
|
-
#
|
|
277
|
-
# scope should only validate if this parameter from the above scope is
|
|
278
|
-
# present
|
|
302
|
+
# @param dependent_on [Symbol] if given, specifies that this scope should
|
|
303
|
+
# only validate if this parameter from the above scope is present
|
|
279
304
|
# @yield parameter scope
|
|
280
|
-
def new_lateral_scope(
|
|
305
|
+
def new_lateral_scope(dependent_on:, &)
|
|
281
306
|
self.class.new(
|
|
282
307
|
api: @api,
|
|
283
|
-
element: nil,
|
|
284
308
|
parent: self,
|
|
285
|
-
|
|
309
|
+
optional: @optional,
|
|
286
310
|
type: type == Array ? Array : Hash,
|
|
287
|
-
dependent_on
|
|
288
|
-
&
|
|
311
|
+
dependent_on:,
|
|
312
|
+
&
|
|
289
313
|
)
|
|
290
314
|
end
|
|
291
315
|
|
|
292
|
-
# Returns a new parameter scope, subordinate to the current one
|
|
293
|
-
#
|
|
294
|
-
# @param
|
|
295
|
-
# `optional` invocation that opened this scope.
|
|
316
|
+
# Returns a new parameter scope, subordinate to the current one, sharing
|
|
317
|
+
# the given group options with all parameters defined within.
|
|
318
|
+
# @param group [Hash] common options to merge into each parameter in the scope
|
|
296
319
|
# @yield parameter scope
|
|
297
|
-
def new_group_scope(
|
|
298
|
-
self.class.new(
|
|
299
|
-
api: @api,
|
|
300
|
-
parent: self,
|
|
301
|
-
group: attrs.first,
|
|
302
|
-
&block
|
|
303
|
-
)
|
|
320
|
+
def new_group_scope(group, &)
|
|
321
|
+
self.class.new(api: @api, parent: self, group:, &)
|
|
304
322
|
end
|
|
305
323
|
|
|
306
|
-
# Pushes declared params to parent or settings
|
|
324
|
+
# Pushes declared params to parent or settings, then clears @declared_params.
|
|
325
|
+
# Clearing here (rather than in initialize) keeps the lifecycle ownership in
|
|
326
|
+
# one place: this method both consumes and invalidates the ivar so that
|
|
327
|
+
# push_declared_params cannot be called on the frozen scope later.
|
|
307
328
|
def configure_declared_params
|
|
308
329
|
push_renamed_param(full_path, @element_renamed) if @element_renamed
|
|
330
|
+
return @parent.push_declared_params [{ @element => @declared_params }] if nested?
|
|
309
331
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
else
|
|
313
|
-
@api.namespace_stackable(:declared_params, @declared_params)
|
|
314
|
-
end
|
|
315
|
-
|
|
316
|
-
# params were stored in settings, it can be cleaned from the params scope
|
|
332
|
+
@api.inheritable_setting.add_declared_params(@declared_params)
|
|
333
|
+
ensure
|
|
317
334
|
@declared_params = nil
|
|
318
335
|
end
|
|
319
336
|
|
|
320
|
-
def
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
doc.type = coerce_type
|
|
337
|
+
def find_nearest_array_ancestor
|
|
338
|
+
scope = @parent
|
|
339
|
+
scope = scope.parent while scope && !scope.iterates_elements?
|
|
340
|
+
scope
|
|
341
|
+
end
|
|
327
342
|
|
|
328
|
-
|
|
343
|
+
# Every element-iterating scope on the chain adds one level of nesting to
|
|
344
|
+
# what {#params} returns, because +map_params+ maps over the array it
|
|
345
|
+
# resolved from the parent. Counting them tells {AttributesIterator} how
|
|
346
|
+
# deep the declaration says the params for this scope may legitimately be.
|
|
347
|
+
def find_array_depth
|
|
348
|
+
(iterates_elements? ? 1 : 0) + (@parent&.array_depth || 0)
|
|
349
|
+
end
|
|
329
350
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
351
|
+
# +required+ is the DSL's own signal — +requires+ passes true, +optional+
|
|
352
|
+
# false. It used to travel as a +:presence+ key that +requires+ wrote into
|
|
353
|
+
# the caller's option Hash, which is why a user-supplied +presence:+ was
|
|
354
|
+
# silently overwritten there and silently honoured by +optional+, where
|
|
355
|
+
# nothing overwrote it. The key is built here from the flag instead, after
|
|
356
|
+
# the caller has merged in any enclosing +with+ attributes, so a
|
|
357
|
+
# group-level +message:+ reaches the presence validator.
|
|
358
|
+
#
|
|
359
|
+
# A +presence:+ supplied by an API still decides the outcome exactly as it
|
|
360
|
+
# used to — deprecated rather than dropped, so nothing changes under an
|
|
361
|
+
# API that relies on it until the key is ignored outright.
|
|
362
|
+
def validates(attrs, validations, required: false)
|
|
363
|
+
if validations.key?(:presence)
|
|
364
|
+
Grape.deprecator.warn('Passing a `presence` option is deprecated and it will be ignored in a future release. Declare the parameter with `requires` to make it required, `optional` to make it optional.')
|
|
336
365
|
end
|
|
337
366
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
# NB. values and excepts should be nil, Proc, Array, or Range.
|
|
343
|
-
# Specifically, values should NOT be a Hash
|
|
344
|
-
|
|
345
|
-
# use values or excepts to guess coerce type when stated type is Array
|
|
346
|
-
coerce_type = guess_coerce_type(coerce_type, values, except_values, excepts)
|
|
367
|
+
declared = required ? validations.merge(presence: { value: true, message: validations[:message] }) : validations
|
|
368
|
+
declared = declared.merge(oneof: collected_oneof(declared)) if declared.key?(:oneof)
|
|
369
|
+
spec = ValidationsSpec.from(declared)
|
|
347
370
|
|
|
348
|
-
|
|
349
|
-
check_incompatible_option_values(default, values, except_values, excepts)
|
|
371
|
+
document_params(attrs, spec)
|
|
350
372
|
|
|
351
|
-
#
|
|
352
|
-
|
|
373
|
+
# Presence runs first — `required` is forwarded to every subsequent
|
|
374
|
+
# validator (some short-circuit on it).
|
|
375
|
+
validate_presence(spec, attrs)
|
|
353
376
|
|
|
354
|
-
|
|
377
|
+
# Coerce runs second — later validators see the typed value.
|
|
378
|
+
validate_coerce(spec, attrs)
|
|
355
379
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
# Validate for presence before any other validators
|
|
359
|
-
validates_presence(validations, attrs, doc, opts)
|
|
360
|
-
|
|
361
|
-
# Before we run the rest of the validators, let's handle
|
|
362
|
-
# whatever coercion so that we are working with correctly
|
|
363
|
-
# type casted values
|
|
364
|
-
coerce_type validations, attrs, doc, opts
|
|
365
|
-
|
|
366
|
-
validations.each do |type, options|
|
|
367
|
-
# Don't try to look up validators for documentation params that don't have one.
|
|
368
|
-
next if RESERVED_DOCUMENTATION_KEYWORDS.include?(type)
|
|
369
|
-
|
|
370
|
-
validate(type, options, attrs, doc, opts)
|
|
380
|
+
spec.validator_entries.each do |type, options|
|
|
381
|
+
validate(type, options, attrs, spec.required?, spec.shared_opts)
|
|
371
382
|
end
|
|
372
383
|
end
|
|
373
384
|
|
|
374
|
-
#
|
|
375
|
-
#
|
|
376
|
-
#
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
# options.
|
|
382
|
-
#
|
|
383
|
-
# @param validations [Hash] list of validations supplied to the
|
|
384
|
-
# parameter declaration
|
|
385
|
-
# @return [class-like] type to which the parameter will be coerced
|
|
386
|
-
# @raise [ArgumentError] if the given type options are invalid
|
|
387
|
-
def infer_coercion(validations)
|
|
388
|
-
raise ArgumentError, ':type may not be supplied with :types' if validations.key?(:type) && validations.key?(:types)
|
|
389
|
-
|
|
390
|
-
validations[:coerce] = (options_key?(:type, :value, validations) ? validations[:type][:value] : validations[:type]) if validations.key?(:type)
|
|
391
|
-
validations[:coerce_message] = (options_key?(:type, :message, validations) ? validations[:type][:message] : nil) if validations.key?(:type)
|
|
392
|
-
validations[:coerce] = (options_key?(:types, :value, validations) ? validations[:types][:value] : validations[:types]) if validations.key?(:types)
|
|
393
|
-
validations[:coerce_message] = (options_key?(:types, :message, validations) ? validations[:types][:message] : nil) if validations.key?(:types)
|
|
394
|
-
|
|
395
|
-
validations.delete(:types) if validations.key?(:types)
|
|
396
|
-
|
|
397
|
-
coerce_type = validations[:coerce]
|
|
398
|
-
|
|
399
|
-
# Special case - when the argument is a single type that is a
|
|
400
|
-
# variant-type collection.
|
|
401
|
-
if Types.multiple?(coerce_type) && validations.key?(:type)
|
|
402
|
-
validations[:coerce] = Types::VariantCollectionCoercer.new(
|
|
403
|
-
coerce_type,
|
|
404
|
-
validations.delete(:coerce_with)
|
|
405
|
-
)
|
|
406
|
-
end
|
|
407
|
-
validations.delete(:type)
|
|
408
|
-
|
|
409
|
-
coerce_type
|
|
410
|
-
end
|
|
411
|
-
|
|
412
|
-
# Enforce correct usage of :coerce_with parameter.
|
|
413
|
-
# We do not allow coercion without a type, nor with
|
|
414
|
-
# +JSON+ as a type since this defines its own coercion
|
|
415
|
-
# method.
|
|
416
|
-
def check_coerce_with(validations)
|
|
417
|
-
return unless validations.key?(:coerce_with)
|
|
418
|
-
# type must be supplied for coerce_with..
|
|
419
|
-
raise ArgumentError, 'must supply type for coerce_with' unless validations.key?(:coerce)
|
|
420
|
-
|
|
421
|
-
# but not special JSON types, which
|
|
422
|
-
# already imply coercion method
|
|
423
|
-
return if [JSON, Array[JSON]].exclude? validations[:coerce]
|
|
385
|
+
# Enforce correct usage of :coerce_with on a CoerceOptions.
|
|
386
|
+
# We do not allow coercion without a type, nor with +JSON+ as a type
|
|
387
|
+
# since that defines its own coercion method.
|
|
388
|
+
def check_coerce_with(coerce_options)
|
|
389
|
+
return unless coerce_options.coerce_method
|
|
390
|
+
raise ArgumentError, 'must supply type for coerce_with' unless coerce_options.type
|
|
391
|
+
return unless SPECIAL_JSON.include?(coerce_options.type)
|
|
424
392
|
|
|
425
393
|
raise ArgumentError, 'coerce_with disallowed for type: JSON'
|
|
426
394
|
end
|
|
427
395
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
# This validation has special handling since it is
|
|
431
|
-
# composited from more than one +requires+/+optional+
|
|
432
|
-
# parameter, and needs to be run before most other
|
|
433
|
-
# validations.
|
|
434
|
-
def coerce_type(validations, attrs, doc, opts)
|
|
435
|
-
check_coerce_with(validations)
|
|
436
|
-
|
|
437
|
-
return unless validations.key?(:coerce)
|
|
438
|
-
|
|
439
|
-
coerce_options = {
|
|
440
|
-
type: validations[:coerce],
|
|
441
|
-
method: validations[:coerce_with],
|
|
442
|
-
message: validations[:coerce_message]
|
|
443
|
-
}
|
|
444
|
-
validate('coerce', coerce_options, attrs, doc, opts)
|
|
445
|
-
validations.delete(:coerce_with)
|
|
446
|
-
validations.delete(:coerce)
|
|
447
|
-
validations.delete(:coerce_message)
|
|
448
|
-
end
|
|
449
|
-
|
|
450
|
-
def guess_coerce_type(coerce_type, *values_list)
|
|
451
|
-
return coerce_type unless coerce_type == Array
|
|
396
|
+
def validate_presence(spec, attrs)
|
|
397
|
+
return unless spec.required?
|
|
452
398
|
|
|
453
|
-
|
|
454
|
-
next if !values || values.is_a?(Proc)
|
|
455
|
-
return values.first.class if values.is_a?(Range) || !values.empty?
|
|
456
|
-
end
|
|
457
|
-
coerce_type
|
|
399
|
+
validate('presence', spec.presence_options, attrs, true, spec.shared_opts)
|
|
458
400
|
end
|
|
459
401
|
|
|
460
|
-
def
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
return unless excepts && !excepts.is_a?(Proc)
|
|
471
|
-
raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :except, excepts) \
|
|
472
|
-
unless Array(default).none? { |def_val| excepts.include?(def_val) }
|
|
473
|
-
end
|
|
402
|
+
def validate_coerce(spec, attrs)
|
|
403
|
+
coerce_options = spec.coerce_options
|
|
404
|
+
check_coerce_with(coerce_options)
|
|
405
|
+
# Falsy check is intentional: when a remountable API is first evaluated
|
|
406
|
+
# on its base instance (no configuration supplied yet),
|
|
407
|
+
# configuration[:some_type] evaluates to nil. Skipping instantiation
|
|
408
|
+
# here is correct — the real mounted instance will replay this step
|
|
409
|
+
# with the actual type value.
|
|
410
|
+
return unless coerce_options.type
|
|
474
411
|
|
|
475
|
-
|
|
476
|
-
validator_options = {
|
|
477
|
-
attributes: attrs,
|
|
478
|
-
options: options,
|
|
479
|
-
required: doc.required,
|
|
480
|
-
params_scope: self,
|
|
481
|
-
opts: opts,
|
|
482
|
-
validator_class: Validations.require_validator(type)
|
|
483
|
-
}
|
|
484
|
-
@api.namespace_stackable(:validations, validator_options)
|
|
412
|
+
validate('coerce', coerce_options, attrs, spec.required?, spec.shared_opts)
|
|
485
413
|
end
|
|
486
414
|
|
|
487
|
-
|
|
488
|
-
|
|
415
|
+
# Translate a `oneof: [proc, proc, ...]` declaration into a list of
|
|
416
|
+
# captured validator arrays — one array per variant. Each variant's
|
|
417
|
+
# block is evaluated in its own +ParamsScope+ backed by an
|
|
418
|
+
# {OneofCollector} so the full params DSL is available inside variants
|
|
419
|
+
# and the resulting validators are kept out of the real API's
|
|
420
|
+
# registration list.
|
|
421
|
+
# Returns the collected variants rather than writing them back into
|
|
422
|
+
# +validations+, which is the options Hash the +requires+/+optional+ call
|
|
423
|
+
# site built.
|
|
424
|
+
def collected_oneof(validations)
|
|
425
|
+
raise ArgumentError, 'oneof: requires type: Hash' unless validations[:type] == Hash
|
|
489
426
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
427
|
+
variants = validations[:oneof]
|
|
428
|
+
raise ArgumentError, 'oneof: must be a non-empty Array of blocks' unless variants.is_a?(Array) && variants.any?
|
|
429
|
+
raise ArgumentError, 'oneof: each variant must be a Proc' unless variants.all?(Proc)
|
|
493
430
|
|
|
494
|
-
|
|
495
|
-
value_types = value_types.map { |type| Grape::API::Boolean.build(type) } if coerce_type == Grape::API::Boolean
|
|
496
|
-
raise Grape::Exceptions::IncompatibleOptionValues.new(:type, coerce_type, :values, values) unless value_types.all?(coerce_type)
|
|
497
|
-
end
|
|
431
|
+
variants.map { |block| OneofCollector.collect(block) }
|
|
498
432
|
end
|
|
499
433
|
|
|
500
|
-
def
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
434
|
+
def validate(type, options, attrs, required, opts)
|
|
435
|
+
validator_class = Validations.require_validator(type)
|
|
436
|
+
validator_instance = validator_class.new(
|
|
437
|
+
attrs,
|
|
438
|
+
options,
|
|
439
|
+
required,
|
|
440
|
+
self,
|
|
441
|
+
opts
|
|
442
|
+
)
|
|
443
|
+
@api.inheritable_setting.add_validation(validator_instance)
|
|
509
444
|
end
|
|
510
445
|
|
|
511
446
|
def all_element_blank?(scoped_params)
|
|
512
447
|
scoped_params.respond_to?(:all?) && scoped_params.all?(&:blank?)
|
|
513
448
|
end
|
|
514
|
-
|
|
515
|
-
# Validators don't have access to each other and they don't need, however,
|
|
516
|
-
# some validators might influence others, so their options should be shared
|
|
517
|
-
def derive_validator_options(validations)
|
|
518
|
-
allow_blank = validations[:allow_blank]
|
|
519
|
-
|
|
520
|
-
{
|
|
521
|
-
allow_blank: allow_blank.is_a?(Hash) ? allow_blank[:value] : allow_blank,
|
|
522
|
-
fail_fast: validations.delete(:fail_fast) || false
|
|
523
|
-
}
|
|
524
|
-
end
|
|
525
|
-
|
|
526
|
-
def validates_presence(validations, attrs, doc, opts)
|
|
527
|
-
return unless validations.key?(:presence) && validations[:presence]
|
|
528
|
-
|
|
529
|
-
validate(:presence, validations.delete(:presence), attrs, doc, opts)
|
|
530
|
-
validations.delete(:message) if validations.key?(:message)
|
|
531
|
-
end
|
|
532
449
|
end
|
|
533
450
|
end
|
|
534
451
|
end
|