grape 2.0.0 → 2.4.0
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 +151 -1
- data/CONTRIBUTING.md +1 -1
- data/README.md +404 -334
- data/UPGRADING.md +279 -7
- data/grape.gemspec +8 -8
- data/lib/grape/api/instance.rb +34 -66
- data/lib/grape/api.rb +47 -70
- data/lib/grape/content_types.rb +13 -10
- data/lib/grape/cookies.rb +31 -24
- data/lib/grape/dry_types.rb +0 -2
- data/lib/grape/dsl/api.rb +0 -2
- data/lib/grape/dsl/desc.rb +49 -44
- data/lib/grape/dsl/headers.rb +2 -2
- data/lib/grape/dsl/helpers.rb +8 -4
- data/lib/grape/dsl/inside_route.rb +67 -54
- data/lib/grape/dsl/parameters.rb +10 -9
- data/lib/grape/dsl/request_response.rb +14 -18
- data/lib/grape/dsl/routing.rb +34 -17
- data/lib/grape/dsl/validations.rb +13 -0
- data/lib/grape/endpoint.rb +120 -118
- data/lib/grape/{util/env.rb → env.rb} +0 -5
- data/lib/grape/error_formatter/base.rb +51 -21
- data/lib/grape/error_formatter/json.rb +7 -15
- data/lib/grape/error_formatter/serializable_hash.rb +7 -0
- data/lib/grape/error_formatter/txt.rb +11 -17
- data/lib/grape/error_formatter/xml.rb +3 -13
- data/lib/grape/error_formatter.rb +5 -25
- data/lib/grape/exceptions/base.rb +18 -30
- data/lib/grape/exceptions/conflicting_types.rb +11 -0
- data/lib/grape/exceptions/invalid_parameters.rb +11 -0
- data/lib/grape/exceptions/too_deep_parameters.rb +11 -0
- data/lib/grape/exceptions/unknown_auth_strategy.rb +11 -0
- data/lib/grape/exceptions/unknown_params_builder.rb +11 -0
- data/lib/grape/exceptions/validation.rb +5 -6
- data/lib/grape/exceptions/validation_array_errors.rb +1 -0
- data/lib/grape/exceptions/validation_errors.rb +4 -6
- data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +2 -5
- data/lib/grape/extensions/hash.rb +7 -2
- data/lib/grape/extensions/hashie/mash.rb +3 -5
- data/lib/grape/formatter/base.rb +16 -0
- data/lib/grape/formatter/json.rb +4 -6
- data/lib/grape/formatter/serializable_hash.rb +1 -1
- 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/{util/json.rb → json.rb} +1 -3
- data/lib/grape/locale/en.yml +46 -42
- data/lib/grape/middleware/auth/base.rb +11 -34
- data/lib/grape/middleware/auth/dsl.rb +23 -31
- data/lib/grape/middleware/base.rb +41 -23
- data/lib/grape/middleware/error.rb +77 -76
- data/lib/grape/middleware/formatter.rb +48 -79
- data/lib/grape/middleware/globals.rb +1 -3
- data/lib/grape/middleware/stack.rb +26 -37
- data/lib/grape/middleware/versioner/accept_version_header.rb +6 -33
- data/lib/grape/middleware/versioner/base.rb +74 -0
- data/lib/grape/middleware/versioner/header.rb +59 -126
- data/lib/grape/middleware/versioner/param.rb +4 -25
- data/lib/grape/middleware/versioner/path.rb +10 -34
- data/lib/grape/middleware/versioner.rb +7 -14
- data/lib/grape/namespace.rb +4 -5
- data/lib/grape/params_builder/base.rb +18 -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 +32 -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 -60
- data/lib/grape/request.rb +161 -30
- data/lib/grape/router/base_route.rb +39 -0
- data/lib/grape/router/greedy_route.rb +20 -0
- data/lib/grape/router/pattern.rb +45 -31
- data/lib/grape/router/route.rb +28 -57
- data/lib/grape/router.rb +56 -43
- data/lib/grape/util/base_inheritable.rb +4 -4
- data/lib/grape/util/cache.rb +0 -3
- data/lib/grape/util/endpoint_configuration.rb +1 -1
- data/lib/grape/util/header.rb +13 -0
- data/lib/grape/util/inheritable_values.rb +0 -2
- data/lib/grape/util/lazy/block.rb +29 -0
- data/lib/grape/util/lazy/value.rb +38 -0
- data/lib/grape/util/lazy/value_array.rb +21 -0
- data/lib/grape/util/lazy/value_enumerable.rb +34 -0
- data/lib/grape/util/lazy/value_hash.rb +21 -0
- data/lib/grape/util/media_type.rb +70 -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 +1 -6
- data/lib/grape/util/strict_hash_configuration.rb +3 -3
- data/lib/grape/validations/attributes_doc.rb +38 -36
- data/lib/grape/validations/attributes_iterator.rb +1 -0
- data/lib/grape/validations/contract_scope.rb +34 -0
- data/lib/grape/validations/params_scope.rb +36 -32
- data/lib/grape/validations/types/array_coercer.rb +0 -2
- data/lib/grape/validations/types/dry_type_coercer.rb +9 -15
- data/lib/grape/validations/types/json.rb +0 -2
- data/lib/grape/validations/types/primitive_coercer.rb +0 -2
- data/lib/grape/validations/types/set_coercer.rb +0 -3
- data/lib/grape/validations/types.rb +0 -3
- data/lib/grape/validations/validator_factory.rb +2 -2
- data/lib/grape/validations/validators/allow_blank_validator.rb +1 -1
- data/lib/grape/validations/validators/base.rb +8 -11
- data/lib/grape/validations/validators/coerce_validator.rb +1 -1
- data/lib/grape/validations/validators/contract_scope_validator.rb +41 -0
- data/lib/grape/validations/validators/default_validator.rb +6 -2
- data/lib/grape/validations/validators/exactly_one_of_validator.rb +1 -1
- data/lib/grape/validations/validators/except_values_validator.rb +2 -2
- data/lib/grape/validations/validators/length_validator.rb +49 -0
- data/lib/grape/validations/validators/presence_validator.rb +1 -1
- data/lib/grape/validations/validators/regexp_validator.rb +2 -2
- data/lib/grape/validations/validators/values_validator.rb +20 -57
- data/lib/grape/validations.rb +8 -21
- data/lib/grape/version.rb +1 -1
- data/lib/grape/{util/xml.rb → xml.rb} +1 -1
- data/lib/grape.rb +42 -274
- metadata +45 -44
- data/lib/grape/eager_load.rb +0 -20
- data/lib/grape/http/headers.rb +0 -71
- 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/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/validations/types/build_coercer.rb +0 -94
@@ -1,30 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'grape/dsl/headers'
|
4
|
-
|
5
3
|
module Grape
|
6
4
|
module Middleware
|
7
5
|
class Base
|
8
|
-
include
|
6
|
+
include Grape::DSL::Headers
|
9
7
|
|
10
8
|
attr_reader :app, :env, :options
|
11
9
|
|
12
|
-
TEXT_HTML = 'text/html'
|
13
|
-
|
14
|
-
include Grape::DSL::Headers
|
15
|
-
|
16
10
|
# @param [Rack Application] app The standard argument for a Rack middleware.
|
17
11
|
# @param [Hash] options A hash of options, simply stored for use by subclasses.
|
18
|
-
def initialize(app,
|
12
|
+
def initialize(app, **options)
|
19
13
|
@app = app
|
20
|
-
@options =
|
14
|
+
@options = merge_default_options(options)
|
21
15
|
@app_response = nil
|
22
16
|
end
|
23
17
|
|
24
|
-
def default_options
|
25
|
-
{}
|
26
|
-
end
|
27
|
-
|
28
18
|
def call(env)
|
29
19
|
dup.call!(env).to_a
|
30
20
|
end
|
@@ -57,28 +47,42 @@ module Grape
|
|
57
47
|
# @return [Response, nil] a Rack SPEC response or nil to call the application afterwards.
|
58
48
|
def after; end
|
59
49
|
|
50
|
+
def rack_request
|
51
|
+
@rack_request ||= Rack::Request.new(env)
|
52
|
+
end
|
53
|
+
|
54
|
+
def context
|
55
|
+
env[Grape::Env::API_ENDPOINT]
|
56
|
+
end
|
57
|
+
|
60
58
|
def response
|
61
59
|
return @app_response if @app_response.is_a?(Rack::Response)
|
62
60
|
|
63
|
-
@app_response = Rack::Response
|
61
|
+
@app_response = Rack::Response[*@app_response]
|
64
62
|
end
|
65
63
|
|
66
|
-
def
|
67
|
-
|
64
|
+
def content_types
|
65
|
+
@content_types ||= Grape::ContentTypes.content_types_for(options[:content_types])
|
68
66
|
end
|
69
67
|
|
70
|
-
def
|
71
|
-
ContentTypes.
|
68
|
+
def mime_types
|
69
|
+
@mime_types ||= Grape::ContentTypes.mime_types_for(content_types)
|
70
|
+
end
|
71
|
+
|
72
|
+
def content_type_for(format)
|
73
|
+
content_types_indifferent_access[format]
|
72
74
|
end
|
73
75
|
|
74
76
|
def content_type
|
75
|
-
content_type_for(env[Grape::Env::API_FORMAT] || options[:format]) ||
|
77
|
+
content_type_for(env[Grape::Env::API_FORMAT] || options[:format]) || 'text/html'
|
76
78
|
end
|
77
79
|
|
78
|
-
def
|
79
|
-
|
80
|
-
|
81
|
-
|
80
|
+
def query_params
|
81
|
+
rack_request.GET
|
82
|
+
rescue Rack::QueryParser::ParamsTooDeepError
|
83
|
+
raise Grape::Exceptions::TooDeepParameters.new(Rack::Utils.param_depth_limit)
|
84
|
+
rescue Rack::Utils::ParameterTypeError
|
85
|
+
raise Grape::Exceptions::ConflictingTypes
|
82
86
|
end
|
83
87
|
|
84
88
|
private
|
@@ -91,6 +95,20 @@ module Grape
|
|
91
95
|
when Array then response[1].merge!(headers)
|
92
96
|
end
|
93
97
|
end
|
98
|
+
|
99
|
+
def content_types_indifferent_access
|
100
|
+
@content_types_indifferent_access ||= content_types.with_indifferent_access
|
101
|
+
end
|
102
|
+
|
103
|
+
def merge_default_options(options)
|
104
|
+
if respond_to?(:default_options)
|
105
|
+
default_options.deep_merge(options)
|
106
|
+
elsif self.class.const_defined?(:DEFAULT_OPTIONS)
|
107
|
+
self.class::DEFAULT_OPTIONS.deep_merge(options)
|
108
|
+
else
|
109
|
+
options
|
110
|
+
end
|
111
|
+
end
|
94
112
|
end
|
95
113
|
end
|
96
114
|
end
|
@@ -1,139 +1,140 @@
|
|
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
|
-
rescue_handlers: {}, # rescue handler blocks
|
25
|
-
base_only_rescue_handlers: {}, # rescue handler blocks rescuing only the base class
|
26
|
-
all_rescue_handler: nil # rescue handler block to rescue from all exceptions
|
27
|
-
}
|
28
|
-
end
|
29
|
-
|
30
|
-
def initialize(app, *options)
|
6
|
+
DEFAULT_OPTIONS = {
|
7
|
+
default_status: 500,
|
8
|
+
default_message: '',
|
9
|
+
format: :txt,
|
10
|
+
rescue_all: false,
|
11
|
+
rescue_grape_exceptions: false,
|
12
|
+
rescue_subclasses: true,
|
13
|
+
rescue_options: {
|
14
|
+
backtrace: false,
|
15
|
+
original_exception: false
|
16
|
+
}.freeze
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
def initialize(app, **options)
|
31
20
|
super
|
32
|
-
self.class.
|
21
|
+
self.class.include(options[:helpers]) if options[:helpers]
|
33
22
|
end
|
34
23
|
|
35
24
|
def call!(env)
|
36
25
|
@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
|
26
|
+
error_response(catch(:error) { return @app.call(@env) })
|
27
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
28
|
+
run_rescue_handler(find_handler(e.class), e, @env[Grape::Env::API_ENDPOINT])
|
51
29
|
end
|
52
30
|
|
53
|
-
|
54
|
-
|
55
|
-
|
31
|
+
private
|
32
|
+
|
33
|
+
def rack_response(status, headers, message)
|
34
|
+
message = Rack::Utils.escape_html(message) if headers[Rack::CONTENT_TYPE] == 'text/html'
|
35
|
+
Rack::Response.new(Array.wrap(message), Rack::Utils.status_code(status), Grape::Util::Header.new.merge(headers))
|
56
36
|
end
|
57
37
|
|
58
|
-
def
|
59
|
-
|
38
|
+
def format_message(message, backtrace, original_exception = nil)
|
39
|
+
format = env[Grape::Env::API_FORMAT] || options[:format]
|
40
|
+
formatter = Grape::ErrorFormatter.formatter_for(format, options[:error_formatters], options[:default_error_formatter])
|
41
|
+
return formatter.call(message, backtrace, options, env, original_exception) if formatter
|
42
|
+
|
43
|
+
throw :error,
|
44
|
+
status: 406,
|
45
|
+
message: "The requested format '#{format}' is not supported.",
|
46
|
+
backtrace: backtrace,
|
47
|
+
original_exception: original_exception
|
48
|
+
end
|
49
|
+
|
50
|
+
def find_handler(klass)
|
51
|
+
rescue_handler_for_base_only_class(klass) ||
|
52
|
+
rescue_handler_for_class_or_its_ancestor(klass) ||
|
53
|
+
rescue_handler_for_grape_exception(klass) ||
|
54
|
+
rescue_handler_for_any_class(klass) ||
|
55
|
+
raise
|
60
56
|
end
|
61
57
|
|
62
|
-
# TODO: This method is deprecated. Refactor out.
|
63
58
|
def error_response(error = {})
|
64
59
|
status = error[:status] || options[:default_status]
|
60
|
+
env[Grape::Env::API_ENDPOINT].status(status) # error! may not have been called
|
65
61
|
message = error[:message] || options[:default_message]
|
66
|
-
headers = { Rack::CONTENT_TYPE => content_type }
|
67
|
-
|
62
|
+
headers = { Rack::CONTENT_TYPE => content_type }.tap do |h|
|
63
|
+
h.merge!(error[:headers]) if error[:headers].is_a?(Hash)
|
64
|
+
end
|
68
65
|
backtrace = error[:backtrace] || error[:original_exception]&.backtrace || []
|
69
66
|
original_exception = error.is_a?(Exception) ? error : error[:original_exception] || nil
|
70
|
-
rack_response(format_message(message, backtrace, original_exception)
|
67
|
+
rack_response(status, headers, format_message(message, backtrace, original_exception))
|
71
68
|
end
|
72
69
|
|
73
|
-
def
|
74
|
-
message
|
75
|
-
Rack::Response.new([message], Rack::Utils.status_code(status), headers)
|
70
|
+
def default_rescue_handler(exception)
|
71
|
+
error_response(message: exception.message, backtrace: exception.backtrace, original_exception: exception)
|
76
72
|
end
|
77
73
|
|
78
|
-
def format_message(message, backtrace, original_exception = nil)
|
79
|
-
format = env[Grape::Env::API_FORMAT] || options[:format]
|
80
|
-
formatter = Grape::ErrorFormatter.formatter_for(format, **options)
|
81
|
-
throw :error,
|
82
|
-
status: 406,
|
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)
|
87
|
-
end
|
88
|
-
|
89
|
-
private
|
90
|
-
|
91
74
|
def rescue_handler_for_base_only_class(klass)
|
92
|
-
error, handler = options[:base_only_rescue_handlers]
|
75
|
+
error, handler = options[:base_only_rescue_handlers]&.find { |err, _handler| klass == err }
|
93
76
|
|
94
77
|
return unless error
|
95
78
|
|
96
|
-
handler || :default_rescue_handler
|
79
|
+
handler || method(:default_rescue_handler)
|
97
80
|
end
|
98
81
|
|
99
82
|
def rescue_handler_for_class_or_its_ancestor(klass)
|
100
|
-
error, handler = options[:rescue_handlers]
|
83
|
+
error, handler = options[:rescue_handlers]&.find { |err, _handler| klass <= err }
|
101
84
|
|
102
85
|
return unless error
|
103
86
|
|
104
|
-
handler || :default_rescue_handler
|
87
|
+
handler || method(:default_rescue_handler)
|
105
88
|
end
|
106
89
|
|
107
90
|
def rescue_handler_for_grape_exception(klass)
|
108
91
|
return unless klass <= Grape::Exceptions::Base
|
109
|
-
return :error_response if klass == Grape::Exceptions::InvalidVersionHeader
|
92
|
+
return method(:error_response) if klass == Grape::Exceptions::InvalidVersionHeader
|
110
93
|
return unless options[:rescue_grape_exceptions] || !options[:rescue_all]
|
111
94
|
|
112
|
-
options[:grape_exceptions_rescue_handler] || :error_response
|
95
|
+
options[:grape_exceptions_rescue_handler] || method(:error_response)
|
113
96
|
end
|
114
97
|
|
115
98
|
def rescue_handler_for_any_class(klass)
|
116
99
|
return unless klass <= StandardError
|
117
100
|
return unless options[:rescue_all] || options[:rescue_grape_exceptions]
|
118
101
|
|
119
|
-
options[:all_rescue_handler] || :default_rescue_handler
|
102
|
+
options[:all_rescue_handler] || method(:default_rescue_handler)
|
120
103
|
end
|
121
104
|
|
122
|
-
def run_rescue_handler(handler, error)
|
105
|
+
def run_rescue_handler(handler, error, endpoint)
|
123
106
|
if handler.instance_of?(Symbol)
|
124
107
|
raise NoMethodError, "undefined method '#{handler}'" unless respond_to?(handler)
|
125
108
|
|
126
109
|
handler = public_method(handler)
|
127
110
|
end
|
128
111
|
|
129
|
-
response =
|
112
|
+
response = catch(:error) do
|
113
|
+
handler.arity.zero? ? endpoint.instance_exec(&handler) : endpoint.instance_exec(error, &handler)
|
114
|
+
end
|
130
115
|
|
131
|
-
if
|
116
|
+
if error?(response)
|
117
|
+
error_response(response)
|
118
|
+
elsif response.is_a?(Rack::Response)
|
132
119
|
response
|
133
120
|
else
|
134
|
-
run_rescue_handler(:default_rescue_handler, Grape::Exceptions::InvalidResponse.new)
|
121
|
+
run_rescue_handler(method(:default_rescue_handler), Grape::Exceptions::InvalidResponse.new, endpoint)
|
135
122
|
end
|
136
123
|
end
|
124
|
+
|
125
|
+
def error!(message, status = options[:default_status], headers = {}, backtrace = [], original_exception = nil)
|
126
|
+
env[Grape::Env::API_ENDPOINT].status(status) # not error! inside route
|
127
|
+
rack_response(
|
128
|
+
status, headers.reverse_merge(Rack::CONTENT_TYPE => content_type),
|
129
|
+
format_message(message, backtrace, original_exception)
|
130
|
+
)
|
131
|
+
end
|
132
|
+
|
133
|
+
def error?(response)
|
134
|
+
return false unless response.is_a?(Hash)
|
135
|
+
|
136
|
+
response.key?(:message) && response.key?(:status) && response.key?(:headers)
|
137
|
+
end
|
137
138
|
end
|
138
139
|
end
|
139
140
|
end
|
@@ -1,19 +1,11 @@
|
|
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
|
-
default_format: :txt,
|
13
|
-
formatters: {},
|
14
|
-
parsers: {}
|
15
|
-
}
|
16
|
-
end
|
6
|
+
DEFAULT_OPTIONS = {
|
7
|
+
default_format: :txt
|
8
|
+
}.freeze
|
17
9
|
|
18
10
|
def before
|
19
11
|
negotiate_content_type
|
@@ -26,7 +18,7 @@ module Grape
|
|
26
18
|
status, headers, bodies = *@app_response
|
27
19
|
|
28
20
|
if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status)
|
29
|
-
|
21
|
+
[status, headers, []]
|
30
22
|
else
|
31
23
|
build_formatted_response(status, headers, bodies)
|
32
24
|
end
|
@@ -54,8 +46,8 @@ module Grape
|
|
54
46
|
end
|
55
47
|
|
56
48
|
def fetch_formatter(headers, options)
|
57
|
-
api_format = mime_types[headers[Rack::CONTENT_TYPE]]
|
58
|
-
Grape::Formatter.formatter_for(api_format,
|
49
|
+
api_format = env.fetch(Grape::Env::API_FORMAT) { mime_types[headers[Rack::CONTENT_TYPE]] }
|
50
|
+
Grape::Formatter.formatter_for(api_format, options[:formatters])
|
59
51
|
end
|
60
52
|
|
61
53
|
# Set the content type header for the API format if it is not already present.
|
@@ -70,45 +62,38 @@ module Grape
|
|
70
62
|
end
|
71
63
|
end
|
72
64
|
|
73
|
-
def request
|
74
|
-
@request ||= Rack::Request.new(env)
|
75
|
-
end
|
76
|
-
|
77
|
-
# store read input in env['api.request.input']
|
78
65
|
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)
|
84
|
-
|
85
|
-
return unless (input = env[Grape::Env::RACK_INPUT])
|
66
|
+
input = rack_request.body # reads RACK_INPUT
|
67
|
+
return if input.nil?
|
68
|
+
return unless read_body_input?
|
86
69
|
|
87
|
-
input.rewind
|
70
|
+
input.try(:rewind)
|
88
71
|
body = env[Grape::Env::API_REQUEST_INPUT] = input.read
|
89
72
|
begin
|
90
|
-
read_rack_input(body)
|
73
|
+
read_rack_input(body)
|
91
74
|
ensure
|
92
|
-
input.rewind
|
75
|
+
input.try(:rewind)
|
93
76
|
end
|
94
77
|
end
|
95
78
|
|
96
|
-
# store parsed input in env['api.request.body']
|
97
79
|
def read_rack_input(body)
|
98
|
-
|
80
|
+
return if body.empty?
|
99
81
|
|
100
|
-
|
101
|
-
|
82
|
+
media_type = rack_request.media_type
|
83
|
+
fmt = media_type ? mime_types[media_type] : options[:default_format]
|
84
|
+
|
85
|
+
throw :error, status: 415, message: "The provided content-type '#{media_type}' is not supported." unless content_type_for(fmt)
|
86
|
+
parser = Grape::Parser.parser_for fmt, options[:parsers]
|
102
87
|
if parser
|
103
88
|
begin
|
104
89
|
body = (env[Grape::Env::API_REQUEST_BODY] = parser.call(body, env))
|
105
90
|
if body.is_a?(Hash)
|
106
|
-
env[
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
env[
|
91
|
+
env[Rack::RACK_REQUEST_FORM_HASH] = if env.key?(Rack::RACK_REQUEST_FORM_HASH)
|
92
|
+
env[Rack::RACK_REQUEST_FORM_HASH].merge(body)
|
93
|
+
else
|
94
|
+
body
|
95
|
+
end
|
96
|
+
env[Rack::RACK_REQUEST_FORM_INPUT] = env[Rack::RACK_INPUT]
|
112
97
|
end
|
113
98
|
rescue Grape::Exceptions::Base => e
|
114
99
|
raise e
|
@@ -120,59 +105,43 @@ module Grape
|
|
120
105
|
end
|
121
106
|
end
|
122
107
|
|
108
|
+
# this middleware will not try to format the following content-types since Rack already handles them
|
109
|
+
# when calling Rack's `params` function
|
110
|
+
# - application/x-www-form-urlencoded
|
111
|
+
# - multipart/form-data
|
112
|
+
# - multipart/related
|
113
|
+
# - multipart/mixed
|
114
|
+
def read_body_input?
|
115
|
+
(rack_request.post? || rack_request.put? || rack_request.patch? || rack_request.delete?) &&
|
116
|
+
!(rack_request.form_data? && rack_request.content_type) &&
|
117
|
+
!rack_request.parseable_data? &&
|
118
|
+
(rack_request.content_length.to_i.positive? || rack_request.env['HTTP_TRANSFER_ENCODING'] == 'chunked')
|
119
|
+
end
|
120
|
+
|
123
121
|
def negotiate_content_type
|
124
|
-
fmt = format_from_extension ||
|
122
|
+
fmt = format_from_extension || query_params['format'] || options[:format] || format_from_header || options[:default_format]
|
125
123
|
if content_type_for(fmt)
|
126
|
-
env[Grape::Env::API_FORMAT] = fmt
|
124
|
+
env[Grape::Env::API_FORMAT] = fmt.to_sym
|
127
125
|
else
|
128
126
|
throw :error, status: 406, message: "The requested format '#{fmt}' is not supported."
|
129
127
|
end
|
130
128
|
end
|
131
129
|
|
132
130
|
def format_from_extension
|
133
|
-
|
131
|
+
request_path = rack_request.path.try(:scrub)
|
132
|
+
dot_pos = request_path.rindex('.')
|
133
|
+
return unless dot_pos
|
134
134
|
|
135
|
-
|
136
|
-
|
137
|
-
# avoid symbol memory leak on an unknown format
|
138
|
-
return extension.to_sym if content_type_for(extension)
|
139
|
-
end
|
140
|
-
nil
|
141
|
-
end
|
142
|
-
|
143
|
-
def format_from_params
|
144
|
-
fmt = Rack::Utils.parse_nested_query(env[Grape::Http::Headers::QUERY_STRING])[Grape::Http::Headers::FORMAT]
|
145
|
-
# avoid symbol memory leak on an unknown format
|
146
|
-
return fmt.to_sym if content_type_for(fmt)
|
147
|
-
|
148
|
-
fmt
|
135
|
+
extension = request_path[dot_pos + 1..]
|
136
|
+
extension if content_type_for(extension)
|
149
137
|
end
|
150
138
|
|
151
139
|
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=([\w.]+) # optional "quality" preference (eg q=0.5)
|
168
|
-
)?
|
169
|
-
}x
|
170
|
-
|
171
|
-
vendor_prefix_pattern = /vnd\.[^+]+\+/
|
140
|
+
accept_header = env['HTTP_ACCEPT'].try(:scrub)
|
141
|
+
return if accept_header.blank?
|
172
142
|
|
173
|
-
|
174
|
-
|
175
|
-
.flat_map { |mime, _| [mime, mime.sub(vendor_prefix_pattern, '')] }
|
143
|
+
media_type = Rack::Utils.best_q_match(accept_header, mime_types.keys)
|
144
|
+
mime_types[media_type] if media_type
|
176
145
|
end
|
177
146
|
end
|
178
147
|
end
|
@@ -1,7 +1,5 @@
|
|
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 Globals < Base
|
@@ -9,7 +7,7 @@ module Grape
|
|
9
7
|
request = Grape::Request.new(@env, build_params_with: @options[:build_params_with])
|
10
8
|
@env[Grape::Env::GRAPE_REQUEST] = request
|
11
9
|
@env[Grape::Env::GRAPE_REQUEST_HEADERS] = request.headers
|
12
|
-
@env[Grape::Env::GRAPE_REQUEST_PARAMS] = request.params if @env[
|
10
|
+
@env[Grape::Env::GRAPE_REQUEST_PARAMS] = request.params if @env[Rack::RACK_INPUT]
|
13
11
|
end
|
14
12
|
end
|
15
13
|
end
|
@@ -5,10 +5,11 @@ module Grape
|
|
5
5
|
# Class to handle the stack of middlewares based on ActionDispatch::MiddlewareStack
|
6
6
|
# It allows to insert and insert after
|
7
7
|
class Stack
|
8
|
+
extend Forwardable
|
8
9
|
class Middleware
|
9
10
|
attr_reader :args, :block, :klass
|
10
11
|
|
11
|
-
def initialize(klass,
|
12
|
+
def initialize(klass, args, block)
|
12
13
|
@klass = klass
|
13
14
|
@args = args
|
14
15
|
@block = block
|
@@ -31,8 +32,12 @@ module Grape
|
|
31
32
|
klass.to_s
|
32
33
|
end
|
33
34
|
|
34
|
-
def
|
35
|
-
|
35
|
+
def build(builder)
|
36
|
+
# we need to force the ruby2_keywords_hash for middlewares that initialize contains keywords
|
37
|
+
# like ActionDispatch::RequestId since middleware arguments are serialized
|
38
|
+
# https://rubyapi.org/3.4/o/hash#method-c-ruby2_keywords_hash
|
39
|
+
args[-1] = Hash.ruby2_keywords_hash(args[-1]) if args.last.is_a?(Hash) && Hash.respond_to?(:ruby2_keywords_hash)
|
40
|
+
builder.use(klass, *args, &block)
|
36
41
|
end
|
37
42
|
end
|
38
43
|
|
@@ -40,33 +45,17 @@ module Grape
|
|
40
45
|
|
41
46
|
attr_accessor :middlewares, :others
|
42
47
|
|
48
|
+
def_delegators :middlewares, :each, :size, :last, :[]
|
49
|
+
|
43
50
|
def initialize
|
44
51
|
@middlewares = []
|
45
52
|
@others = []
|
46
53
|
end
|
47
54
|
|
48
|
-
def
|
49
|
-
@middlewares.each(&block)
|
50
|
-
end
|
51
|
-
|
52
|
-
def size
|
53
|
-
middlewares.size
|
54
|
-
end
|
55
|
-
|
56
|
-
def last
|
57
|
-
middlewares.last
|
58
|
-
end
|
59
|
-
|
60
|
-
def [](i)
|
61
|
-
middlewares[i]
|
62
|
-
end
|
63
|
-
|
64
|
-
def insert(index, *args, &block)
|
55
|
+
def insert(index, klass, *args, &block)
|
65
56
|
index = assert_index(index, :before)
|
66
|
-
|
67
|
-
middlewares.insert(index, middleware)
|
57
|
+
middlewares.insert(index, self.class::Middleware.new(klass, args, block))
|
68
58
|
end
|
69
|
-
ruby2_keywords :insert if respond_to?(:ruby2_keywords, true)
|
70
59
|
|
71
60
|
alias insert_before insert
|
72
61
|
|
@@ -74,39 +63,39 @@ module Grape
|
|
74
63
|
index = assert_index(index, :after)
|
75
64
|
insert(index + 1, *args, &block)
|
76
65
|
end
|
77
|
-
ruby2_keywords :insert_after if respond_to?(:ruby2_keywords, true)
|
78
66
|
|
79
|
-
def use(*args, &block)
|
80
|
-
middleware = self.class::Middleware.new(
|
67
|
+
def use(klass, *args, &block)
|
68
|
+
middleware = self.class::Middleware.new(klass, args, block)
|
81
69
|
middlewares.push(middleware)
|
82
70
|
end
|
83
|
-
ruby2_keywords :use if respond_to?(:ruby2_keywords, true)
|
84
71
|
|
85
72
|
def merge_with(middleware_specs)
|
86
|
-
middleware_specs.each do |operation, *args|
|
73
|
+
middleware_specs.each do |operation, klass, *args|
|
87
74
|
if args.last.is_a?(Proc)
|
88
75
|
last_proc = args.pop
|
89
|
-
public_send(operation, *args, &last_proc)
|
76
|
+
public_send(operation, klass, *args, &last_proc)
|
90
77
|
else
|
91
|
-
public_send(operation, *args)
|
78
|
+
public_send(operation, klass, *args)
|
92
79
|
end
|
93
80
|
end
|
94
81
|
end
|
95
82
|
|
96
83
|
# @return [Rack::Builder] the builder object with our middlewares applied
|
97
|
-
def build
|
98
|
-
|
99
|
-
|
100
|
-
m
|
84
|
+
def build
|
85
|
+
Rack::Builder.new.tap do |builder|
|
86
|
+
others.shift(others.size).each { |m| merge_with(m) }
|
87
|
+
middlewares.each do |m|
|
88
|
+
m.build(builder)
|
89
|
+
end
|
101
90
|
end
|
102
|
-
builder
|
103
91
|
end
|
104
92
|
|
105
93
|
# @description Add middlewares with :use operation to the stack. Store others with :insert_* operation for later
|
106
94
|
# @param [Array] other_specs An array of middleware specifications (e.g. [[:use, klass], [:insert_before, *args]])
|
107
95
|
def concat(other_specs)
|
108
|
-
|
109
|
-
|
96
|
+
use, not_use = other_specs.partition { |o| o.first == :use }
|
97
|
+
others << not_use
|
98
|
+
merge_with(use)
|
110
99
|
end
|
111
100
|
|
112
101
|
protected
|