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
data/lib/grape/parser.rb
CHANGED
|
@@ -2,32 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
module Grape
|
|
4
4
|
module Parser
|
|
5
|
-
extend Util::
|
|
5
|
+
extend Grape::Util::Registry
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
def builtin_parsers
|
|
9
|
-
@builtin_parsers ||= {
|
|
10
|
-
json: Grape::Parser::Json,
|
|
11
|
-
jsonapi: Grape::Parser::Json,
|
|
12
|
-
xml: Grape::Parser::Xml
|
|
13
|
-
}
|
|
14
|
-
end
|
|
7
|
+
module_function
|
|
15
8
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
end
|
|
9
|
+
def parser_for(format, parsers = nil)
|
|
10
|
+
return parsers[format] if parsers&.key?(format)
|
|
19
11
|
|
|
20
|
-
|
|
21
|
-
spec = parsers(**options)[api_format]
|
|
22
|
-
case spec
|
|
23
|
-
when nil
|
|
24
|
-
nil
|
|
25
|
-
when Symbol
|
|
26
|
-
method(spec)
|
|
27
|
-
else
|
|
28
|
-
spec
|
|
29
|
-
end
|
|
30
|
-
end
|
|
12
|
+
registry[format]
|
|
31
13
|
end
|
|
32
14
|
end
|
|
33
15
|
end
|
data/lib/grape/path.rb
CHANGED
|
@@ -1,98 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'grape/util/cache'
|
|
4
|
-
|
|
5
3
|
module Grape
|
|
6
|
-
#
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def initialize(raw_path, namespace, settings)
|
|
15
|
-
@raw_path = raw_path
|
|
16
|
-
@namespace = namespace
|
|
17
|
-
@settings = settings
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def mount_path
|
|
21
|
-
settings[:mount_path]
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def root_prefix
|
|
25
|
-
split_setting(:root_prefix)
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def uses_specific_format?
|
|
29
|
-
if settings.key?(:format) && settings.key?(:content_types)
|
|
30
|
-
(settings[:format] && Array(settings[:content_types]).size == 1)
|
|
31
|
-
else
|
|
32
|
-
false
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def uses_path_versioning?
|
|
37
|
-
if settings.key?(:version) && settings[:version_options] && settings[:version_options].key?(:using)
|
|
38
|
-
(settings[:version] && settings[:version_options][:using] == :path)
|
|
39
|
-
else
|
|
40
|
-
false
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def namespace?
|
|
45
|
-
namespace&.match?(/^\S/) && namespace != '/'
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def path?
|
|
49
|
-
raw_path&.match?(/^\S/) && raw_path != '/'
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def suffix
|
|
53
|
-
if uses_specific_format?
|
|
54
|
-
"(.#{settings[:format]})"
|
|
55
|
-
elsif !uses_path_versioning? || (namespace? || path?)
|
|
56
|
-
'(.:format)'
|
|
57
|
-
else
|
|
58
|
-
'(/.:format)'
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def path
|
|
63
|
-
Grape::Router.normalize_path(PartsCache[parts])
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def path_with_suffix
|
|
67
|
-
"#{path}#{suffix}"
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def to_s
|
|
71
|
-
path_with_suffix
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
private
|
|
75
|
-
|
|
76
|
-
class PartsCache < Grape::Util::Cache
|
|
77
|
-
def initialize
|
|
78
|
-
@cache = Hash.new do |h, parts|
|
|
79
|
-
h[parts] = -parts.join('/')
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def parts
|
|
85
|
-
parts = [mount_path, root_prefix].compact
|
|
86
|
-
parts << ':version' if uses_path_versioning?
|
|
87
|
-
parts << namespace.to_s
|
|
88
|
-
parts << raw_path.to_s
|
|
89
|
-
parts.flatten.reject { |part| part == '/' }
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
def split_setting(key)
|
|
93
|
-
return if settings[key].nil?
|
|
94
|
-
|
|
95
|
-
settings[key].to_s.split('/')
|
|
96
|
-
end
|
|
97
|
-
end
|
|
4
|
+
# @deprecated +Grape::Path+ moved to {Grape::Router::Pattern::Path}, since it
|
|
5
|
+
# is a router-internal detail that only exists to build a
|
|
6
|
+
# {Grape::Router::Pattern}. Reference the new constant instead.
|
|
7
|
+
Path = ActiveSupport::Deprecation::DeprecatedConstantProxy.new(
|
|
8
|
+
'Grape::Path',
|
|
9
|
+
'Grape::Router::Pattern::Path',
|
|
10
|
+
Grape.deprecator
|
|
11
|
+
)
|
|
98
12
|
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
# Marks a body as JSON that has already been rendered, so the JSON formatters
|
|
5
|
+
# serve it verbatim instead of encoding it a second time.
|
|
6
|
+
#
|
|
7
|
+
# +Grape::Formatter::Json+ calls +to_json+ on whatever the endpoint returned, so a
|
|
8
|
+
# String holding pre-rendered JSON — a blob cached in Redis, a +json_agg+ column read
|
|
9
|
+
# straight from Postgres, output from another serializer — comes back encoded twice:
|
|
10
|
+
#
|
|
11
|
+
# '{"a":1}' => "\"{\\\"a\\\":1}\""
|
|
12
|
+
#
|
|
13
|
+
# Wrapping the body opts that one response out of encoding:
|
|
14
|
+
#
|
|
15
|
+
# get '/cached' do
|
|
16
|
+
# body Grape::PrecompiledJson.new(Rails.cache.read('payload'))
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# An Array is joined into a JSON array without its members being parsed, which is
|
|
20
|
+
# what makes a cached collection cheap — N rendered blobs are spliced together
|
|
21
|
+
# rather than round-tripped:
|
|
22
|
+
#
|
|
23
|
+
# Grape::PrecompiledJson.new(['{"id":1}', '{"id":2}']).to_s # => '[{"id":1},{"id":2}]'
|
|
24
|
+
#
|
|
25
|
+
# +Array#join+ calls +to_s+ on each member, so members may themselves be
|
|
26
|
+
# +PrecompiledJson+ instances.
|
|
27
|
+
#
|
|
28
|
+
# Wrap only the whole body. A wrapper nested inside a Hash or Array that is then
|
|
29
|
+
# handed to an encoder is serialized as an ordinary object — +ActiveSupport::JSON+
|
|
30
|
+
# renders it through +as_json+ as +{"value":"{\"a\":1}"}+ — because no encoder knows
|
|
31
|
+
# to unwrap it. Nothing checks that the String actually holds JSON; that is the
|
|
32
|
+
# caller's responsibility.
|
|
33
|
+
class PrecompiledJson
|
|
34
|
+
# @param value [String, Array<String>] pre-rendered JSON
|
|
35
|
+
def initialize(value)
|
|
36
|
+
@value = value
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @return [String] the JSON to serve
|
|
40
|
+
# @raise [Grape::Exceptions::InvalidFormatter] if the value is neither a String
|
|
41
|
+
# nor an Array; the formatter middleware turns this into a 500 rather than
|
|
42
|
+
# letting a body Rack cannot serve reach the SPEC check.
|
|
43
|
+
def to_s
|
|
44
|
+
return @value if @value.is_a?(String)
|
|
45
|
+
return "[#{@value.join(',')}]" if @value.is_a?(Array)
|
|
46
|
+
|
|
47
|
+
raise Grape::Exceptions::InvalidFormatter.new(@value.class, 'json')
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
data/lib/grape/request.rb
CHANGED
|
@@ -1,53 +1,227 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'grape/util/lazy_object'
|
|
4
|
-
|
|
5
3
|
module Grape
|
|
6
4
|
class Request < Rack::Request
|
|
7
|
-
|
|
5
|
+
# Based on rack 3 KNOWN_HEADERS
|
|
6
|
+
# https://github.com/rack/rack/blob/4f15e7b814922af79605be4b02c5b7c3044ba206/lib/rack/headers.rb#L10
|
|
7
|
+
|
|
8
|
+
KNOWN_HEADERS = %w[
|
|
9
|
+
Accept
|
|
10
|
+
Accept-CH
|
|
11
|
+
Accept-Encoding
|
|
12
|
+
Accept-Language
|
|
13
|
+
Accept-Patch
|
|
14
|
+
Accept-Ranges
|
|
15
|
+
Accept-Version
|
|
16
|
+
Access-Control-Allow-Credentials
|
|
17
|
+
Access-Control-Allow-Headers
|
|
18
|
+
Access-Control-Allow-Methods
|
|
19
|
+
Access-Control-Allow-Origin
|
|
20
|
+
Access-Control-Expose-Headers
|
|
21
|
+
Access-Control-Max-Age
|
|
22
|
+
Age
|
|
23
|
+
Allow
|
|
24
|
+
Alt-Svc
|
|
25
|
+
Authorization
|
|
26
|
+
Cache-Control
|
|
27
|
+
Client-Ip
|
|
28
|
+
Connection
|
|
29
|
+
Content-Disposition
|
|
30
|
+
Content-Encoding
|
|
31
|
+
Content-Language
|
|
32
|
+
Content-Length
|
|
33
|
+
Content-Location
|
|
34
|
+
Content-MD5
|
|
35
|
+
Content-Range
|
|
36
|
+
Content-Security-Policy
|
|
37
|
+
Content-Security-Policy-Report-Only
|
|
38
|
+
Content-Type
|
|
39
|
+
Cookie
|
|
40
|
+
Date
|
|
41
|
+
Delta-Base
|
|
42
|
+
Dnt
|
|
43
|
+
ETag
|
|
44
|
+
Expect-CT
|
|
45
|
+
Expires
|
|
46
|
+
Feature-Policy
|
|
47
|
+
Forwarded
|
|
48
|
+
Host
|
|
49
|
+
If-Modified-Since
|
|
50
|
+
If-None-Match
|
|
51
|
+
IM
|
|
52
|
+
Last-Modified
|
|
53
|
+
Link
|
|
54
|
+
Location
|
|
55
|
+
NEL
|
|
56
|
+
P3P
|
|
57
|
+
Permissions-Policy
|
|
58
|
+
Pragma
|
|
59
|
+
Preference-Applied
|
|
60
|
+
Proxy-Authenticate
|
|
61
|
+
Public-Key-Pins
|
|
62
|
+
Range
|
|
63
|
+
Referer
|
|
64
|
+
Referrer-Policy
|
|
65
|
+
Refresh
|
|
66
|
+
Report-To
|
|
67
|
+
Retry-After
|
|
68
|
+
Sec-Fetch-Dest
|
|
69
|
+
Sec-Fetch-Mode
|
|
70
|
+
Sec-Fetch-Site
|
|
71
|
+
Sec-Fetch-User
|
|
72
|
+
Server
|
|
73
|
+
Set-Cookie
|
|
74
|
+
Status
|
|
75
|
+
Strict-Transport-Security
|
|
76
|
+
Timing-Allow-Origin
|
|
77
|
+
Tk
|
|
78
|
+
Trailer
|
|
79
|
+
Transfer-Encoding
|
|
80
|
+
Upgrade
|
|
81
|
+
Upgrade-Insecure-Requests
|
|
82
|
+
User-Agent
|
|
83
|
+
Vary
|
|
84
|
+
Version
|
|
85
|
+
Via
|
|
86
|
+
Warning
|
|
87
|
+
WWW-Authenticate
|
|
88
|
+
X-Accel-Buffering
|
|
89
|
+
X-Accel-Charset
|
|
90
|
+
X-Accel-Expires
|
|
91
|
+
X-Accel-Limit-Rate
|
|
92
|
+
X-Accel-Mapping
|
|
93
|
+
X-Accel-Redirect
|
|
94
|
+
X-Access-Token
|
|
95
|
+
X-Auth-Request-Access-Token
|
|
96
|
+
X-Auth-Request-Email
|
|
97
|
+
X-Auth-Request-Groups
|
|
98
|
+
X-Auth-Request-Preferred-Username
|
|
99
|
+
X-Auth-Request-Redirect
|
|
100
|
+
X-Auth-Request-Token
|
|
101
|
+
X-Auth-Request-User
|
|
102
|
+
X-Cascade
|
|
103
|
+
X-Client-Ip
|
|
104
|
+
X-Content-Duration
|
|
105
|
+
X-Content-Security-Policy
|
|
106
|
+
X-Content-Type-Options
|
|
107
|
+
X-Correlation-Id
|
|
108
|
+
X-Download-Options
|
|
109
|
+
X-Forwarded-Access-Token
|
|
110
|
+
X-Forwarded-Email
|
|
111
|
+
X-Forwarded-For
|
|
112
|
+
X-Forwarded-Groups
|
|
113
|
+
X-Forwarded-Host
|
|
114
|
+
X-Forwarded-Port
|
|
115
|
+
X-Forwarded-Preferred-Username
|
|
116
|
+
X-Forwarded-Proto
|
|
117
|
+
X-Forwarded-Scheme
|
|
118
|
+
X-Forwarded-Ssl
|
|
119
|
+
X-Forwarded-Uri
|
|
120
|
+
X-Forwarded-User
|
|
121
|
+
X-Frame-Options
|
|
122
|
+
X-HTTP-Method-Override
|
|
123
|
+
X-Permitted-Cross-Domain-Policies
|
|
124
|
+
X-Powered-By
|
|
125
|
+
X-Real-IP
|
|
126
|
+
X-Redirect-By
|
|
127
|
+
X-Request-Id
|
|
128
|
+
X-Requested-With
|
|
129
|
+
X-Runtime
|
|
130
|
+
X-Sendfile
|
|
131
|
+
X-Sendfile-Type
|
|
132
|
+
X-UA-Compatible
|
|
133
|
+
X-WebKit-CS
|
|
134
|
+
X-XSS-Protection
|
|
135
|
+
].to_h do |header|
|
|
136
|
+
["HTTP_#{header.upcase.tr('-', '_')}", header]
|
|
137
|
+
end.freeze
|
|
8
138
|
|
|
9
139
|
alias rack_params params
|
|
140
|
+
alias rack_cookies cookies
|
|
10
141
|
|
|
11
|
-
def initialize(env,
|
|
12
|
-
extend options[:build_params_with] || Grape.config.param_builder
|
|
142
|
+
def initialize(env, build_params_with: nil)
|
|
13
143
|
super(env)
|
|
144
|
+
@build_params_with = build_params_with
|
|
14
145
|
end
|
|
15
146
|
|
|
16
147
|
def params
|
|
17
|
-
@params ||=
|
|
18
|
-
rescue EOFError
|
|
19
|
-
raise Grape::Exceptions::EmptyMessageBody.new(content_type)
|
|
20
|
-
rescue Rack::Multipart::MultipartPartLimitError
|
|
21
|
-
raise Grape::Exceptions::TooManyMultipartFiles.new(Rack::Utils.multipart_part_limit)
|
|
148
|
+
@params ||= make_params
|
|
22
149
|
end
|
|
23
150
|
|
|
24
151
|
def headers
|
|
25
152
|
@headers ||= build_headers
|
|
26
153
|
end
|
|
27
154
|
|
|
155
|
+
def cookies
|
|
156
|
+
@cookies ||= Grape::Cookies.new(rack_cookies)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# True once the cookie jar has been materialized (a cookie was read or
|
|
160
|
+
# written this request). Lets the endpoint skip response-cookie flushing,
|
|
161
|
+
# and the Grape::Cookies allocation it triggers, when no cookie was
|
|
162
|
+
# touched on the request.
|
|
163
|
+
def cookies?
|
|
164
|
+
!@cookies.nil?
|
|
165
|
+
end
|
|
166
|
+
|
|
28
167
|
private
|
|
29
168
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
169
|
+
# Resolved on first use rather than in the constructor: a request that never
|
|
170
|
+
# reads +params+ -- an endpoint with no validations whose block does not ask
|
|
171
|
+
# for them -- pays neither the registry lookup nor the global config read,
|
|
172
|
+
# and every request builds a Grape::Request.
|
|
173
|
+
def params_builder
|
|
174
|
+
@params_builder ||= Grape::ParamsBuilder.params_builder_for(@build_params_with || Grape.config[:param_builder])
|
|
36
175
|
end
|
|
37
176
|
|
|
38
|
-
def
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
177
|
+
def make_params
|
|
178
|
+
params = params_builder.call(rack_params)
|
|
179
|
+
filtered = routing_args_as_params(env[Grape::Env::GRAPE_ROUTING_ARGS])
|
|
180
|
+
return params if filtered.blank?
|
|
42
181
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
end
|
|
182
|
+
params.deep_merge!(filtered)
|
|
183
|
+
rescue *Grape::RACK_ERRORS
|
|
184
|
+
raise Grape::Exceptions::RequestError
|
|
47
185
|
end
|
|
48
186
|
|
|
49
|
-
|
|
50
|
-
|
|
187
|
+
# The routing args carry two things that are not request params:
|
|
188
|
+
# +:route_info+, which is always Grape's own, and +:version+, which is only
|
|
189
|
+
# Grape's own when the API declared a version — that is captured as a path
|
|
190
|
+
# segment and exposed through +env['api.version']+ instead.
|
|
191
|
+
#
|
|
192
|
+
# An API that declares no version can legitimately name a param +:version+
|
|
193
|
+
# (`route_param :version`, `get '/:version'`), and that capture belongs to
|
|
194
|
+
# the application. Dropping it unconditionally left `params[:version]` nil
|
|
195
|
+
# on a route that had matched, losing the segment silently.
|
|
196
|
+
def routing_args_as_params(routing_args)
|
|
197
|
+
return if routing_args.nil?
|
|
198
|
+
return routing_args.except(:version, :route_info) if grape_owns_version?(routing_args)
|
|
199
|
+
|
|
200
|
+
routing_args.except(:route_info)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# A route reports a +version+ only when the API declared one, which is the
|
|
204
|
+
# case where the captured segment is Grape's rather than the application's.
|
|
205
|
+
def grape_owns_version?(routing_args)
|
|
206
|
+
return false unless routing_args.key?(:version)
|
|
207
|
+
|
|
208
|
+
route = routing_args[:route_info]
|
|
209
|
+
route.respond_to?(:version) && !route.version.nil?
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Uses a plain `each_header` block instead of `each_header.with_object`:
|
|
213
|
+
# `with_object` can only pass the block one value plus the memo, so the
|
|
214
|
+
# `k, v` pair would be boxed into a throwaway Array on every header. A
|
|
215
|
+
# two-arg block receives `k`/`v` directly and allocates nothing extra.
|
|
216
|
+
def build_headers
|
|
217
|
+
headers = Grape::Util::Header.new
|
|
218
|
+
each_header do |k, v|
|
|
219
|
+
next unless k.start_with? 'HTTP_'
|
|
220
|
+
|
|
221
|
+
transformed_header = KNOWN_HEADERS.fetch(k) { -k[5..].tr('_', '-').downcase }
|
|
222
|
+
headers[transformed_header] = v
|
|
223
|
+
end
|
|
224
|
+
headers
|
|
51
225
|
end
|
|
52
226
|
end
|
|
53
227
|
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
class Router
|
|
5
|
+
class BaseRoute
|
|
6
|
+
extend Forwardable
|
|
7
|
+
|
|
8
|
+
delegate_missing_to :@options
|
|
9
|
+
|
|
10
|
+
attr_reader :options, :pattern, :prefix, :settings, :namespace
|
|
11
|
+
|
|
12
|
+
# +version+, +anchor+ and +requirements+ shape the matcher, so they are
|
|
13
|
+
# read from the pattern rather than stored again on the route.
|
|
14
|
+
def_delegators :@pattern, :path, :origin, :version, :anchor, :requirements
|
|
15
|
+
def_delegators :@options, :description, *Grape::Util::ApiDescription::DSL_METHODS
|
|
16
|
+
|
|
17
|
+
def initialize(pattern, options = {}, namespace: nil, prefix: nil, settings: nil)
|
|
18
|
+
@pattern = pattern
|
|
19
|
+
@options = options.is_a?(ActiveSupport::OrderedOptions) ? options : ActiveSupport::OrderedOptions.new.update(options)
|
|
20
|
+
@namespace = namespace
|
|
21
|
+
@prefix = prefix
|
|
22
|
+
@settings = settings
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# +success+ and +failure+ are the +desc+ DSL's names for +entity+ and
|
|
26
|
+
# +http_codes+: the block form writes the canonical key, a keyword option
|
|
27
|
+
# keeps the name it was written with, so both spellings are read here.
|
|
28
|
+
# Without this they resolve through +delegate_missing_to+, which answers
|
|
29
|
+
# nil for whichever of the two keys the description did not use.
|
|
30
|
+
def success
|
|
31
|
+
@options[:entity] || @options[:success]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def failure
|
|
35
|
+
@options[:http_codes] || @options[:failure]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @deprecated Use {#default_response}, the name grape-swagger asks for.
|
|
39
|
+
# This one has to be written out rather than delegated like the rest:
|
|
40
|
+
# an ActiveSupport::OrderedOptions answers an unknown name with that
|
|
41
|
+
# key's value, but +default+ is not unknown to it — it is +Hash#default+,
|
|
42
|
+
# the Hash's own default value — so a delegator would report nil for
|
|
43
|
+
# every route.
|
|
44
|
+
def default
|
|
45
|
+
Grape.deprecator.warn('`Grape::Router::Route#default` is deprecated. Use `#default_response` instead.')
|
|
46
|
+
default_response
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Assigned eagerly in {#to_regexp} (router compilation) rather than
|
|
50
|
+
# memoized here: this reader is called from request-time route matching
|
|
51
|
+
# on instances shared across threads, so it must not write state.
|
|
52
|
+
attr_reader :regexp_capture_index
|
|
53
|
+
|
|
54
|
+
# The number of the group this route occupies in the union the router
|
|
55
|
+
# compiled it into. Only the union can say what it is, so it is written
|
|
56
|
+
# back by {Router#compile!} right after building one, and like
|
|
57
|
+
# +regexp_capture_index+ it is never assigned at request time.
|
|
58
|
+
attr_reader :regexp_capture_group
|
|
59
|
+
|
|
60
|
+
def pattern_regexp
|
|
61
|
+
@pattern.to_regexp
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def to_regexp(index)
|
|
65
|
+
@regexp_capture_index = CaptureIndexCache[index]
|
|
66
|
+
Regexp.new("(?<#{regexp_capture_index}>#{pattern_regexp})")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @api private
|
|
70
|
+
# @see #regexp_capture_group
|
|
71
|
+
def resolve_capture_group!(union_named_captures)
|
|
72
|
+
@regexp_capture_group = union_named_captures.fetch(regexp_capture_index).first
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
class CaptureIndexCache < Grape::Util::Cache
|
|
76
|
+
def initialize
|
|
77
|
+
super
|
|
78
|
+
@cache = Hash.new do |h, index|
|
|
79
|
+
h[index] = "_#{index}"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Act like a Grape::Router::Route but for greedy_match
|
|
4
|
+
# see @neutral_map
|
|
5
|
+
|
|
6
|
+
module Grape
|
|
7
|
+
class Router
|
|
8
|
+
class GreedyRoute < BaseRoute
|
|
9
|
+
extend Forwardable
|
|
10
|
+
|
|
11
|
+
def_delegators :@endpoint, :call
|
|
12
|
+
|
|
13
|
+
attr_reader :endpoint, :allow_header
|
|
14
|
+
|
|
15
|
+
def initialize(pattern, endpoint:, allow_header:)
|
|
16
|
+
super(pattern)
|
|
17
|
+
@endpoint = endpoint
|
|
18
|
+
@allow_header = allow_header
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def params
|
|
22
|
+
nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def params_for(_input)
|
|
26
|
+
nil
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -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
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
class Router
|
|
5
|
+
class Pattern
|
|
6
|
+
# Assembles the path template a {Pattern} compiles into a matcher. It turns
|
|
7
|
+
# a raw path plus the path-settings snapshot taken by
|
|
8
|
+
# {Grape::Util::InheritableSetting#path_settings} (mount paths, root
|
|
9
|
+
# prefix, path-versioning and format) into an +origin+ (the route prefix)
|
|
10
|
+
# and a +suffix+ (the format segment). {Pattern.build} is the entry point
|
|
11
|
+
# that wires this into pattern construction.
|
|
12
|
+
class Path
|
|
13
|
+
DEFAULT_FORMAT_SEGMENT = '(/.:format)'
|
|
14
|
+
NO_VERSIONING_WITH_VALID_PATH_FORMAT_SEGMENT = '(.:format)'
|
|
15
|
+
VERSION_SEGMENT = ':version'
|
|
16
|
+
|
|
17
|
+
attr_reader :origin, :suffix
|
|
18
|
+
|
|
19
|
+
def initialize(raw_path, raw_namespace, settings)
|
|
20
|
+
@origin = PartsCache[build_parts(raw_path, raw_namespace, settings)]
|
|
21
|
+
@suffix = build_suffix(raw_path, raw_namespace, settings)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def to_s
|
|
25
|
+
"#{origin}#{suffix}"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def build_suffix(raw_path, raw_namespace, settings)
|
|
31
|
+
return "(.#{settings.format})" if uses_specific_format?(settings)
|
|
32
|
+
return NO_VERSIONING_WITH_VALID_PATH_FORMAT_SEGMENT if !uses_path_versioning?(settings) || valid_part?(raw_namespace) || valid_part?(raw_path)
|
|
33
|
+
|
|
34
|
+
DEFAULT_FORMAT_SEGMENT
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def build_parts(raw_path, raw_namespace, settings)
|
|
38
|
+
parts = []
|
|
39
|
+
add_part(parts, settings.mount_path)
|
|
40
|
+
add_part(parts, settings.root_prefix)
|
|
41
|
+
parts << VERSION_SEGMENT if uses_path_versioning?(settings)
|
|
42
|
+
add_part(parts, raw_namespace)
|
|
43
|
+
add_part(parts, raw_path)
|
|
44
|
+
parts
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def add_part(parts, value)
|
|
48
|
+
parts << value if value && not_slash?(value)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def not_slash?(value)
|
|
52
|
+
value != '/'
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def uses_specific_format?(settings)
|
|
56
|
+
settings.format && Array(settings.content_types).size == 1
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def uses_path_versioning?(settings)
|
|
60
|
+
settings.version && settings.version_options&.using == :path
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def valid_part?(part)
|
|
64
|
+
part&.match?(/\A\S/) && not_slash?(part)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
class PartsCache < Grape::Util::Cache
|
|
68
|
+
def initialize
|
|
69
|
+
super
|
|
70
|
+
@cache = Hash.new do |h, parts|
|
|
71
|
+
h[parts] = Grape::Util::PathNormalizer.call(parts.join('/'))
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|