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
|
@@ -1,137 +1,229 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'grape/middleware/base'
|
|
4
|
-
require 'active_support/core_ext/string/output_safety'
|
|
5
|
-
|
|
6
3
|
module Grape
|
|
7
4
|
module Middleware
|
|
8
5
|
class Error < Base
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
6
|
+
extend Forwardable
|
|
7
|
+
include PrecomputedContentTypes
|
|
8
|
+
|
|
9
|
+
Options = Data.define(
|
|
10
|
+
:all_rescue_handler, :base_only_rescue_handlers, :content_types,
|
|
11
|
+
:default_error_formatter, :default_message, :default_status,
|
|
12
|
+
:error_formatters, :format,
|
|
13
|
+
:grape_exceptions_rescue_handler, :internal_grape_exceptions_rescue_handler,
|
|
14
|
+
:rescue_all, :rescue_grape_exceptions, :rescue_handlers, :rescue_options
|
|
15
|
+
) do
|
|
16
|
+
include Grape::Middleware::DeprecatedOptionsHashAccess
|
|
17
|
+
|
|
18
|
+
def initialize(
|
|
19
|
+
all_rescue_handler: nil, base_only_rescue_handlers: nil, content_types: nil,
|
|
20
|
+
default_error_formatter: nil, default_message: '', default_status: 500,
|
|
21
|
+
error_formatters: nil, format: :txt,
|
|
22
|
+
grape_exceptions_rescue_handler: nil, internal_grape_exceptions_rescue_handler: nil,
|
|
23
|
+
rescue_all: false, rescue_grape_exceptions: false, rescue_handlers: nil,
|
|
24
|
+
rescue_options: nil
|
|
25
|
+
)
|
|
26
|
+
# `rescue_options:` arrives nil from `Endpoint#error_middleware_options`
|
|
27
|
+
# when no `rescue_from` has been called — fall back to the documented
|
|
28
|
+
# defaults rather than letting nil propagate to `def_delegator
|
|
29
|
+
# :rescue_options, :backtrace`.
|
|
30
|
+
rescue_options ||= Grape::DSL::RescueOptions.new
|
|
31
|
+
super
|
|
32
|
+
end
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
+
# @deprecated Kept as a frozen Hash representation of the {Options}
|
|
36
|
+
# defaults for back-compat. Will be removed in a future release.
|
|
37
|
+
DEFAULT_OPTIONS = Options.new.to_h.freeze
|
|
38
|
+
|
|
39
|
+
def_delegators :config,
|
|
40
|
+
:all_rescue_handler, :base_only_rescue_handlers, :default_error_formatter,
|
|
41
|
+
:default_message, :default_status, :error_formatters, :format,
|
|
42
|
+
:grape_exceptions_rescue_handler, :internal_grape_exceptions_rescue_handler,
|
|
43
|
+
:rescue_all, :rescue_grape_exceptions, :rescue_handlers, :rescue_options
|
|
44
|
+
|
|
45
|
+
# +:backtrace+ / +:original_exception+ on the rescue options become
|
|
46
|
+
# +#include_backtrace+ / +#include_original_exception+ on the middleware,
|
|
47
|
+
# which is what the formatter call site reads.
|
|
48
|
+
def_delegator :rescue_options, :backtrace, :include_backtrace
|
|
49
|
+
def_delegator :rescue_options, :original_exception, :include_original_exception
|
|
50
|
+
|
|
35
51
|
def call!(env)
|
|
36
52
|
@env = env
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
end)
|
|
41
|
-
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
42
|
-
handler =
|
|
43
|
-
rescue_handler_for_base_only_class(e.class) ||
|
|
44
|
-
rescue_handler_for_class_or_its_ancestor(e.class) ||
|
|
45
|
-
rescue_handler_for_grape_exception(e.class) ||
|
|
46
|
-
rescue_handler_for_any_class(e.class) ||
|
|
47
|
-
raise
|
|
48
|
-
|
|
49
|
-
run_rescue_handler(handler, e)
|
|
50
|
-
end
|
|
53
|
+
error_response(catch(:error) { return @app.call(@env) })
|
|
54
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
55
|
+
run_rescue_handler(find_handler(e.class), e, @env[Grape::Env::API_ENDPOINT])
|
|
51
56
|
end
|
|
52
57
|
|
|
53
|
-
|
|
54
|
-
headers = headers.reverse_merge(Grape::Http::Headers::CONTENT_TYPE => content_type)
|
|
55
|
-
rack_response(format_message(message, backtrace, original_exception), status, headers)
|
|
56
|
-
end
|
|
58
|
+
private
|
|
57
59
|
|
|
58
|
-
def
|
|
59
|
-
|
|
60
|
+
def rack_response(status, headers, message)
|
|
61
|
+
message = Rack::Utils.escape_html(message) if html_content_type?(headers[Rack::CONTENT_TYPE])
|
|
62
|
+
Rack::Response.new(Array.wrap(message), Rack::Utils.status_code(status), Grape::Util::Header.new.merge(headers))
|
|
60
63
|
end
|
|
61
64
|
|
|
62
|
-
#
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
65
|
+
# Escaping must key off the media type only, case-insensitively. Comparing
|
|
66
|
+
# the raw header against 'text/html' would let a parameterized value such
|
|
67
|
+
# as 'text/html; charset=utf-8' (or a differently-cased 'Text/HTML', which
|
|
68
|
+
# browsers still treat as HTML) skip escaping and reflect an unescaped
|
|
69
|
+
# message into an HTML response. Such a header can be set from several
|
|
70
|
+
# places (a registered content type, or a custom Content-Type passed to
|
|
71
|
+
# error!/rescue_from), but they all render here.
|
|
72
|
+
def html_content_type?(content_type)
|
|
73
|
+
Grape::ContentTypes.media_type(content_type).to_s.casecmp?('text/html')
|
|
71
74
|
end
|
|
72
75
|
|
|
73
|
-
def
|
|
74
|
-
|
|
75
|
-
|
|
76
|
+
def format_message(error)
|
|
77
|
+
current_format = env[Grape::Env::API_FORMAT] || format
|
|
78
|
+
formatter = Grape::ErrorFormatter.formatter_for(current_format, error_formatters, default_error_formatter)
|
|
79
|
+
return formatter.call(error:, env:, include_backtrace:, include_original_exception:) if formatter
|
|
80
|
+
|
|
81
|
+
throw :error, Grape::Exceptions::ErrorResponse.new(
|
|
82
|
+
status: 406,
|
|
83
|
+
message: "The requested format '#{current_format}' is not supported.",
|
|
84
|
+
backtrace: error.backtrace,
|
|
85
|
+
original_exception: error.original_exception
|
|
86
|
+
)
|
|
76
87
|
end
|
|
77
88
|
|
|
78
|
-
def
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
message: "The requested format '#{format}' is not supported.",
|
|
84
|
-
backtrace: backtrace,
|
|
85
|
-
original_exception: original_exception unless formatter
|
|
86
|
-
formatter.call(message, backtrace, options, env, original_exception)
|
|
89
|
+
def find_handler(klass)
|
|
90
|
+
registered_rescue_handler(klass) ||
|
|
91
|
+
rescue_handler_for_grape_exception(klass) ||
|
|
92
|
+
rescue_handler_for_any_class(klass) ||
|
|
93
|
+
raise
|
|
87
94
|
end
|
|
88
95
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
96
|
+
def error_response(error = nil)
|
|
97
|
+
raw = Grape::Exceptions::ErrorResponse.coerce(error)
|
|
98
|
+
headers = { Rack::CONTENT_TYPE => content_type }
|
|
99
|
+
headers.merge!(raw.headers) if raw.headers.is_a?(Hash)
|
|
100
|
+
payload = raw.with(
|
|
101
|
+
status: raw.status || default_status,
|
|
102
|
+
message: raw.message || default_message,
|
|
103
|
+
headers:,
|
|
104
|
+
backtrace: raw.backtrace || raw.original_exception&.backtrace || []
|
|
105
|
+
)
|
|
106
|
+
env[Grape::Env::API_ENDPOINT].status(payload.status) # error! may not have been called
|
|
107
|
+
rack_response(payload.status, payload.headers, format_message(payload))
|
|
108
|
+
end
|
|
93
109
|
|
|
94
|
-
|
|
110
|
+
def default_rescue_handler(exception)
|
|
111
|
+
error_response(
|
|
112
|
+
Grape::Exceptions::ErrorResponse.new(
|
|
113
|
+
message: exception.message,
|
|
114
|
+
backtrace: exception.backtrace,
|
|
115
|
+
original_exception: exception
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
end
|
|
95
119
|
|
|
96
|
-
|
|
120
|
+
def registered_rescue_handler(klass)
|
|
121
|
+
rescue_handler_from(base_only_rescue_handlers) { |err| klass == err } ||
|
|
122
|
+
rescue_handler_from(rescue_handlers) { |err| klass <= err }
|
|
97
123
|
end
|
|
98
124
|
|
|
99
|
-
def
|
|
100
|
-
error, handler =
|
|
125
|
+
def rescue_handler_from(handlers)
|
|
126
|
+
error, handler = handlers&.find { |err, _handler| yield(err) }
|
|
101
127
|
|
|
102
128
|
return unless error
|
|
103
129
|
|
|
104
|
-
handler || :default_rescue_handler
|
|
130
|
+
handler || method(:default_rescue_handler)
|
|
105
131
|
end
|
|
106
132
|
|
|
107
133
|
def rescue_handler_for_grape_exception(klass)
|
|
108
134
|
return unless klass <= Grape::Exceptions::Base
|
|
109
|
-
return :error_response if klass == Grape::Exceptions::InvalidVersionHeader
|
|
110
|
-
return unless
|
|
135
|
+
return method(:error_response) if klass == Grape::Exceptions::InvalidVersionHeader
|
|
136
|
+
return unless rescue_grape_exceptions || !rescue_all
|
|
111
137
|
|
|
112
|
-
|
|
138
|
+
grape_exceptions_rescue_handler || method(:error_response)
|
|
113
139
|
end
|
|
114
140
|
|
|
115
141
|
def rescue_handler_for_any_class(klass)
|
|
116
142
|
return unless klass <= StandardError
|
|
117
|
-
return unless
|
|
143
|
+
return unless rescue_all || rescue_grape_exceptions
|
|
118
144
|
|
|
119
|
-
|
|
145
|
+
all_rescue_handler || method(:default_rescue_handler)
|
|
120
146
|
end
|
|
121
147
|
|
|
122
|
-
def run_rescue_handler(handler, error)
|
|
123
|
-
if handler.
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
148
|
+
def run_rescue_handler(handler, error, endpoint, redispatched: false)
|
|
149
|
+
handler = endpoint.public_method(handler) if handler.is_a?(Symbol)
|
|
150
|
+
response = catch(:error) do
|
|
151
|
+
handler.arity.zero? ? endpoint.instance_exec(&handler) : endpoint.instance_exec(error, &handler)
|
|
152
|
+
rescue StandardError => e
|
|
153
|
+
return redispatch(e, endpoint, redispatched)
|
|
127
154
|
end
|
|
128
155
|
|
|
129
|
-
|
|
156
|
+
return error_response(response) if error?(response)
|
|
157
|
+
return response if response.is_a?(Rack::Response)
|
|
158
|
+
|
|
159
|
+
run_rescue_handler(method(:default_rescue_handler), Grape::Exceptions::InvalidResponse.new, endpoint)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Route an exception raised inside a +rescue_from+ block.
|
|
163
|
+
#
|
|
164
|
+
# * If we have already redispatched once (the redispatched handler
|
|
165
|
+
# itself raised), go straight to {#framework_default} — bounds the
|
|
166
|
+
# chain at one redispatch.
|
|
167
|
+
# * Else if the exception has a registered +rescue_from+ handler,
|
|
168
|
+
# run it.
|
|
169
|
+
# * Else if it's a +Grape::Exceptions::Base+ subclass, render it
|
|
170
|
+
# through +error_response+ with its own +status+ and +message+.
|
|
171
|
+
# * Else fall through to {#safe_default}, which lets the user opt
|
|
172
|
+
# in via +rescue_from :internal_grape_exceptions+ or, failing
|
|
173
|
+
# that, applies the framework default.
|
|
174
|
+
def redispatch(error, endpoint, already_redispatched)
|
|
175
|
+
return framework_default(endpoint) if already_redispatched
|
|
176
|
+
|
|
177
|
+
registered = registered_rescue_handler(error.class)
|
|
178
|
+
|
|
179
|
+
return run_rescue_handler(registered, error, endpoint, redispatched: true) if registered
|
|
180
|
+
return run_rescue_handler(method(:error_response), error, endpoint, redispatched: true) if error.is_a?(Grape::Exceptions::Base)
|
|
181
|
+
|
|
182
|
+
safe_default(error, endpoint)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# The unrecognised-error path. Exposes the original exception on
|
|
186
|
+
# the rack env so upstream Rack middleware (loggers, error
|
|
187
|
+
# trackers) can observe it. If the user registered a
|
|
188
|
+
# +rescue_from :internal_grape_exceptions+ handler, that handler
|
|
189
|
+
# runs and owns the response. Otherwise the framework renders the
|
|
190
|
+
# generic +InternalServerError+ — never the original exception's
|
|
191
|
+
# message. The framework deliberately does no logging of its own
|
|
192
|
+
# here; that's the application's call.
|
|
193
|
+
def safe_default(error, endpoint)
|
|
194
|
+
env[Grape::Env::GRAPE_EXCEPTION] = error
|
|
195
|
+
return run_rescue_handler(internal_grape_exceptions_rescue_handler, error, endpoint, redispatched: true) if internal_grape_exceptions_rescue_handler
|
|
196
|
+
|
|
197
|
+
framework_default(endpoint)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def framework_default(endpoint)
|
|
201
|
+
run_rescue_handler(method(:default_rescue_handler), Grape::Exceptions::InternalServerError.new, endpoint)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def error!(message, status = default_status, headers = {}, backtrace = [], original_exception = nil)
|
|
205
|
+
env[Grape::Env::API_ENDPOINT].status(status) # not error! inside route
|
|
206
|
+
merged_headers = { Rack::CONTENT_TYPE => content_type }.merge!(headers)
|
|
207
|
+
error = Grape::Exceptions::ErrorResponse.new(
|
|
208
|
+
status:, message:, headers: merged_headers, backtrace:, original_exception:
|
|
209
|
+
)
|
|
210
|
+
rack_response(status, merged_headers, format_message(error))
|
|
211
|
+
end
|
|
130
212
|
|
|
131
|
-
|
|
132
|
-
|
|
213
|
+
def error?(response)
|
|
214
|
+
case response
|
|
215
|
+
when Grape::Exceptions::ErrorResponse
|
|
216
|
+
true
|
|
217
|
+
when Hash
|
|
218
|
+
return false unless response.key?(:message) && response.key?(:status) && response.key?(:headers)
|
|
219
|
+
|
|
220
|
+
Grape.deprecator.warn(
|
|
221
|
+
'Returning or throwing a Hash from a rescue handler is deprecated. ' \
|
|
222
|
+
'Use `error!(...)` or a `Grape::Exceptions::ErrorResponse` instead.'
|
|
223
|
+
)
|
|
224
|
+
true
|
|
133
225
|
else
|
|
134
|
-
|
|
226
|
+
false
|
|
135
227
|
end
|
|
136
228
|
end
|
|
137
229
|
end
|
|
@@ -1,20 +1,27 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'grape/middleware/base'
|
|
4
|
-
|
|
5
3
|
module Grape
|
|
6
4
|
module Middleware
|
|
7
5
|
class Formatter < Base
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
6
|
+
extend Forwardable
|
|
7
|
+
include PrecomputedContentTypes
|
|
8
|
+
|
|
9
|
+
Options = Data.define(:content_types, :default_format, :format, :formatters, :parsers) do
|
|
10
|
+
include Grape::Middleware::DeprecatedOptionsHashAccess
|
|
11
|
+
|
|
12
|
+
def initialize(content_types: nil, default_format: :txt, format: nil, formatters: nil, parsers: nil)
|
|
13
|
+
super
|
|
14
|
+
end
|
|
16
15
|
end
|
|
17
16
|
|
|
17
|
+
# @deprecated Kept as a frozen Hash representation of the {Options}
|
|
18
|
+
# defaults for back-compat. Will be removed in a future release.
|
|
19
|
+
DEFAULT_OPTIONS = Options.new.to_h.freeze
|
|
20
|
+
|
|
21
|
+
ALL_MEDIA_TYPES = '*/*'
|
|
22
|
+
|
|
23
|
+
def_delegators :config, :default_format, :format, :formatters, :parsers
|
|
24
|
+
|
|
18
25
|
def before
|
|
19
26
|
negotiate_content_type
|
|
20
27
|
read_body_input
|
|
@@ -23,13 +30,11 @@ module Grape
|
|
|
23
30
|
def after
|
|
24
31
|
return unless @app_response
|
|
25
32
|
|
|
26
|
-
status, headers, bodies =
|
|
33
|
+
status, headers, bodies = @app_response
|
|
27
34
|
|
|
28
|
-
if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
build_formatted_response(status, headers, bodies)
|
|
32
|
-
end
|
|
35
|
+
return [status, headers, []] if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status)
|
|
36
|
+
|
|
37
|
+
build_formatted_response(status, headers, bodies)
|
|
33
38
|
end
|
|
34
39
|
|
|
35
40
|
private
|
|
@@ -43,19 +48,28 @@ module Grape
|
|
|
43
48
|
end
|
|
44
49
|
else
|
|
45
50
|
# Allow content-type to be explicitly overwritten
|
|
46
|
-
formatter = fetch_formatter(headers
|
|
47
|
-
bodymap =
|
|
48
|
-
bodies.
|
|
51
|
+
formatter = fetch_formatter(headers)
|
|
52
|
+
bodymap = instrument_format_response(formatter) do
|
|
53
|
+
bodies.map { |body| formatter.call(body, env) }
|
|
49
54
|
end
|
|
50
55
|
Rack::Response.new(bodymap, status, headers)
|
|
51
56
|
end
|
|
52
57
|
rescue Grape::Exceptions::InvalidFormatter => e
|
|
53
|
-
throw :error, status: 500, message: e.message, backtrace: e.backtrace, original_exception: e
|
|
58
|
+
throw :error, Grape::Exceptions::ErrorResponse.new(status: 500, message: e.message, backtrace: e.backtrace, original_exception: e)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Guards on +listening?+ so that with no subscriber the payload Hash and
|
|
62
|
+
# notification machinery are skipped and the block runs directly (no added
|
|
63
|
+
# allocations); the block is forwarded anonymously.
|
|
64
|
+
def instrument_format_response(formatter, &)
|
|
65
|
+
return yield unless ActiveSupport::Notifications.notifier.listening?('format_response.grape')
|
|
66
|
+
|
|
67
|
+
ActiveSupport::Notifications.instrument('format_response.grape', formatter:, env:, &)
|
|
54
68
|
end
|
|
55
69
|
|
|
56
|
-
def fetch_formatter(headers
|
|
57
|
-
api_format = mime_types[headers[
|
|
58
|
-
Grape::Formatter.formatter_for(api_format,
|
|
70
|
+
def fetch_formatter(headers)
|
|
71
|
+
api_format = env.fetch(Grape::Env::API_FORMAT) { mime_types[headers[Rack::CONTENT_TYPE]] }
|
|
72
|
+
Grape::Formatter.formatter_for(api_format, formatters)
|
|
59
73
|
end
|
|
60
74
|
|
|
61
75
|
# Set the content type header for the API format if it is not already present.
|
|
@@ -63,116 +77,91 @@ module Grape
|
|
|
63
77
|
# @param headers [Hash]
|
|
64
78
|
# @return [Hash]
|
|
65
79
|
def ensure_content_type(headers)
|
|
66
|
-
if headers[
|
|
67
|
-
headers
|
|
68
|
-
else
|
|
69
|
-
headers.merge(Grape::Http::Headers::CONTENT_TYPE => content_type_for(env[Grape::Env::API_FORMAT]))
|
|
70
|
-
end
|
|
71
|
-
end
|
|
80
|
+
return headers if headers[Rack::CONTENT_TYPE]
|
|
72
81
|
|
|
73
|
-
|
|
74
|
-
|
|
82
|
+
headers[Rack::CONTENT_TYPE] = content_type_for(env[Grape::Env::API_FORMAT])
|
|
83
|
+
headers
|
|
75
84
|
end
|
|
76
85
|
|
|
77
|
-
# store read input in env['api.request.input']
|
|
78
86
|
def read_body_input
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
!request.parseable_data? &&
|
|
83
|
-
(request.content_length.to_i.positive? || request.env[Grape::Http::Headers::HTTP_TRANSFER_ENCODING] == CHUNKED)
|
|
87
|
+
input = rack_request.body # reads RACK_INPUT
|
|
88
|
+
return if input.nil?
|
|
89
|
+
return unless read_body_input?
|
|
84
90
|
|
|
85
|
-
|
|
91
|
+
rewind = input.respond_to?(:rewind)
|
|
86
92
|
|
|
87
|
-
input.rewind
|
|
93
|
+
input.rewind if rewind
|
|
88
94
|
body = env[Grape::Env::API_REQUEST_INPUT] = input.read
|
|
89
95
|
begin
|
|
90
|
-
read_rack_input(body)
|
|
96
|
+
read_rack_input(body)
|
|
91
97
|
ensure
|
|
92
|
-
input.rewind
|
|
98
|
+
input.rewind if rewind
|
|
93
99
|
end
|
|
94
100
|
end
|
|
95
101
|
|
|
96
|
-
# store parsed input in env['api.request.body']
|
|
97
102
|
def read_rack_input(body)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
103
|
+
return if body.empty?
|
|
104
|
+
|
|
105
|
+
media_type = rack_request.media_type
|
|
106
|
+
fmt = media_type ? mime_types[media_type] : default_format
|
|
107
|
+
|
|
108
|
+
throw :error, Grape::Exceptions::ErrorResponse.new(status: 415, message: "The provided content-type '#{media_type}' is not supported.") unless content_type_for(fmt)
|
|
109
|
+
parser = Grape::Parser.parser_for fmt, parsers
|
|
110
|
+
return env[Grape::Env::API_REQUEST_BODY] = body unless parser
|
|
111
|
+
|
|
112
|
+
begin
|
|
113
|
+
body = (env[Grape::Env::API_REQUEST_BODY] = parser.call(body, env))
|
|
114
|
+
if body.is_a?(Hash)
|
|
115
|
+
if (form_hash = env[Rack::RACK_REQUEST_FORM_HASH])
|
|
116
|
+
form_hash.merge!(body)
|
|
117
|
+
else
|
|
118
|
+
env[Rack::RACK_REQUEST_FORM_HASH] = body
|
|
112
119
|
end
|
|
113
|
-
|
|
114
|
-
raise e
|
|
115
|
-
rescue StandardError => e
|
|
116
|
-
throw :error, status: 400, message: e.message, backtrace: e.backtrace, original_exception: e
|
|
120
|
+
env[Rack::RACK_REQUEST_FORM_INPUT] = env[Rack::RACK_INPUT]
|
|
117
121
|
end
|
|
118
|
-
|
|
119
|
-
|
|
122
|
+
rescue Grape::Exceptions::Base => e
|
|
123
|
+
raise e
|
|
124
|
+
rescue StandardError => e
|
|
125
|
+
throw :error, Grape::Exceptions::ErrorResponse.new(status: 400, message: e.message, backtrace: e.backtrace, original_exception: e)
|
|
120
126
|
end
|
|
121
127
|
end
|
|
122
128
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
129
|
+
# this middleware will not try to format the following content-types since Rack already handles them
|
|
130
|
+
# when calling Rack's `params` function
|
|
131
|
+
# - application/x-www-form-urlencoded
|
|
132
|
+
# - multipart/form-data
|
|
133
|
+
# - multipart/related
|
|
134
|
+
# - multipart/mixed
|
|
135
|
+
def read_body_input?
|
|
136
|
+
return false unless rack_request.post? || rack_request.put? || rack_request.patch? || rack_request.delete?
|
|
137
|
+
return false if rack_request.form_data? && rack_request.content_type
|
|
138
|
+
return false if rack_request.parseable_data?
|
|
139
|
+
|
|
140
|
+
rack_request.content_length.to_i.positive? || rack_request.env['HTTP_TRANSFER_ENCODING'] == 'chunked'
|
|
130
141
|
end
|
|
131
142
|
|
|
132
|
-
def
|
|
133
|
-
|
|
143
|
+
def negotiate_content_type
|
|
144
|
+
fmt = format_from_extension || query_params['format'] || format || format_from_header || default_format
|
|
145
|
+
return env[Grape::Env::API_FORMAT] = fmt.to_sym if content_type_for(fmt)
|
|
134
146
|
|
|
135
|
-
|
|
136
|
-
extension = parts.last
|
|
137
|
-
# avoid symbol memory leak on an unknown format
|
|
138
|
-
return extension.to_sym if content_type_for(extension)
|
|
139
|
-
end
|
|
140
|
-
nil
|
|
147
|
+
throw :error, Grape::Exceptions::ErrorResponse.new(status: 406, message: "The requested format '#{fmt}' is not supported.")
|
|
141
148
|
end
|
|
142
149
|
|
|
143
|
-
def
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return
|
|
150
|
+
def format_from_extension
|
|
151
|
+
request_path = try_scrub(rack_request.path)
|
|
152
|
+
dot_pos = request_path.rindex('.')
|
|
153
|
+
return unless dot_pos
|
|
147
154
|
|
|
148
|
-
|
|
155
|
+
extension = request_path[(dot_pos + 1)..]
|
|
156
|
+
extension if content_type_for(extension)
|
|
149
157
|
end
|
|
150
158
|
|
|
151
159
|
def format_from_header
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
end
|
|
155
|
-
nil
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
def mime_array
|
|
159
|
-
accept = env[Grape::Http::Headers::HTTP_ACCEPT]
|
|
160
|
-
return [] unless accept
|
|
161
|
-
|
|
162
|
-
accept_into_mime_and_quality = %r{
|
|
163
|
-
(
|
|
164
|
-
\w+/[\w+.-]+) # eg application/vnd.example.myformat+xml
|
|
165
|
-
(?:
|
|
166
|
-
(?:;[^,]*?)? # optionally multiple formats in a row
|
|
167
|
-
;\s*q=([\d.]+) # optional "quality" preference (eg q=0.5)
|
|
168
|
-
)?
|
|
169
|
-
}x
|
|
170
|
-
|
|
171
|
-
vendor_prefix_pattern = /vnd\.[^+]+\+/
|
|
160
|
+
accept_header = try_scrub(env['HTTP_ACCEPT'])
|
|
161
|
+
return if accept_header.blank? || accept_header == ALL_MEDIA_TYPES
|
|
172
162
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
.flat_map { |mime, _| [mime, mime.sub(vendor_prefix_pattern, '')] }
|
|
163
|
+
media_type = Rack::Utils.best_q_match(accept_header, mime_types.keys)
|
|
164
|
+
mime_types[media_type] if media_type
|
|
176
165
|
end
|
|
177
166
|
end
|
|
178
167
|
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Middleware
|
|
5
|
+
# Include in a middleware subclass that needs content-type negotiation.
|
|
6
|
+
# Provides +content_types+ / +mime_types+ / +content_type_for+ /
|
|
7
|
+
# +content_type+ resolved from +config.content_types+ and
|
|
8
|
+
# +config.format+ — so the consuming middleware's +Options+ Data class
|
|
9
|
+
# must declare both fields. Warms those caches on the parent instance
|
|
10
|
+
# at initialization so per-request +dup+s inherit them (avoiding
|
|
11
|
+
# ~1 µs/request of +with_indifferent_access+ recomputation).
|
|
12
|
+
#
|
|
13
|
+
# Opt-in: plain +Grape::Middleware::Base+ subclasses that don't need
|
|
14
|
+
# content-type-aware helpers don't pay for them.
|
|
15
|
+
module PrecomputedContentTypes
|
|
16
|
+
def initialize(app, **options)
|
|
17
|
+
super
|
|
18
|
+
content_types
|
|
19
|
+
mime_types
|
|
20
|
+
content_types_indifferent_access
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def content_types
|
|
24
|
+
@content_types ||= Grape::ContentTypes.content_types_for(config.content_types)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def mime_types
|
|
28
|
+
@mime_types ||= Grape::ContentTypes.mime_types_for(content_types)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def content_type_for(format)
|
|
32
|
+
content_types_indifferent_access[format]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def content_type
|
|
36
|
+
content_type_for(env[Grape::Env::API_FORMAT] || config.format) || 'text/html'
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def content_types_indifferent_access
|
|
42
|
+
@content_types_indifferent_access ||= ActiveSupport::HashWithIndifferentAccess.new(content_types)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|