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,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Util
|
|
5
|
+
module Lazy
|
|
6
|
+
# Abstract parent for lazy wrappers used by the remount/configuration
|
|
7
|
+
# machinery. Call sites can type-check with +is_a?(Grape::Util::Lazy::Base)+
|
|
8
|
+
# instead of enumerating the concrete subclasses.
|
|
9
|
+
class Base
|
|
10
|
+
def to_s
|
|
11
|
+
evaluate.to_s
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Util
|
|
5
|
+
module Lazy
|
|
6
|
+
class Block < Base
|
|
7
|
+
def initialize(&new_block)
|
|
8
|
+
super()
|
|
9
|
+
@block = new_block
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def evaluate_from(configuration)
|
|
13
|
+
@block.call(configuration)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def evaluate
|
|
17
|
+
@block.call({})
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Util
|
|
5
|
+
module Lazy
|
|
6
|
+
class Value < Base
|
|
7
|
+
attr_reader :access_keys
|
|
8
|
+
|
|
9
|
+
def initialize(value, access_keys = [])
|
|
10
|
+
super()
|
|
11
|
+
@value = value
|
|
12
|
+
@access_keys = access_keys
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def evaluate_from(configuration)
|
|
16
|
+
matching_lazy_value = configuration.fetch(@access_keys)
|
|
17
|
+
matching_lazy_value.evaluate
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def evaluate
|
|
21
|
+
@value
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def reached_by(parent_access_keys, access_key)
|
|
25
|
+
@access_keys = parent_access_keys + [access_key]
|
|
26
|
+
self
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Util
|
|
5
|
+
module Lazy
|
|
6
|
+
class ValueArray < ValueEnumerable
|
|
7
|
+
def initialize(array)
|
|
8
|
+
super
|
|
9
|
+
@value_hash = []
|
|
10
|
+
array.each_with_index do |value, index|
|
|
11
|
+
self[index] = value
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def evaluate
|
|
16
|
+
@value_hash.map(&:evaluate)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Util
|
|
5
|
+
module Lazy
|
|
6
|
+
class ValueEnumerable < Value
|
|
7
|
+
def [](key)
|
|
8
|
+
return Value.new(nil).reached_by(access_keys, key) if @value_hash[key].nil?
|
|
9
|
+
|
|
10
|
+
@value_hash[key].reached_by(access_keys, key)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def fetch(access_keys)
|
|
14
|
+
access_keys.reduce(self) { |node, key| node[key] }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def []=(key, value)
|
|
18
|
+
value_class = case value
|
|
19
|
+
when Hash
|
|
20
|
+
ValueHash
|
|
21
|
+
when Array
|
|
22
|
+
ValueArray
|
|
23
|
+
else
|
|
24
|
+
Value
|
|
25
|
+
end
|
|
26
|
+
@value_hash[key] = value_class.new(value)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Util
|
|
5
|
+
module Lazy
|
|
6
|
+
class ValueHash < ValueEnumerable
|
|
7
|
+
def initialize(hash)
|
|
8
|
+
super
|
|
9
|
+
@value_hash = ActiveSupport::HashWithIndifferentAccess.new
|
|
10
|
+
hash.each do |key, value|
|
|
11
|
+
self[key] = value
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def evaluate
|
|
16
|
+
@value_hash.transform_values(&:evaluate)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Util
|
|
5
|
+
class MediaType
|
|
6
|
+
attr_reader :type, :subtype, :vendor, :version, :format
|
|
7
|
+
|
|
8
|
+
# based on the HTTP Accept header with the pattern:
|
|
9
|
+
# application/vnd.:vendor-:version+:format
|
|
10
|
+
#
|
|
11
|
+
# Matched against a down-cased media type: they are case-insensitive
|
|
12
|
+
# (RFC 9110 §8.3.1), while a vendor and version are declared in the DSL
|
|
13
|
+
# in the case they will be compared in.
|
|
14
|
+
VENDOR_VERSION_HEADER_REGEX = /\Avnd\.(?<vendor>[a-z0-9.\-_!^]+?)(?:-(?<version>[a-z0-9*.]+))?(?:\+(?<format>[a-z0-9*\-.]+))?\z/
|
|
15
|
+
|
|
16
|
+
def initialize(type:, subtype:)
|
|
17
|
+
@type = type
|
|
18
|
+
@subtype = subtype
|
|
19
|
+
VENDOR_VERSION_HEADER_REGEX.match(subtype) do |m|
|
|
20
|
+
@vendor = m[:vendor]
|
|
21
|
+
@version = m[:version]
|
|
22
|
+
@format = m[:format]
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def ==(other)
|
|
27
|
+
self.class == other.class &&
|
|
28
|
+
other.type == type &&
|
|
29
|
+
other.subtype == subtype &&
|
|
30
|
+
other.vendor == vendor &&
|
|
31
|
+
other.version == version &&
|
|
32
|
+
other.format == format
|
|
33
|
+
end
|
|
34
|
+
alias eql? ==
|
|
35
|
+
|
|
36
|
+
def hash
|
|
37
|
+
[self.class, type, subtype, vendor, version, format].hash
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class << self
|
|
41
|
+
def best_quality(header, available_media_types)
|
|
42
|
+
parse(best_quality_media_type(header, available_media_types))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def parse(media_type)
|
|
46
|
+
return if media_type.blank?
|
|
47
|
+
|
|
48
|
+
type, subtype = media_type.downcase.split('/', 2)
|
|
49
|
+
return if type.blank? || subtype.blank?
|
|
50
|
+
|
|
51
|
+
new(type:, subtype:)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def match?(media_type)
|
|
55
|
+
return false if media_type.blank?
|
|
56
|
+
|
|
57
|
+
subtype = media_type.downcase.split('/', 2).last
|
|
58
|
+
return false if subtype.blank?
|
|
59
|
+
|
|
60
|
+
VENDOR_VERSION_HEADER_REGEX.match?(subtype)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# The available types are registered in lower case and Rack matches them
|
|
64
|
+
# literally, so the header has to be down-cased to be compared against
|
|
65
|
+
# them at all.
|
|
66
|
+
def best_quality_media_type(header, available_media_types)
|
|
67
|
+
header.blank? ? available_media_types.first : Rack::Utils.best_q_match(header.downcase, available_media_types)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private_class_method :best_quality_media_type
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Util
|
|
5
|
+
module PathNormalizer
|
|
6
|
+
# Taken from Rails
|
|
7
|
+
# call("/foo") # => "/foo"
|
|
8
|
+
# call("/foo/") # => "/foo"
|
|
9
|
+
# call("foo") # => "/foo"
|
|
10
|
+
# call("") # => "/"
|
|
11
|
+
# call("/%ab") # => "/%AB"
|
|
12
|
+
# https://github.com/rails/rails/blob/00cc4ff0259c0185fe08baadaa40e63ea2534f6e/actionpack/lib/action_dispatch/journey/router/utils.rb#L19
|
|
13
|
+
def self.call(path)
|
|
14
|
+
return '/' unless path
|
|
15
|
+
return path if path == '/'
|
|
16
|
+
|
|
17
|
+
# Fast path for the overwhelming majority of paths that don't need to be
|
|
18
|
+
# normalized. Two String#include? calls rather than one `%|//` regexp:
|
|
19
|
+
# same predicate, and the scan stays in C without building a match.
|
|
20
|
+
return path if path.start_with?('/') && !(path.end_with?('/') || path.include?('%') || path.include?('//'))
|
|
21
|
+
|
|
22
|
+
# Slow path. The bangs below are safe because +normalized+ is a fresh
|
|
23
|
+
# String built here, never the one the caller passed in.
|
|
24
|
+
encoding = path.encoding
|
|
25
|
+
normalized = "/#{path}"
|
|
26
|
+
normalized.squeeze!('/')
|
|
27
|
+
|
|
28
|
+
unless normalized == '/'
|
|
29
|
+
normalized.delete_suffix!('/')
|
|
30
|
+
normalized.gsub!(/(%[a-f0-9]{2})/) { ::Regexp.last_match(1).upcase }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
normalized.force_encoding(encoding)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Util
|
|
5
|
+
module Registry
|
|
6
|
+
def register(klass)
|
|
7
|
+
short_name = build_short_name(klass)
|
|
8
|
+
return if short_name.nil?
|
|
9
|
+
|
|
10
|
+
warn "#{short_name} is already registered with class #{registry[short_name]}. It will be overridden globally with the following: #{klass.name}" if registry.key?(short_name)
|
|
11
|
+
registry[short_name] = registry[short_name.to_sym] = klass
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def build_short_name(klass)
|
|
17
|
+
return if klass.name.blank?
|
|
18
|
+
|
|
19
|
+
klass.name.demodulize.underscore
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Every registration under both spellings in one plain Hash, so a lookup
|
|
23
|
+
# is a single +Hash#[]+. A +HashWithIndifferentAccess+ converted the key
|
|
24
|
+
# on every read instead -- and these registries are read on the request
|
|
25
|
+
# path, by +Grape::Formatter+ once per response, by +Grape::Parser+ once
|
|
26
|
+
# per parsed body and by +Grape::ParamsBuilder+ once per params build,
|
|
27
|
+
# always with a Symbol, which is the spelling +convert_key+ allocates a
|
|
28
|
+
# String for.
|
|
29
|
+
#
|
|
30
|
+
# +register+ derives the short name as a String, so the Symbol is the
|
|
31
|
+
# alias.
|
|
32
|
+
def registry
|
|
33
|
+
@registry ||= {}
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Util
|
|
5
|
+
# Diagnostics for +rescue_from+ registrations that can never run.
|
|
6
|
+
#
|
|
7
|
+
# Middleware::Error resolves with +find+, so within a scope the first
|
|
8
|
+
# matching class wins and one registered for a class an earlier handler
|
|
9
|
+
# already covers is dead code — silently, before this warned:
|
|
10
|
+
#
|
|
11
|
+
# rescue_from StandardError do ... end # wins
|
|
12
|
+
# rescue_from ArgumentError do ... end # never runs
|
|
13
|
+
#
|
|
14
|
+
# Warn rather than reorder: which should win is the author's call, and
|
|
15
|
+
# +rescue_from :all+ (consulted only after the registered handlers) already
|
|
16
|
+
# offers "broad first, specific still wins" to anyone who wants it.
|
|
17
|
+
module ShadowedRescueHandlers
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
# @param registered [Hash] the scope's own handlers, in registration order
|
|
21
|
+
# @param mapping [Hash] the handlers being registered now
|
|
22
|
+
# @return [void]
|
|
23
|
+
#
|
|
24
|
+
# Only a scope's own registrations are compared: across scopes the nearest
|
|
25
|
+
# one deliberately wins, so an inner +rescue_from StandardError+ shadowing
|
|
26
|
+
# an outer +rescue_from ArgumentError+ is the documented behaviour rather
|
|
27
|
+
# than a mistake. Classes sharing a handler object are skipped too, since
|
|
28
|
+
# +rescue_from A, B+ registers one handler for both and the entry that
|
|
29
|
+
# loses to the other changes nothing.
|
|
30
|
+
def warn_about(registered, mapping)
|
|
31
|
+
return if registered.empty?
|
|
32
|
+
|
|
33
|
+
mapping.each do |klass, handler|
|
|
34
|
+
covered_by, = registered.find { |already, existing| klass <= already && !existing.equal?(handler) }
|
|
35
|
+
next unless covered_by
|
|
36
|
+
|
|
37
|
+
warn(message_for(klass, covered_by))
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def message_for(klass, covered_by)
|
|
42
|
+
return "Grape: rescue_from #{klass} was already registered in this scope; the first handler is kept and this one will never run." if klass == covered_by
|
|
43
|
+
|
|
44
|
+
"Grape: rescue_from #{klass} will never run — #{covered_by} was registered earlier in the same scope " \
|
|
45
|
+
'and is matched first. Register the more specific class before the broader one.'
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -1,28 +1,55 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative 'base_inheritable'
|
|
4
|
-
|
|
5
3
|
module Grape
|
|
6
4
|
module Util
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
# A read-only view of one settings scope's stackable registrations.
|
|
6
|
+
#
|
|
7
|
+
# Grape stores nothing here anymore: the registrations live on
|
|
8
|
+
# Grape::Util::InheritableSetting, one Array per key per scope, and are
|
|
9
|
+
# reached through its semantic accessors (+helpers+, +middleware+,
|
|
10
|
+
# +namespaces+, ...). This class survives only because grape-swagger
|
|
11
|
+
# reads InheritableSetting#namespace_stackable directly — including
|
|
12
|
+
# walking the #inherited_values chain and reading each level's own
|
|
13
|
+
# #new_values to recover per-scope registrations — and is expected to be
|
|
14
|
+
# removed once grape-swagger reads those accessors instead.
|
|
15
|
+
#
|
|
16
|
+
# Instances are built on demand by InheritableSetting#namespace_stackable
|
|
17
|
+
# and are not the backing store: writing to #new_values does not register
|
|
18
|
+
# anything.
|
|
19
|
+
class StackableValues
|
|
20
|
+
EMPTY = [].freeze
|
|
21
|
+
|
|
22
|
+
attr_reader :inherited_values, :new_values
|
|
23
|
+
|
|
24
|
+
# @param new_values [Hash, nil] the scope's own registrations, one Array
|
|
25
|
+
# per key; nil when the scope never registered anything.
|
|
26
|
+
# @param inherited_values [StackableValues, Hash] the enclosing scope's
|
|
27
|
+
# view, or an empty Hash at the root of the chain.
|
|
28
|
+
def initialize(new_values, inherited_values)
|
|
29
|
+
@new_values = new_values
|
|
30
|
+
@inherited_values = inherited_values
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Outermost scope first. Even if there is no value, an empty (frozen)
|
|
34
|
+
# array will be returned.
|
|
9
35
|
def [](name)
|
|
10
|
-
inherited_value = inherited_values[name]
|
|
11
|
-
new_value = new_values[name]
|
|
36
|
+
inherited_value = @inherited_values[name]
|
|
37
|
+
new_value = @new_values && @new_values[name]
|
|
12
38
|
|
|
13
|
-
return new_value ||
|
|
39
|
+
return new_value || EMPTY unless inherited_value
|
|
14
40
|
|
|
15
41
|
concat_values(inherited_value, new_value)
|
|
16
42
|
end
|
|
17
43
|
|
|
18
|
-
def
|
|
19
|
-
new_values
|
|
20
|
-
|
|
44
|
+
def keys
|
|
45
|
+
return @inherited_values.keys if @new_values.nil? || @new_values.empty?
|
|
46
|
+
|
|
47
|
+
(@inherited_values.keys + @new_values.keys).uniq
|
|
21
48
|
end
|
|
22
49
|
|
|
23
50
|
def to_hash
|
|
24
|
-
keys.
|
|
25
|
-
|
|
51
|
+
keys.to_h do |key|
|
|
52
|
+
[key, self[key]]
|
|
26
53
|
end
|
|
27
54
|
end
|
|
28
55
|
|
|
@@ -31,10 +58,7 @@ module Grape
|
|
|
31
58
|
def concat_values(inherited_value, new_value)
|
|
32
59
|
return inherited_value unless new_value
|
|
33
60
|
|
|
34
|
-
|
|
35
|
-
value.concat(inherited_value)
|
|
36
|
-
value.concat(new_value)
|
|
37
|
-
end
|
|
61
|
+
inherited_value + new_value
|
|
38
62
|
end
|
|
39
63
|
end
|
|
40
64
|
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Util
|
|
5
|
+
module Translation
|
|
6
|
+
FALLBACK_LOCALE = :en
|
|
7
|
+
private_constant :FALLBACK_LOCALE
|
|
8
|
+
# Sentinel returned by I18n when a key is missing (passed as the default:
|
|
9
|
+
# value). Using a named class rather than plain Object.new makes it
|
|
10
|
+
# identifiable in debug output and immune to backends that call .to_s on
|
|
11
|
+
# the default before returning it.
|
|
12
|
+
MISSING = Class.new { def inspect = 'Grape::Util::Translation::MISSING' }.new.freeze
|
|
13
|
+
private_constant :MISSING
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
# Extra keyword args (**) are forwarded verbatim to I18n as interpolation
|
|
18
|
+
# variables (e.g. +min:+, +max:+ from LengthValidator's Hash message).
|
|
19
|
+
# Callers must not pass unintended keyword arguments — any extra keyword
|
|
20
|
+
# will silently become an I18n interpolation variable.
|
|
21
|
+
def translate(key, default: MISSING, scope: 'grape.errors.messages', locale: nil, **)
|
|
22
|
+
i18n_opts = { default:, scope:, ** }
|
|
23
|
+
i18n_opts[:locale] = locale if locale
|
|
24
|
+
message = ::I18n.translate(key, **i18n_opts)
|
|
25
|
+
return message unless message.equal?(MISSING)
|
|
26
|
+
|
|
27
|
+
effective_default = default.equal?(MISSING) ? [*Array(scope), key].join('.') : default
|
|
28
|
+
return effective_default if fallback_locale?(locale) || fallback_locale_unavailable?
|
|
29
|
+
|
|
30
|
+
::I18n.translate(key, default: effective_default, scope:, locale: FALLBACK_LOCALE, **)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def fallback_locale?(locale)
|
|
34
|
+
(locale || ::I18n.locale) == FALLBACK_LOCALE
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def fallback_locale_unavailable?
|
|
38
|
+
::I18n.enforce_available_locales && !::I18n.available_locales.include?(FALLBACK_LOCALE)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -3,55 +3,88 @@
|
|
|
3
3
|
module Grape
|
|
4
4
|
module Validations
|
|
5
5
|
class AttributesIterator
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def initialize(
|
|
6
|
+
# +attrs+ and +scope+ are static per validator; only +params+ varies
|
|
7
|
+
# per request, so an instance can be built once and reused (it keeps
|
|
8
|
+
# no request-derived state). Reused instances are shared across
|
|
9
|
+
# threads, so +each+ must stay free of mutable instance state.
|
|
10
|
+
def initialize(attrs, scope)
|
|
11
|
+
@attrs = attrs
|
|
11
12
|
@scope = scope
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
# How many times #do_each may descend into a nested array. The
|
|
14
|
+
# declaration allows one level per Array-typed scope on the chain, less
|
|
15
|
+
# the one +Array.wrap+ already consumes in #each. Anything deeper was
|
|
16
|
+
# put there by the request, not by the declaration.
|
|
17
|
+
@max_nesting = [scope.array_depth - 1, 0].max
|
|
15
18
|
end
|
|
16
19
|
|
|
17
|
-
def each(&
|
|
18
|
-
|
|
20
|
+
def each(params, &)
|
|
21
|
+
original_params = @scope.params(params)
|
|
22
|
+
# A scope resolves to a Hash unless the declaration nests arrays, and
|
|
23
|
+
# then #do_each has nothing to do but hand it straight back: Array.wrap
|
|
24
|
+
# boxes it, the loop unboxes it on its only iteration, and with no Array
|
|
25
|
+
# anywhere neither the nesting descent nor the index bookkeeping
|
|
26
|
+
# applies. Every validator on a flat +params+ block comes through here.
|
|
27
|
+
return yield_attributes(original_params, &) if original_params.is_a?(Hash) && !@scope.iterates_elements?
|
|
28
|
+
|
|
29
|
+
# because we need recursion for nested arrays
|
|
30
|
+
do_each(Array.wrap(original_params), original_params, &)
|
|
19
31
|
end
|
|
20
32
|
|
|
21
33
|
private
|
|
22
34
|
|
|
23
|
-
def do_each(params_to_process,
|
|
35
|
+
def do_each(params_to_process, original_params, parent_indices = [], &block)
|
|
24
36
|
params_to_process.each_with_index do |resource_params, index|
|
|
25
37
|
# when we get arrays of arrays it means that target element located inside array
|
|
26
|
-
# we need this because we want to know parent arrays
|
|
27
|
-
|
|
28
|
-
|
|
38
|
+
# we need this because we want to know parent arrays indices
|
|
39
|
+
#
|
|
40
|
+
# Only descend as far as the declaration nests. A request that wraps
|
|
41
|
+
# its elements deeper than that is yielded as-is, so the attribute
|
|
42
|
+
# validators see a non-hash and fail it the same way any other
|
|
43
|
+
# unexpected element type does.
|
|
44
|
+
if resource_params.is_a?(Array) && parent_indices.size < @max_nesting
|
|
45
|
+
do_each(resource_params, original_params, [index] + parent_indices, &block)
|
|
29
46
|
next
|
|
30
47
|
end
|
|
31
48
|
|
|
32
|
-
if @scope.
|
|
33
|
-
next unless
|
|
49
|
+
if @scope.iterates_elements?
|
|
50
|
+
next unless original_params.is_a?(Array) # do not validate content of array if it isn't array
|
|
34
51
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
52
|
+
store_indices(@scope, index, parent_indices)
|
|
53
|
+
elsif original_params.is_a?(Array)
|
|
54
|
+
# Lateral scope (no @element) whose params resolved to an array —
|
|
55
|
+
# delegate index tracking to the nearest element-iterating ancestor
|
|
56
|
+
# so that full_name produces the correct bracketed index.
|
|
57
|
+
target = @scope.nearest_array_ancestor
|
|
58
|
+
store_indices(target, index, parent_indices) if target
|
|
42
59
|
end
|
|
43
60
|
|
|
44
|
-
yield_attributes(resource_params,
|
|
61
|
+
yield_attributes(resource_params, &block)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def store_indices(target_scope, index, parent_indices)
|
|
66
|
+
# No tracker means we're outside a ParamScopeTracker.track block (e.g.
|
|
67
|
+
# a unit test that invokes a validator directly). Index tracking is
|
|
68
|
+
# skipped — full_name will produce bracket-less names — but validation
|
|
69
|
+
# continues rather than crashing.
|
|
70
|
+
tracker = ParamScopeTracker.current or return
|
|
71
|
+
parent_scope = target_scope.parent
|
|
72
|
+
parent_indices.each do |parent_index|
|
|
73
|
+
break unless parent_scope
|
|
74
|
+
|
|
75
|
+
tracker.store_index(parent_scope, parent_index)
|
|
76
|
+
parent_scope = parent_scope.parent
|
|
45
77
|
end
|
|
78
|
+
tracker.store_index(target_scope, index)
|
|
46
79
|
end
|
|
47
80
|
|
|
48
|
-
def yield_attributes(_resource_params
|
|
81
|
+
def yield_attributes(_resource_params)
|
|
49
82
|
raise NotImplementedError
|
|
50
83
|
end
|
|
51
84
|
|
|
52
|
-
# This is a special case so that we can ignore
|
|
53
|
-
# values are missing lower down. Unfortunately we can remove this
|
|
54
|
-
#
|
|
85
|
+
# This is a special case so that we can ignore trees where option
|
|
86
|
+
# values are missing lower down. Unfortunately we can't remove this
|
|
87
|
+
# at the parameter parsing stage as they are required to ensure
|
|
55
88
|
# the correct indexing is maintained
|
|
56
89
|
def skip?(val)
|
|
57
90
|
val == Grape::DSL::Parameters::EmptyOptionalValue
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Validations
|
|
5
|
+
# Immutable value object describing how a parameter is coerced. Assembled
|
|
6
|
+
# by {ValidationsSpec#coerce_options} from the parsed +type+/+coerce_with+/
|
|
7
|
+
# +coerce_message+ declaration — never written by the user — and consumed
|
|
8
|
+
# by {ParamsScope#check_coerce_with} / {ParamsScope#validate_coerce} and by
|
|
9
|
+
# {Validators::Validators::CoerceValidator} (which receives it as its
|
|
10
|
+
# +options+ argument).
|
|
11
|
+
#
|
|
12
|
+
# All three fields may be +nil+ (e.g. a remountable API evaluated on its
|
|
13
|
+
# base instance has no resolved +type+ yet).
|
|
14
|
+
# +coerce_method+ (not +method+) avoids shadowing +Object#method+.
|
|
15
|
+
CoerceOptions = Data.define(:type, :coerce_method, :message) do
|
|
16
|
+
def initialize(type: nil, coerce_method: nil, message: nil)
|
|
17
|
+
super
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Validations
|
|
5
|
+
class ContractScope
|
|
6
|
+
# Declare the contract to be used for the endpoint's parameters.
|
|
7
|
+
# @param api [API] the API endpoint to modify.
|
|
8
|
+
# @param contract the contract or schema to be used for validation. Optional.
|
|
9
|
+
# @yield a block yielding a new schema class. Optional.
|
|
10
|
+
def initialize(api, contract = nil, &block)
|
|
11
|
+
# When block is passed, the first arg is either schema or nil.
|
|
12
|
+
declared = block ? Dry::Schema.Params(parent: contract, &block) : contract
|
|
13
|
+
|
|
14
|
+
if declared.respond_to?(:schema)
|
|
15
|
+
# It's a Dry::Validation::Contract, then.
|
|
16
|
+
schema = declared.new
|
|
17
|
+
key_map = schema.schema.key_map
|
|
18
|
+
else
|
|
19
|
+
# Dry::Schema::Processor, hopefully.
|
|
20
|
+
schema = declared
|
|
21
|
+
key_map = declared.key_map
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
api.inheritable_setting.add_contract_key_map(key_map)
|
|
25
|
+
api.inheritable_setting.add_validation(Validators::ContractScopeValidator.new(schema:))
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|