grape 1.8.0 → 3.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +391 -1
- data/CONTRIBUTING.md +3 -11
- data/README.md +648 -552
- data/UPGRADING.md +803 -7
- data/grape.gemspec +11 -14
- data/lib/grape/api/instance.rb +68 -158
- data/lib/grape/api.rb +71 -107
- data/lib/grape/content_types.rb +23 -11
- data/lib/grape/cookies.rb +28 -25
- data/lib/grape/declared_params_handler.rb +116 -0
- data/lib/grape/dry_types.rb +48 -6
- data/lib/grape/dsl/callbacks.rb +14 -58
- data/lib/grape/dsl/declared.rb +35 -0
- data/lib/grape/dsl/desc.rb +15 -63
- data/lib/grape/dsl/entity.rb +88 -0
- data/lib/grape/dsl/headers.rb +2 -2
- data/lib/grape/dsl/helpers.rb +83 -64
- data/lib/grape/dsl/inside_route.rb +56 -295
- data/lib/grape/dsl/logger.rb +4 -9
- data/lib/grape/dsl/middleware.rb +22 -40
- data/lib/grape/dsl/parameters.rb +50 -82
- data/lib/grape/dsl/request_response.rb +144 -146
- data/lib/grape/dsl/rescue_options.rb +24 -0
- data/lib/grape/dsl/routing.rb +261 -186
- data/lib/grape/dsl/settings.rb +29 -135
- data/lib/grape/dsl/validations.rb +39 -32
- data/lib/grape/dsl/version_options.rb +23 -0
- data/lib/grape/endpoint/options.rb +19 -0
- data/lib/grape/endpoint.rb +277 -250
- data/lib/grape/{util/env.rb → env.rb} +1 -8
- data/lib/grape/error_formatter/base.rb +56 -21
- data/lib/grape/error_formatter/json.rb +13 -15
- data/lib/grape/error_formatter/serializable_hash.rb +7 -0
- data/lib/grape/error_formatter/txt.rb +12 -18
- data/lib/grape/error_formatter/xml.rb +3 -13
- data/lib/grape/error_formatter.rb +5 -25
- data/lib/grape/exceptions/base.rb +22 -58
- data/lib/grape/exceptions/error_response.rb +45 -0
- data/lib/grape/exceptions/incompatible_option_values.rb +1 -1
- data/lib/grape/exceptions/internal_server_error.rb +16 -0
- data/lib/grape/exceptions/invalid_accept_header.rb +1 -1
- data/lib/grape/exceptions/invalid_formatter.rb +1 -1
- data/lib/grape/exceptions/invalid_message_body.rb +1 -1
- data/lib/grape/exceptions/invalid_version_header.rb +1 -1
- data/lib/grape/exceptions/invalid_versioner_option.rb +1 -1
- data/lib/grape/exceptions/method_not_allowed.rb +1 -1
- data/lib/grape/exceptions/missing_group_type.rb +0 -2
- data/lib/grape/exceptions/missing_mime_type.rb +1 -1
- data/lib/grape/exceptions/request_error.rb +11 -0
- data/lib/grape/exceptions/unknown_auth_strategy.rb +11 -0
- data/lib/grape/exceptions/unknown_parameter.rb +1 -1
- data/lib/grape/exceptions/unknown_params_builder.rb +11 -0
- data/lib/grape/exceptions/unknown_validator.rb +1 -1
- data/lib/grape/exceptions/unsupported_group_type.rb +0 -2
- data/lib/grape/exceptions/validation.rb +25 -9
- data/lib/grape/exceptions/validation_array_errors.rb +5 -0
- data/lib/grape/exceptions/validation_errors.rb +22 -26
- data/lib/grape/formatter/base.rb +16 -0
- data/lib/grape/formatter/json.rb +4 -6
- data/lib/grape/formatter/serializable_hash.rb +6 -10
- data/lib/grape/formatter/txt.rb +3 -5
- data/lib/grape/formatter/xml.rb +4 -6
- data/lib/grape/formatter.rb +7 -25
- data/lib/grape/json.rb +46 -0
- data/lib/grape/locale/en.yml +43 -42
- data/lib/grape/middleware/auth/base.rb +11 -33
- data/lib/grape/middleware/auth/dsl.rb +37 -31
- data/lib/grape/middleware/auth/strategies.rb +1 -2
- data/lib/grape/middleware/base.rb +56 -33
- data/lib/grape/middleware/deprecated_options_hash_access.rb +19 -0
- data/lib/grape/middleware/error.rb +183 -91
- data/lib/grape/middleware/formatter.rb +94 -105
- data/lib/grape/middleware/precomputed_content_types.rb +46 -0
- data/lib/grape/middleware/stack.rb +27 -38
- data/lib/grape/middleware/versioner/accept_version_header.rb +6 -33
- data/lib/grape/middleware/versioner/base.rb +72 -0
- data/lib/grape/middleware/versioner/header.rb +44 -129
- data/lib/grape/middleware/versioner/param.rb +4 -25
- data/lib/grape/middleware/versioner/path.rb +13 -32
- data/lib/grape/middleware/versioner.rb +7 -14
- data/lib/grape/mountable.rb +22 -0
- data/lib/grape/namespace.rb +18 -12
- data/lib/grape/params_builder/base.rb +20 -0
- data/lib/grape/params_builder/hash.rb +11 -0
- data/lib/grape/params_builder/hash_with_indifferent_access.rb +11 -0
- data/lib/grape/params_builder/hashie_mash.rb +11 -0
- data/lib/grape/params_builder.rb +15 -0
- data/lib/grape/parser/base.rb +16 -0
- data/lib/grape/parser/json.rb +6 -8
- data/lib/grape/parser/xml.rb +6 -8
- data/lib/grape/parser.rb +5 -23
- data/lib/grape/path.rb +38 -63
- data/lib/grape/railtie.rb +9 -0
- data/lib/grape/request.rb +168 -26
- data/lib/grape/router/base_route.rb +48 -0
- data/lib/grape/router/greedy_route.rb +26 -0
- data/lib/grape/router/mustermann_pattern.rb +44 -0
- data/lib/grape/router/pattern.rb +30 -34
- data/lib/grape/router/route.rb +28 -61
- data/lib/grape/router.rb +71 -86
- data/lib/grape/serve_stream/file_body.rb +1 -0
- data/lib/grape/serve_stream/sendfile_response.rb +3 -5
- data/lib/grape/serve_stream/stream_response.rb +1 -0
- data/lib/grape/testing.rb +33 -0
- data/lib/grape/util/api_description.rb +58 -0
- data/lib/grape/util/base_inheritable.rb +14 -14
- data/lib/grape/util/cache.rb +1 -3
- data/lib/grape/util/deep_freeze.rb +35 -0
- data/lib/grape/util/endpoint_configuration.rb +1 -1
- data/lib/grape/util/header.rb +13 -0
- data/lib/grape/util/inheritable_setting.rb +51 -27
- data/lib/grape/util/inheritable_values.rb +7 -5
- data/lib/grape/util/lazy/base.rb +16 -0
- data/lib/grape/util/lazy/block.rb +22 -0
- data/lib/grape/util/lazy/value.rb +31 -0
- data/lib/grape/util/lazy/value_array.rb +21 -0
- data/lib/grape/util/lazy/value_enumerable.rb +31 -0
- data/lib/grape/util/lazy/value_hash.rb +21 -0
- data/lib/grape/util/media_type.rb +67 -0
- data/lib/grape/util/path_normalizer.rb +34 -0
- data/lib/grape/util/registry.rb +27 -0
- data/lib/grape/util/reverse_stackable_values.rb +1 -6
- data/lib/grape/util/stackable_values.rb +12 -14
- data/lib/grape/util/translation.rb +42 -0
- data/lib/grape/validations/attributes_iterator.rb +43 -27
- data/lib/grape/validations/coerce_options.rb +21 -0
- data/lib/grape/validations/contract_scope.rb +28 -0
- data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
- data/lib/grape/validations/oneof_collector.rb +39 -0
- data/lib/grape/validations/param_scope_tracker.rb +62 -0
- data/lib/grape/validations/params_documentation.rb +52 -0
- data/lib/grape/validations/params_scope.rb +180 -303
- data/lib/grape/validations/shared_options.rb +19 -0
- data/lib/grape/validations/single_attribute_iterator.rb +2 -2
- data/lib/grape/validations/types/array_coercer.rb +4 -7
- data/lib/grape/validations/types/custom_type_coercer.rb +41 -85
- data/lib/grape/validations/types/custom_type_collection_coercer.rb +1 -1
- data/lib/grape/validations/types/dry_type_coercer.rb +15 -28
- data/lib/grape/validations/types/json.rb +0 -2
- data/lib/grape/validations/types/primitive_coercer.rb +11 -35
- data/lib/grape/validations/types/set_coercer.rb +1 -4
- data/lib/grape/validations/types/variant_collection_coercer.rb +11 -0
- data/lib/grape/validations/types.rb +29 -54
- data/lib/grape/validations/validations_spec.rb +164 -0
- data/lib/grape/validations/validators/all_or_none_of_validator.rb +6 -3
- data/lib/grape/validations/validators/allow_blank_validator.rb +10 -5
- data/lib/grape/validations/validators/at_least_one_of_validator.rb +5 -2
- data/lib/grape/validations/validators/base.rb +120 -36
- data/lib/grape/validations/validators/coerce_validator.rb +18 -36
- data/lib/grape/validations/validators/contract_scope_validator.rb +45 -0
- data/lib/grape/validations/validators/default_validator.rb +13 -16
- data/lib/grape/validations/validators/exactly_one_of_validator.rb +10 -3
- data/lib/grape/validations/validators/except_values_validator.rb +15 -5
- data/lib/grape/validations/validators/length_validator.rb +48 -0
- data/lib/grape/validations/validators/multiple_params_base.rb +12 -9
- data/lib/grape/validations/validators/{mutual_exclusion_validator.rb → mutually_exclusive_validator.rb} +4 -2
- data/lib/grape/validations/validators/oneof_validator.rb +49 -0
- data/lib/grape/validations/validators/presence_validator.rb +4 -2
- data/lib/grape/validations/validators/regexp_validator.rb +11 -3
- data/lib/grape/validations/validators/same_as_validator.rb +6 -15
- data/lib/grape/validations/validators/values_validator.rb +36 -65
- data/lib/grape/validations.rb +8 -21
- data/lib/grape/version.rb +1 -2
- data/lib/grape/xml.rb +17 -0
- data/lib/grape.rb +75 -288
- metadata +79 -291
- data/lib/grape/api/helpers.rb +0 -9
- data/lib/grape/dsl/api.rb +0 -19
- data/lib/grape/dsl/configuration.rb +0 -15
- data/lib/grape/eager_load.rb +0 -20
- data/lib/grape/exceptions/empty_message_body.rb +0 -11
- data/lib/grape/exceptions/missing_option.rb +0 -11
- data/lib/grape/exceptions/too_many_multipart_files.rb +0 -11
- data/lib/grape/exceptions/unknown_options.rb +0 -11
- data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +0 -27
- data/lib/grape/extensions/hash.rb +0 -22
- data/lib/grape/extensions/hashie/mash.rb +0 -26
- data/lib/grape/http/headers.rb +0 -61
- data/lib/grape/middleware/globals.rb +0 -16
- data/lib/grape/middleware/helpers.rb +0 -12
- data/lib/grape/middleware/versioner/parse_media_type_patch.rb +0 -24
- data/lib/grape/router/attribute_translator.rb +0 -63
- data/lib/grape/types/invalid_value.rb +0 -8
- data/lib/grape/util/json.rb +0 -12
- data/lib/grape/util/lazy_block.rb +0 -27
- data/lib/grape/util/lazy_object.rb +0 -43
- data/lib/grape/util/lazy_value.rb +0 -91
- data/lib/grape/util/registrable.rb +0 -15
- data/lib/grape/util/strict_hash_configuration.rb +0 -108
- data/lib/grape/util/xml.rb +0 -10
- data/lib/grape/validations/attributes_doc.rb +0 -58
- data/lib/grape/validations/types/build_coercer.rb +0 -94
- data/lib/grape/validations/validator_factory.rb +0 -15
- data/spec/grape/api/custom_validations_spec.rb +0 -213
- data/spec/grape/api/deeply_included_options_spec.rb +0 -56
- data/spec/grape/api/defines_boolean_in_params_spec.rb +0 -38
- data/spec/grape/api/documentation_spec.rb +0 -59
- data/spec/grape/api/inherited_helpers_spec.rb +0 -114
- data/spec/grape/api/instance_spec.rb +0 -103
- data/spec/grape/api/invalid_format_spec.rb +0 -45
- data/spec/grape/api/namespace_parameters_in_route_spec.rb +0 -38
- data/spec/grape/api/nested_helpers_spec.rb +0 -50
- data/spec/grape/api/optional_parameters_in_route_spec.rb +0 -43
- data/spec/grape/api/parameters_modification_spec.rb +0 -41
- data/spec/grape/api/patch_method_helpers_spec.rb +0 -79
- data/spec/grape/api/recognize_path_spec.rb +0 -21
- data/spec/grape/api/required_parameters_in_route_spec.rb +0 -37
- data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +0 -26
- data/spec/grape/api/routes_with_requirements_spec.rb +0 -59
- data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +0 -41
- data/spec/grape/api/shared_helpers_spec.rb +0 -36
- data/spec/grape/api_remount_spec.rb +0 -509
- data/spec/grape/api_spec.rb +0 -4356
- data/spec/grape/dsl/callbacks_spec.rb +0 -45
- data/spec/grape/dsl/desc_spec.rb +0 -98
- data/spec/grape/dsl/headers_spec.rb +0 -62
- data/spec/grape/dsl/helpers_spec.rb +0 -100
- data/spec/grape/dsl/inside_route_spec.rb +0 -531
- data/spec/grape/dsl/logger_spec.rb +0 -24
- data/spec/grape/dsl/middleware_spec.rb +0 -60
- data/spec/grape/dsl/parameters_spec.rb +0 -180
- data/spec/grape/dsl/request_response_spec.rb +0 -225
- data/spec/grape/dsl/routing_spec.rb +0 -275
- data/spec/grape/dsl/settings_spec.rb +0 -261
- data/spec/grape/dsl/validations_spec.rb +0 -55
- data/spec/grape/endpoint/declared_spec.rb +0 -846
- data/spec/grape/endpoint_spec.rb +0 -1085
- data/spec/grape/entity_spec.rb +0 -336
- data/spec/grape/exceptions/base_spec.rb +0 -81
- data/spec/grape/exceptions/body_parse_errors_spec.rb +0 -185
- data/spec/grape/exceptions/invalid_accept_header_spec.rb +0 -358
- data/spec/grape/exceptions/invalid_formatter_spec.rb +0 -15
- data/spec/grape/exceptions/invalid_response_spec.rb +0 -11
- data/spec/grape/exceptions/invalid_versioner_option_spec.rb +0 -15
- data/spec/grape/exceptions/missing_group_type_spec.rb +0 -17
- data/spec/grape/exceptions/missing_mime_type_spec.rb +0 -17
- data/spec/grape/exceptions/missing_option_spec.rb +0 -15
- data/spec/grape/exceptions/unknown_options_spec.rb +0 -15
- data/spec/grape/exceptions/unknown_validator_spec.rb +0 -15
- data/spec/grape/exceptions/unsupported_group_type_spec.rb +0 -19
- data/spec/grape/exceptions/validation_errors_spec.rb +0 -92
- data/spec/grape/exceptions/validation_spec.rb +0 -19
- data/spec/grape/extensions/param_builders/hash_spec.rb +0 -83
- data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +0 -105
- data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +0 -79
- data/spec/grape/grape_spec.rb +0 -9
- data/spec/grape/integration/global_namespace_function_spec.rb +0 -29
- data/spec/grape/integration/rack_sendfile_spec.rb +0 -48
- data/spec/grape/integration/rack_spec.rb +0 -51
- data/spec/grape/loading_spec.rb +0 -44
- data/spec/grape/middleware/auth/base_spec.rb +0 -31
- data/spec/grape/middleware/auth/dsl_spec.rb +0 -60
- data/spec/grape/middleware/auth/strategies_spec.rb +0 -120
- data/spec/grape/middleware/base_spec.rb +0 -221
- data/spec/grape/middleware/error_spec.rb +0 -85
- data/spec/grape/middleware/exception_spec.rb +0 -294
- data/spec/grape/middleware/formatter_spec.rb +0 -461
- data/spec/grape/middleware/globals_spec.rb +0 -30
- data/spec/grape/middleware/stack_spec.rb +0 -155
- data/spec/grape/middleware/versioner/accept_version_header_spec.rb +0 -122
- data/spec/grape/middleware/versioner/header_spec.rb +0 -345
- data/spec/grape/middleware/versioner/param_spec.rb +0 -171
- data/spec/grape/middleware/versioner/path_spec.rb +0 -62
- data/spec/grape/middleware/versioner_spec.rb +0 -21
- data/spec/grape/named_api_spec.rb +0 -19
- data/spec/grape/parser_spec.rb +0 -86
- data/spec/grape/path_spec.rb +0 -252
- data/spec/grape/presenters/presenter_spec.rb +0 -71
- data/spec/grape/request_spec.rb +0 -126
- data/spec/grape/util/inheritable_setting_spec.rb +0 -242
- data/spec/grape/util/inheritable_values_spec.rb +0 -79
- data/spec/grape/util/reverse_stackable_values_spec.rb +0 -134
- data/spec/grape/util/stackable_values_spec.rb +0 -128
- data/spec/grape/util/strict_hash_configuration_spec.rb +0 -38
- data/spec/grape/validations/attributes_doc_spec.rb +0 -153
- data/spec/grape/validations/instance_behaivour_spec.rb +0 -43
- data/spec/grape/validations/multiple_attributes_iterator_spec.rb +0 -38
- data/spec/grape/validations/params_scope_spec.rb +0 -1420
- data/spec/grape/validations/single_attribute_iterator_spec.rb +0 -56
- data/spec/grape/validations/types/array_coercer_spec.rb +0 -33
- data/spec/grape/validations/types/primitive_coercer_spec.rb +0 -150
- data/spec/grape/validations/types/set_coercer_spec.rb +0 -32
- data/spec/grape/validations/types_spec.rb +0 -111
- data/spec/grape/validations/validators/all_or_none_spec.rb +0 -162
- data/spec/grape/validations/validators/allow_blank_spec.rb +0 -575
- data/spec/grape/validations/validators/at_least_one_of_spec.rb +0 -205
- data/spec/grape/validations/validators/base_spec.rb +0 -38
- data/spec/grape/validations/validators/coerce_spec.rb +0 -1261
- data/spec/grape/validations/validators/default_spec.rb +0 -463
- data/spec/grape/validations/validators/exactly_one_of_spec.rb +0 -233
- data/spec/grape/validations/validators/except_values_spec.rb +0 -192
- data/spec/grape/validations/validators/mutual_exclusion_spec.rb +0 -214
- data/spec/grape/validations/validators/presence_spec.rb +0 -315
- data/spec/grape/validations/validators/regexp_spec.rb +0 -161
- data/spec/grape/validations/validators/same_as_spec.rb +0 -57
- data/spec/grape/validations/validators/values_spec.rb +0 -733
- data/spec/grape/validations/validators/zh-CN.yml +0 -10
- data/spec/grape/validations_spec.rb +0 -2030
- data/spec/integration/eager_load/eager_load_spec.rb +0 -15
- data/spec/integration/multi_json/json_spec.rb +0 -7
- data/spec/integration/multi_xml/xml_spec.rb +0 -7
- data/spec/shared/deprecated_class_examples.rb +0 -16
- data/spec/shared/versioning_examples.rb +0 -215
- data/spec/spec_helper.rb +0 -52
- data/spec/support/basic_auth_encode_helpers.rb +0 -11
- data/spec/support/chunks.rb +0 -14
- data/spec/support/content_type_helpers.rb +0 -15
- data/spec/support/endpoint_faker.rb +0 -25
- data/spec/support/file_streamer.rb +0 -13
- data/spec/support/integer_helpers.rb +0 -13
- data/spec/support/versioned_helpers.rb +0 -55
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
class Router
|
|
5
|
+
# Grape-style path patterns for Mustermann: `:param`, `*splat`, `{name}` /
|
|
6
|
+
# `{+splat}`, `( )` optionals, `|`, and an Integer digit-only constraint
|
|
7
|
+
# (driven by Grape's `params` option).
|
|
8
|
+
#
|
|
9
|
+
# Inlined from the mustermann-grape gem (MIT) by namusyaka, Konstantin Haase
|
|
10
|
+
# and Daniel Doubrovkine. Grape instantiates this class directly (see
|
|
11
|
+
# {Grape::Router::Pattern}), so unlike the gem it is not registered as a
|
|
12
|
+
# Mustermann `type: :grape`.
|
|
13
|
+
class MustermannPattern < ::Mustermann::AST::Pattern
|
|
14
|
+
supported_options :params
|
|
15
|
+
|
|
16
|
+
on(nil, '?', ')') { |c| unexpected(c) }
|
|
17
|
+
|
|
18
|
+
on('*') { |_c| scan(/\w+/) ? node(:named_splat, buffer.matched) : node(:splat) }
|
|
19
|
+
on(':') do |_c|
|
|
20
|
+
param_name = scan(/\w+/)
|
|
21
|
+
# Integer params (declared via Grape's `params` option) match digits only;
|
|
22
|
+
# any other capture matches a single path segment (anything but / ? # .).
|
|
23
|
+
param_type = pattern&.options&.dig(:params, param_name, :type)
|
|
24
|
+
constraint = param_type == 'Integer' ? /\d/ : '[^/?#.]'
|
|
25
|
+
node(:capture, param_name, constraint:) { scan(/\w+/) }
|
|
26
|
+
end
|
|
27
|
+
on('\\') { |_c| node(:char, expect(/./)) }
|
|
28
|
+
on('(') { |_c| node(:optional, node(:group) { read unless scan(')') }) }
|
|
29
|
+
on('|') { |_c| node(:or) }
|
|
30
|
+
|
|
31
|
+
on('{') do |_c|
|
|
32
|
+
type = scan('+') ? :named_splat : :capture
|
|
33
|
+
name = expect(/[\w.]+/)
|
|
34
|
+
type = :splat if (type == :named_splat) && (name == 'splat')
|
|
35
|
+
expect('}')
|
|
36
|
+
node(type, name)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
suffix('?') do |_c, element|
|
|
40
|
+
node(:optional, element)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
data/lib/grape/router/pattern.rb
CHANGED
|
@@ -1,62 +1,58 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'forwardable'
|
|
4
|
-
require 'mustermann/grape'
|
|
5
|
-
require 'grape/util/cache'
|
|
6
|
-
|
|
7
3
|
module Grape
|
|
8
4
|
class Router
|
|
9
5
|
class Pattern
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
extend Forwardable
|
|
7
|
+
|
|
8
|
+
DEFAULT_CAPTURES = %w[format version].freeze
|
|
12
9
|
|
|
13
10
|
attr_reader :origin, :path, :pattern, :to_regexp
|
|
14
11
|
|
|
15
|
-
|
|
16
|
-
def_delegators :pattern, :named_captures, :params
|
|
12
|
+
def_delegators :pattern, :params
|
|
17
13
|
def_delegators :to_regexp, :===
|
|
18
14
|
alias match? ===
|
|
19
15
|
|
|
20
|
-
def initialize(
|
|
21
|
-
@origin
|
|
22
|
-
@path
|
|
23
|
-
@pattern =
|
|
16
|
+
def initialize(origin:, suffix:, anchor:, params:, format:, version:, requirements:)
|
|
17
|
+
@origin = origin
|
|
18
|
+
@path = PatternCache[[build_path_from_pattern(@origin, anchor), suffix]]
|
|
19
|
+
@pattern = MustermannPattern.new(@path, uri_decode: true, params:, capture: extract_capture(format, version, requirements))
|
|
24
20
|
@to_regexp = @pattern.to_regexp
|
|
25
21
|
end
|
|
26
22
|
|
|
23
|
+
def captures_default
|
|
24
|
+
to_regexp.names
|
|
25
|
+
.delete_if { |n| DEFAULT_CAPTURES.include?(n) }
|
|
26
|
+
.to_h { |k| [k, ''] }
|
|
27
|
+
end
|
|
28
|
+
|
|
27
29
|
private
|
|
28
30
|
|
|
29
|
-
def
|
|
30
|
-
capture =
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
options
|
|
34
|
-
end
|
|
31
|
+
def extract_capture(format, version, requirements)
|
|
32
|
+
capture = {}
|
|
33
|
+
capture[:format] = map_str(format) if format.present?
|
|
34
|
+
capture[:version] = map_str(version) if version.present?
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
unless anchor || pattern.end_with?('*path')
|
|
38
|
-
pattern = +pattern
|
|
39
|
-
pattern << '/' unless pattern.end_with?('/')
|
|
40
|
-
pattern << '*path'
|
|
41
|
-
end
|
|
36
|
+
return capture if requirements.blank?
|
|
42
37
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
end.join('/') if pattern.end_with?('*path')
|
|
38
|
+
requirements.merge(capture)
|
|
39
|
+
end
|
|
46
40
|
|
|
47
|
-
|
|
41
|
+
def build_path_from_pattern(pattern, anchor)
|
|
42
|
+
return pattern.dup.insert(pattern.rindex('/') + 1, '?') if pattern.end_with?('*path')
|
|
43
|
+
return pattern if anchor
|
|
44
|
+
return "#{pattern}?*path" if pattern.end_with?('/')
|
|
45
|
+
|
|
46
|
+
"#{pattern}/?*path"
|
|
48
47
|
end
|
|
49
48
|
|
|
50
|
-
def
|
|
51
|
-
|
|
52
|
-
DEFAULT_SUPPORTED_CAPTURE.each_with_object(requirements) do |field, capture|
|
|
53
|
-
option = Array(options[field])
|
|
54
|
-
capture[field] = option.map(&:to_s) if option.present?
|
|
55
|
-
end
|
|
49
|
+
def map_str(value)
|
|
50
|
+
Array.wrap(value).map(&:to_s)
|
|
56
51
|
end
|
|
57
52
|
|
|
58
53
|
class PatternCache < Grape::Util::Cache
|
|
59
54
|
def initialize
|
|
55
|
+
super
|
|
60
56
|
@cache = Hash.new do |h, (pattern, suffix)|
|
|
61
57
|
h[[pattern, suffix]] = -"#{pattern}#{suffix}"
|
|
62
58
|
end
|
data/lib/grape/router/route.rb
CHANGED
|
@@ -1,61 +1,26 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'grape/router/pattern'
|
|
4
|
-
require 'grape/router/attribute_translator'
|
|
5
|
-
require 'forwardable'
|
|
6
|
-
require 'pathname'
|
|
7
|
-
|
|
8
3
|
module Grape
|
|
9
4
|
class Router
|
|
10
|
-
class Route
|
|
11
|
-
ROUTE_ATTRIBUTE_REGEXP = /route_([_a-zA-Z]\w*)/.freeze
|
|
12
|
-
SOURCE_LOCATION_REGEXP = /^(.*?):(\d+?)(?::in `.+?')?$/.freeze
|
|
13
|
-
FIXED_NAMED_CAPTURES = %w[format version].freeze
|
|
14
|
-
|
|
15
|
-
attr_accessor :pattern, :translator, :app, :index, :options
|
|
16
|
-
|
|
17
|
-
alias attributes translator
|
|
18
|
-
|
|
5
|
+
class Route < BaseRoute
|
|
19
6
|
extend Forwardable
|
|
20
|
-
def_delegators :pattern, :path, :origin
|
|
21
|
-
delegate Grape::Router::AttributeTranslator::ROUTE_ATTRIBUTES => :attributes
|
|
22
7
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if match
|
|
26
|
-
method_name = match.captures.last.to_sym
|
|
27
|
-
warn_route_methods(method_name, caller(1).shift)
|
|
28
|
-
@options[method_name]
|
|
29
|
-
else
|
|
30
|
-
super
|
|
31
|
-
end
|
|
32
|
-
end
|
|
8
|
+
FORWARD_MATCH_METHOD = ->(input, pattern) { input.start_with?(pattern.origin) }
|
|
9
|
+
NON_FORWARD_MATCH_METHOD = ->(input, pattern) { pattern.match?(input) }
|
|
33
10
|
|
|
34
|
-
|
|
35
|
-
ROUTE_ATTRIBUTE_REGEXP.match?(method_id.to_s)
|
|
36
|
-
end
|
|
11
|
+
attr_reader :app, :request_method, :index
|
|
37
12
|
|
|
38
|
-
|
|
39
|
-
warn_route_methods(:method, caller(1).shift, :request_method)
|
|
40
|
-
request_method
|
|
41
|
-
end
|
|
13
|
+
def_delegators :@app, :call
|
|
42
14
|
|
|
43
|
-
def
|
|
44
|
-
|
|
45
|
-
|
|
15
|
+
def initialize(endpoint, method, pattern, options)
|
|
16
|
+
super(pattern, options)
|
|
17
|
+
@app = endpoint
|
|
18
|
+
@request_method = upcase_method(method)
|
|
19
|
+
@match_function = options[:forward_match] ? FORWARD_MATCH_METHOD : NON_FORWARD_MATCH_METHOD
|
|
46
20
|
end
|
|
47
21
|
|
|
48
|
-
def
|
|
49
|
-
|
|
50
|
-
method_upcase = Grape::Http::Headers.find_supported_method(method_s) || method_s.upcase
|
|
51
|
-
|
|
52
|
-
@options = options.merge(method: method_upcase)
|
|
53
|
-
@pattern = Pattern.new(pattern, **options)
|
|
54
|
-
@translator = AttributeTranslator.new(**options, request_method: method_upcase)
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def exec(env)
|
|
58
|
-
@app.call(env)
|
|
22
|
+
def convert_to_head_request!
|
|
23
|
+
@request_method = Rack::HEAD
|
|
59
24
|
end
|
|
60
25
|
|
|
61
26
|
def apply(app)
|
|
@@ -64,27 +29,29 @@ module Grape
|
|
|
64
29
|
end
|
|
65
30
|
|
|
66
31
|
def match?(input)
|
|
67
|
-
|
|
32
|
+
return false if input.blank?
|
|
33
|
+
|
|
34
|
+
@match_function.call(input, pattern)
|
|
68
35
|
end
|
|
69
36
|
|
|
70
37
|
def params(input = nil)
|
|
71
|
-
if input.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
parsed ? parsed.delete_if { |_, value| value.nil? }.symbolize_keys : {}
|
|
78
|
-
end
|
|
38
|
+
return params_without_input if input.blank?
|
|
39
|
+
|
|
40
|
+
parsed = pattern.params(input)
|
|
41
|
+
return unless parsed
|
|
42
|
+
|
|
43
|
+
parsed.compact.symbolize_keys
|
|
79
44
|
end
|
|
80
45
|
|
|
81
46
|
private
|
|
82
47
|
|
|
83
|
-
def
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
48
|
+
def params_without_input
|
|
49
|
+
@params_without_input ||= pattern.captures_default.merge(options[:params])
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def upcase_method(method)
|
|
53
|
+
method_s = method.to_s
|
|
54
|
+
Grape::HTTP_SUPPORTED_METHODS.detect { |m| m.casecmp(method_s).zero? } || method_s.upcase
|
|
88
55
|
end
|
|
89
56
|
end
|
|
90
57
|
end
|
data/lib/grape/router.rb
CHANGED
|
@@ -1,60 +1,57 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'grape/router/route'
|
|
4
|
-
require 'grape/util/cache'
|
|
5
|
-
|
|
6
3
|
module Grape
|
|
7
4
|
class Router
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
# @deprecated Use {Grape::Util::PathNormalizer.call} instead.
|
|
10
6
|
def self.normalize_path(path)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
path
|
|
15
|
-
path
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def self.supported_methods
|
|
19
|
-
@supported_methods ||= Grape::Http::Headers::SUPPORTED_METHODS + ['*']
|
|
7
|
+
Grape.deprecator.warn(
|
|
8
|
+
'`Grape::Router.normalize_path` is deprecated. Use `Grape::Util::PathNormalizer.call` instead.'
|
|
9
|
+
)
|
|
10
|
+
Grape::Util::PathNormalizer.call(path)
|
|
20
11
|
end
|
|
21
12
|
|
|
22
13
|
def initialize
|
|
23
14
|
@neutral_map = []
|
|
24
15
|
@neutral_regexes = []
|
|
25
|
-
|
|
26
|
-
|
|
16
|
+
# Plain hashes with no auto-vivifying default: a lookup for an HTTP method
|
|
17
|
+
# that has no routes must not insert a key. `compile!` freezes both maps,
|
|
18
|
+
# so request-time reads never mutate shared state (see #match? / #rotation).
|
|
19
|
+
@map = {}
|
|
20
|
+
@optimized_map = {}
|
|
27
21
|
end
|
|
28
22
|
|
|
29
23
|
def compile!
|
|
30
|
-
return if compiled
|
|
24
|
+
return if @compiled
|
|
31
25
|
|
|
32
26
|
@union = Regexp.union(@neutral_regexes)
|
|
33
27
|
@neutral_regexes = nil
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
@optimized_map[method] = Regexp.union(@optimized_map[method])
|
|
28
|
+
(Grape::HTTP_SUPPORTED_METHODS + ['*']).each do |method|
|
|
29
|
+
next unless @map.key?(method)
|
|
30
|
+
|
|
31
|
+
routes = @map[method]
|
|
32
|
+
optimized_map = routes.map.with_index { |route, index| route.to_regexp(index) }
|
|
33
|
+
@optimized_map[method] = Regexp.union(optimized_map)
|
|
41
34
|
end
|
|
35
|
+
@map.freeze
|
|
36
|
+
@optimized_map.freeze
|
|
42
37
|
@compiled = true
|
|
43
38
|
end
|
|
44
39
|
|
|
45
40
|
def append(route)
|
|
46
|
-
map[route.request_method] << route
|
|
41
|
+
(@map[route.request_method] ||= []) << route
|
|
47
42
|
end
|
|
48
43
|
|
|
49
|
-
def associate_routes(
|
|
50
|
-
@neutral_regexes <<
|
|
51
|
-
@neutral_map <<
|
|
44
|
+
def associate_routes(greedy_route)
|
|
45
|
+
@neutral_regexes << greedy_route.to_regexp(@neutral_map.length)
|
|
46
|
+
@neutral_map << greedy_route
|
|
52
47
|
end
|
|
53
48
|
|
|
54
49
|
def call(env)
|
|
55
50
|
with_optimization do
|
|
56
|
-
|
|
57
|
-
|
|
51
|
+
input = Grape::Util::PathNormalizer.call(env[Rack::PATH_INFO])
|
|
52
|
+
method = env[Rack::REQUEST_METHOD]
|
|
53
|
+
response, route = identity(input, method, env)
|
|
54
|
+
response || rotation(input, method, env, route)
|
|
58
55
|
end
|
|
59
56
|
end
|
|
60
57
|
|
|
@@ -65,113 +62,101 @@ module Grape
|
|
|
65
62
|
any.endpoint
|
|
66
63
|
end
|
|
67
64
|
|
|
65
|
+
DEFAULT_RESPONSE_HEADERS = Grape::Util::Header.new.merge('X-Cascade' => 'pass').freeze
|
|
66
|
+
DEFAULT_RESPONSE_BODY = ['404 Not Found'].freeze
|
|
67
|
+
|
|
68
68
|
private
|
|
69
69
|
|
|
70
|
-
def identity(env)
|
|
70
|
+
def identity(input, method, env)
|
|
71
71
|
route = nil
|
|
72
|
-
response = transaction(env) do
|
|
72
|
+
response = transaction(input, method, env) do
|
|
73
73
|
route = match?(input, method)
|
|
74
|
-
process_route(route, env) if route
|
|
74
|
+
process_route(route, input, env) if route
|
|
75
75
|
end
|
|
76
76
|
[response, route]
|
|
77
77
|
end
|
|
78
78
|
|
|
79
|
-
def rotation(env, exact_route
|
|
79
|
+
def rotation(input, method, env, exact_route)
|
|
80
80
|
response = nil
|
|
81
|
-
|
|
82
|
-
map[method].each do |route|
|
|
81
|
+
@map[method]&.each do |route|
|
|
83
82
|
next if exact_route == route
|
|
84
83
|
next unless route.match?(input)
|
|
85
84
|
|
|
86
|
-
response = process_route(route, env)
|
|
85
|
+
response = process_route(route, input, env)
|
|
87
86
|
break unless cascade?(response)
|
|
88
87
|
end
|
|
89
88
|
response
|
|
90
89
|
end
|
|
91
90
|
|
|
92
|
-
def transaction(env)
|
|
93
|
-
|
|
94
|
-
response
|
|
95
|
-
|
|
96
|
-
return response if response && !(cascade = cascade?(response))
|
|
91
|
+
def transaction(input, method, env)
|
|
92
|
+
response = yield
|
|
93
|
+
return response if halt?(response)
|
|
97
94
|
|
|
95
|
+
last_response_cascade = !response.nil?
|
|
98
96
|
last_neighbor_route = greedy_match?(input)
|
|
99
97
|
|
|
100
98
|
# If last_neighbor_route exists and request method is OPTIONS,
|
|
101
|
-
# return response by using #
|
|
102
|
-
return
|
|
99
|
+
# return response by using #include_allow_header.
|
|
100
|
+
return process_route(last_neighbor_route, input, env, include_allow_header: true) if !last_response_cascade && method == Rack::OPTIONS && last_neighbor_route
|
|
103
101
|
|
|
104
102
|
route = match?(input, '*')
|
|
105
103
|
|
|
106
|
-
return last_neighbor_route.
|
|
104
|
+
return last_neighbor_route.call(env) if last_neighbor_route && last_response_cascade && route
|
|
107
105
|
|
|
108
106
|
if route
|
|
109
|
-
|
|
110
|
-
return
|
|
107
|
+
route_response = process_route(route, input, env)
|
|
108
|
+
return route_response if halt?(route_response)
|
|
109
|
+
|
|
110
|
+
last_response_cascade = !route_response.nil?
|
|
111
111
|
end
|
|
112
112
|
|
|
113
|
-
return
|
|
113
|
+
return process_route(last_neighbor_route, input, env, include_allow_header: true) if !last_response_cascade && last_neighbor_route
|
|
114
114
|
|
|
115
115
|
nil
|
|
116
116
|
end
|
|
117
117
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
118
|
+
# Returns true if `response` should be returned as-is from the enclosing
|
|
119
|
+
# transaction. Closes the body as a side effect when the response is
|
|
120
|
+
# cascading so callers can safely try the next match.
|
|
121
|
+
def halt?(response)
|
|
122
|
+
return false unless response
|
|
122
123
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
cascade = cascade?(response)
|
|
125
|
+
response[2].close if cascade && response[2].respond_to?(:close)
|
|
126
|
+
!cascade
|
|
126
127
|
end
|
|
127
128
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
129
|
+
# Routing args are rebuilt for every attempt: when a route cascades
|
|
130
|
+
# (X-Cascade pass), the next candidate must not observe the previous
|
|
131
|
+
# attempt's +route_info+ or path captures.
|
|
132
|
+
def process_route(route, input, env, include_allow_header: false)
|
|
133
|
+
route_params = route.params(input)
|
|
134
|
+
routing_args = { route_info: route }
|
|
135
|
+
routing_args.merge!(route_params) if route_params.present?
|
|
136
|
+
env[Grape::Env::GRAPE_ROUTING_ARGS] = routing_args
|
|
137
|
+
env[Grape::Env::GRAPE_ALLOWED_METHODS] = route.allow_header if include_allow_header
|
|
138
|
+
route.call(env)
|
|
132
139
|
end
|
|
133
140
|
|
|
134
141
|
def with_optimization
|
|
135
|
-
compile!
|
|
142
|
+
compile!
|
|
136
143
|
yield || default_response
|
|
137
144
|
end
|
|
138
145
|
|
|
139
146
|
def default_response
|
|
140
|
-
[404,
|
|
147
|
+
[404, DEFAULT_RESPONSE_HEADERS.dup, DEFAULT_RESPONSE_BODY.dup]
|
|
141
148
|
end
|
|
142
149
|
|
|
143
150
|
def match?(input, method)
|
|
144
|
-
|
|
145
|
-
return unless current_regexp.match(input)
|
|
146
|
-
|
|
147
|
-
last_match = Regexp.last_match
|
|
148
|
-
@map[method].detect { |route| last_match["_#{route.index}"] }
|
|
151
|
+
@optimized_map[method]&.match(input) { |m| @map[method].detect { |route| m[route.regexp_capture_index] } }
|
|
149
152
|
end
|
|
150
153
|
|
|
151
154
|
def greedy_match?(input)
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
last_match = Regexp.last_match
|
|
155
|
-
@neutral_map.detect { |route| last_match["_#{route.index}"] }
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
def call_with_allow_headers(env, route)
|
|
159
|
-
prepare_env_from_route(env, route)
|
|
160
|
-
env[Grape::Env::GRAPE_ALLOWED_METHODS] = route.allow_header.join(', ').freeze
|
|
161
|
-
route.endpoint.call(env)
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
def prepare_env_from_route(env, route)
|
|
165
|
-
input, = *extract_input_and_method(env)
|
|
166
|
-
env[Grape::Env::GRAPE_ROUTING_ARGS] = make_routing_args(env[Grape::Env::GRAPE_ROUTING_ARGS], route, input)
|
|
155
|
+
@union.match(input) { |m| @neutral_map.detect { |route| m[route.regexp_capture_index] } }
|
|
167
156
|
end
|
|
168
157
|
|
|
169
158
|
def cascade?(response)
|
|
170
|
-
response && response[1][
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
def string_for(input)
|
|
174
|
-
self.class.normalize_path(input)
|
|
159
|
+
response && response[1]['X-Cascade'] == 'pass'
|
|
175
160
|
end
|
|
176
161
|
end
|
|
177
162
|
end
|
|
@@ -6,11 +6,9 @@ module Grape
|
|
|
6
6
|
# for using Rack::SendFile middleware
|
|
7
7
|
class SendfileResponse < Rack::Response
|
|
8
8
|
def respond_to?(method_name, include_all = false)
|
|
9
|
-
if method_name == :to_path
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
super
|
|
13
|
-
end
|
|
9
|
+
return @body.respond_to?(:to_path, include_all) if method_name == :to_path
|
|
10
|
+
|
|
11
|
+
super
|
|
14
12
|
end
|
|
15
13
|
|
|
16
14
|
def to_path
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Testing
|
|
5
|
+
module RunBeforeEach
|
|
6
|
+
def run
|
|
7
|
+
self.class.run_before_each(self)
|
|
8
|
+
super
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module ClassMethods
|
|
13
|
+
def before_each(&block)
|
|
14
|
+
raise ArgumentError, 'a block is required' unless block
|
|
15
|
+
|
|
16
|
+
@before_each ||= []
|
|
17
|
+
@before_each << block
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def reset_before_each
|
|
21
|
+
@before_each&.clear
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def run_before_each(endpoint)
|
|
25
|
+
superclass.run_before_each(endpoint) unless self == Grape::Endpoint
|
|
26
|
+
@before_each&.each { |blk| blk.call(endpoint) }
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
Grape::Endpoint.prepend(RunBeforeEach)
|
|
31
|
+
Grape::Endpoint.extend(ClassMethods)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Util
|
|
5
|
+
class ApiDescription
|
|
6
|
+
DSL_METHODS = %i[
|
|
7
|
+
body_name
|
|
8
|
+
consumes
|
|
9
|
+
default
|
|
10
|
+
deprecated
|
|
11
|
+
detail
|
|
12
|
+
entity
|
|
13
|
+
headers
|
|
14
|
+
hidden
|
|
15
|
+
http_codes
|
|
16
|
+
is_array
|
|
17
|
+
named
|
|
18
|
+
nickname
|
|
19
|
+
params
|
|
20
|
+
produces
|
|
21
|
+
security
|
|
22
|
+
summary
|
|
23
|
+
tags
|
|
24
|
+
].freeze
|
|
25
|
+
|
|
26
|
+
def initialize(description, endpoint_configuration, &)
|
|
27
|
+
@endpoint_configuration = endpoint_configuration
|
|
28
|
+
@attributes = { description: }
|
|
29
|
+
instance_eval(&)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
DSL_METHODS.each do |attribute|
|
|
33
|
+
define_method attribute do |value|
|
|
34
|
+
@attributes[attribute] = value
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
alias success entity
|
|
39
|
+
alias failure http_codes
|
|
40
|
+
|
|
41
|
+
def configuration
|
|
42
|
+
@configuration ||= eval_endpoint_config(@endpoint_configuration)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def settings
|
|
46
|
+
@attributes
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def eval_endpoint_config(configuration)
|
|
52
|
+
return configuration if configuration.is_a?(Hash)
|
|
53
|
+
|
|
54
|
+
configuration.evaluate
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|