grape 2.0.0 → 2.2.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 +96 -1
- data/README.md +364 -317
- data/UPGRADING.md +205 -7
- data/grape.gemspec +7 -7
- data/lib/grape/api/instance.rb +14 -11
- data/lib/grape/api.rb +19 -10
- data/lib/grape/content_types.rb +13 -10
- data/lib/grape/cookies.rb +2 -1
- data/lib/grape/dry_types.rb +0 -2
- data/lib/grape/dsl/desc.rb +22 -20
- data/lib/grape/dsl/headers.rb +1 -1
- data/lib/grape/dsl/helpers.rb +7 -3
- data/lib/grape/dsl/inside_route.rb +51 -15
- data/lib/grape/dsl/parameters.rb +5 -4
- data/lib/grape/dsl/request_response.rb +14 -18
- data/lib/grape/dsl/routing.rb +20 -4
- data/lib/grape/dsl/validations.rb +13 -0
- data/lib/grape/endpoint.rb +43 -35
- data/lib/grape/{util/env.rb → env.rb} +0 -5
- data/lib/grape/error_formatter/json.rb +13 -4
- data/lib/grape/error_formatter/txt.rb +11 -10
- data/lib/grape/error_formatter.rb +13 -25
- data/lib/grape/exceptions/base.rb +3 -3
- data/lib/grape/exceptions/validation.rb +0 -2
- data/lib/grape/exceptions/validation_array_errors.rb +1 -0
- data/lib/grape/exceptions/validation_errors.rb +2 -4
- data/lib/grape/extensions/hash.rb +5 -1
- data/lib/grape/formatter.rb +15 -25
- data/lib/grape/http/headers.rb +18 -34
- data/lib/grape/{util/json.rb → json.rb} +1 -3
- data/lib/grape/locale/en.yml +4 -0
- data/lib/grape/middleware/auth/base.rb +0 -2
- data/lib/grape/middleware/auth/dsl.rb +0 -2
- data/lib/grape/middleware/base.rb +14 -15
- data/lib/grape/middleware/error.rb +61 -54
- data/lib/grape/middleware/formatter.rb +18 -15
- data/lib/grape/middleware/globals.rb +1 -3
- data/lib/grape/middleware/stack.rb +4 -5
- data/lib/grape/middleware/versioner/accept_version_header.rb +8 -33
- data/lib/grape/middleware/versioner/header.rb +62 -123
- data/lib/grape/middleware/versioner/param.rb +5 -23
- data/lib/grape/middleware/versioner/path.rb +11 -33
- data/lib/grape/middleware/versioner.rb +5 -14
- data/lib/grape/middleware/versioner_helpers.rb +75 -0
- data/lib/grape/namespace.rb +3 -4
- data/lib/grape/parser.rb +8 -24
- data/lib/grape/path.rb +24 -29
- data/lib/grape/request.rb +4 -12
- data/lib/grape/router/base_route.rb +39 -0
- data/lib/grape/router/greedy_route.rb +20 -0
- data/lib/grape/router/pattern.rb +39 -30
- data/lib/grape/router/route.rb +22 -59
- data/lib/grape/router.rb +32 -37
- 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/object.rb +45 -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/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 +71 -0
- data/lib/grape/validations/params_scope.rb +22 -19
- data/lib/grape/validations/types/array_coercer.rb +0 -2
- data/lib/grape/validations/types/build_coercer.rb +69 -71
- data/lib/grape/validations/types/dry_type_coercer.rb +1 -11
- 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/validators/base.rb +1 -0
- data/lib/grape/validations/validators/default_validator.rb +5 -1
- data/lib/grape/validations/validators/exactly_one_of_validator.rb +1 -1
- data/lib/grape/validations/validators/length_validator.rb +49 -0
- data/lib/grape/validations/validators/values_validator.rb +6 -1
- data/lib/grape/validations.rb +3 -7
- data/lib/grape/version.rb +1 -1
- data/lib/grape/{util/xml.rb → xml.rb} +1 -1
- data/lib/grape.rb +30 -274
- metadata +31 -38
- data/lib/grape/eager_load.rb +0 -20
- 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/formatter.rb
CHANGED
@@ -2,34 +2,24 @@
|
|
2
2
|
|
3
3
|
module Grape
|
4
4
|
module Formatter
|
5
|
-
|
5
|
+
module_function
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
xml: Grape::Formatter::Xml
|
15
|
-
}
|
16
|
-
end
|
7
|
+
DEFAULTS = {
|
8
|
+
json: Grape::Formatter::Json,
|
9
|
+
jsonapi: Grape::Formatter::Json,
|
10
|
+
serializable_hash: Grape::Formatter::SerializableHash,
|
11
|
+
txt: Grape::Formatter::Txt,
|
12
|
+
xml: Grape::Formatter::Xml
|
13
|
+
}.freeze
|
17
14
|
|
18
|
-
|
19
|
-
builtin_formatters.merge(default_elements).merge!(options[:formatters] || {})
|
20
|
-
end
|
15
|
+
DEFAULT_LAMBDA_FORMATTER = ->(obj, _env) { obj }
|
21
16
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
method(spec)
|
29
|
-
else
|
30
|
-
spec
|
31
|
-
end
|
32
|
-
end
|
17
|
+
def formatter_for(api_format, formatters)
|
18
|
+
select_formatter(formatters, api_format) || DEFAULT_LAMBDA_FORMATTER
|
19
|
+
end
|
20
|
+
|
21
|
+
def select_formatter(formatters, api_format)
|
22
|
+
formatters&.key?(api_format) ? formatters[api_format] : DEFAULTS[api_format]
|
33
23
|
end
|
34
24
|
end
|
35
25
|
end
|
data/lib/grape/http/headers.rb
CHANGED
@@ -1,46 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'grape/util/lazy_object'
|
4
|
-
|
5
3
|
module Grape
|
6
4
|
module Http
|
7
5
|
module Headers
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
REQUEST_METHOD = 'REQUEST_METHOD'
|
12
|
-
QUERY_STRING = 'QUERY_STRING'
|
13
|
-
|
14
|
-
if Grape.lowercase_headers?
|
15
|
-
ALLOW = 'allow'
|
16
|
-
LOCATION = 'location'
|
17
|
-
TRANSFER_ENCODING = 'transfer-encoding'
|
18
|
-
X_CASCADE = 'x-cascade'
|
19
|
-
else
|
20
|
-
ALLOW = 'Allow'
|
21
|
-
LOCATION = 'Location'
|
22
|
-
TRANSFER_ENCODING = 'Transfer-Encoding'
|
23
|
-
X_CASCADE = 'X-Cascade'
|
24
|
-
end
|
25
|
-
|
26
|
-
GET = 'GET'
|
27
|
-
POST = 'POST'
|
28
|
-
PUT = 'PUT'
|
29
|
-
PATCH = 'PATCH'
|
30
|
-
DELETE = 'DELETE'
|
31
|
-
HEAD = 'HEAD'
|
32
|
-
OPTIONS = 'OPTIONS'
|
6
|
+
HTTP_ACCEPT_VERSION = 'HTTP_ACCEPT_VERSION'
|
7
|
+
HTTP_ACCEPT = 'HTTP_ACCEPT'
|
8
|
+
HTTP_TRANSFER_ENCODING = 'HTTP_TRANSFER_ENCODING'
|
33
9
|
|
34
|
-
|
35
|
-
|
10
|
+
ALLOW = 'Allow'
|
11
|
+
LOCATION = 'Location'
|
12
|
+
X_CASCADE = 'X-Cascade'
|
13
|
+
TRANSFER_ENCODING = 'Transfer-Encoding'
|
36
14
|
|
37
|
-
|
38
|
-
|
39
|
-
|
15
|
+
SUPPORTED_METHODS = [
|
16
|
+
Rack::GET,
|
17
|
+
Rack::POST,
|
18
|
+
Rack::PUT,
|
19
|
+
Rack::PATCH,
|
20
|
+
Rack::DELETE,
|
21
|
+
Rack::HEAD,
|
22
|
+
Rack::OPTIONS
|
23
|
+
].freeze
|
40
24
|
|
41
|
-
|
25
|
+
SUPPORTED_METHODS_WITHOUT_OPTIONS = (SUPPORTED_METHODS - [Rack::OPTIONS]).freeze
|
42
26
|
|
43
|
-
HTTP_HEADERS = Grape::Util::
|
27
|
+
HTTP_HEADERS = Grape::Util::Lazy::Object.new do
|
44
28
|
common_http_headers = %w[
|
45
29
|
Version
|
46
30
|
Host
|
data/lib/grape/locale/en.yml
CHANGED
@@ -10,6 +10,10 @@ en:
|
|
10
10
|
values: 'does not have a valid value'
|
11
11
|
except_values: 'has a value not allowed'
|
12
12
|
same_as: 'is not the same as %{parameter}'
|
13
|
+
length: 'is expected to have length within %{min} and %{max}'
|
14
|
+
length_is: 'is expected to have length exactly equal to %{is}'
|
15
|
+
length_min: 'is expected to have length greater than or equal to %{min}'
|
16
|
+
length_max: 'is expected to have length less than or equal to %{max}'
|
13
17
|
missing_vendor_option:
|
14
18
|
problem: 'missing :vendor option'
|
15
19
|
summary: 'when version using header, you must specify :vendor option'
|
@@ -1,23 +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
6
|
include Helpers
|
7
|
+
include Grape::DSL::Headers
|
9
8
|
|
10
9
|
attr_reader :app, :env, :options
|
11
10
|
|
12
11
|
TEXT_HTML = 'text/html'
|
13
12
|
|
14
|
-
include Grape::DSL::Headers
|
15
|
-
|
16
13
|
# @param [Rack Application] app The standard argument for a Rack middleware.
|
17
14
|
# @param [Hash] options A hash of options, simply stored for use by subclasses.
|
18
15
|
def initialize(app, *options)
|
19
16
|
@app = app
|
20
|
-
@options = options.any? ? default_options.
|
17
|
+
@options = options.any? ? default_options.deep_merge(options.shift) : default_options
|
21
18
|
@app_response = nil
|
22
19
|
end
|
23
20
|
|
@@ -63,22 +60,20 @@ module Grape
|
|
63
60
|
@app_response = Rack::Response.new(@app_response[2], @app_response[0], @app_response[1])
|
64
61
|
end
|
65
62
|
|
66
|
-
def
|
67
|
-
|
63
|
+
def content_types
|
64
|
+
@content_types ||= Grape::ContentTypes.content_types_for(options[:content_types])
|
68
65
|
end
|
69
66
|
|
70
|
-
def
|
71
|
-
ContentTypes.
|
67
|
+
def mime_types
|
68
|
+
@mime_types ||= Grape::ContentTypes.mime_types_for(content_types)
|
72
69
|
end
|
73
70
|
|
74
|
-
def
|
75
|
-
|
71
|
+
def content_type_for(format)
|
72
|
+
content_types_indifferent_access[format]
|
76
73
|
end
|
77
74
|
|
78
|
-
def
|
79
|
-
|
80
|
-
types_without_params[v.split(';').first] = k
|
81
|
-
end
|
75
|
+
def content_type
|
76
|
+
content_type_for(env[Grape::Env::API_FORMAT] || options[:format]) || TEXT_HTML
|
82
77
|
end
|
83
78
|
|
84
79
|
private
|
@@ -91,6 +86,10 @@ module Grape
|
|
91
86
|
when Array then response[1].merge!(headers)
|
92
87
|
end
|
93
88
|
end
|
89
|
+
|
90
|
+
def content_types_indifferent_access
|
91
|
+
@content_types_indifferent_access ||= content_types.with_indifferent_access
|
92
|
+
end
|
94
93
|
end
|
95
94
|
end
|
96
95
|
end
|
@@ -1,8 +1,5 @@
|
|
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
|
@@ -29,111 +26,121 @@ module Grape
|
|
29
26
|
|
30
27
|
def initialize(app, *options)
|
31
28
|
super
|
32
|
-
self.class.
|
29
|
+
self.class.include(@options[:helpers]) if @options[:helpers]
|
33
30
|
end
|
34
31
|
|
35
32
|
def call!(env)
|
36
33
|
@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
|
34
|
+
error_response(catch(:error) { return @app.call(@env) })
|
35
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
36
|
+
run_rescue_handler(find_handler(e.class), e, @env[Grape::Env::API_ENDPOINT])
|
51
37
|
end
|
52
38
|
|
53
|
-
|
54
|
-
|
55
|
-
|
39
|
+
private
|
40
|
+
|
41
|
+
def rack_response(status, headers, message)
|
42
|
+
message = Rack::Utils.escape_html(message) if headers[Rack::CONTENT_TYPE] == TEXT_HTML
|
43
|
+
Rack::Response.new(Array.wrap(message), Rack::Utils.status_code(status), Grape::Util::Header.new.merge(headers))
|
44
|
+
end
|
45
|
+
|
46
|
+
def format_message(message, backtrace, original_exception = nil)
|
47
|
+
format = env[Grape::Env::API_FORMAT] || options[:format]
|
48
|
+
formatter = Grape::ErrorFormatter.formatter_for(format, options[:error_formatters], options[:default_error_formatter])
|
49
|
+
return formatter.call(message, backtrace, options, env, original_exception) if formatter
|
50
|
+
|
51
|
+
throw :error,
|
52
|
+
status: 406,
|
53
|
+
message: "The requested format '#{format}' is not supported.",
|
54
|
+
backtrace: backtrace,
|
55
|
+
original_exception: original_exception
|
56
56
|
end
|
57
57
|
|
58
|
-
def
|
59
|
-
|
58
|
+
def find_handler(klass)
|
59
|
+
rescue_handler_for_base_only_class(klass) ||
|
60
|
+
rescue_handler_for_class_or_its_ancestor(klass) ||
|
61
|
+
rescue_handler_for_grape_exception(klass) ||
|
62
|
+
rescue_handler_for_any_class(klass) ||
|
63
|
+
raise
|
60
64
|
end
|
61
65
|
|
62
|
-
# TODO: This method is deprecated. Refactor out.
|
63
66
|
def error_response(error = {})
|
64
67
|
status = error[:status] || options[:default_status]
|
65
68
|
message = error[:message] || options[:default_message]
|
66
|
-
headers = { Rack::CONTENT_TYPE => content_type }
|
67
|
-
|
69
|
+
headers = { Rack::CONTENT_TYPE => content_type }.tap do |h|
|
70
|
+
h.merge!(error[:headers]) if error[:headers].is_a?(Hash)
|
71
|
+
end
|
68
72
|
backtrace = error[:backtrace] || error[:original_exception]&.backtrace || []
|
69
73
|
original_exception = error.is_a?(Exception) ? error : error[:original_exception] || nil
|
70
|
-
rack_response(format_message(message, backtrace, original_exception)
|
71
|
-
end
|
72
|
-
|
73
|
-
def rack_response(message, status = options[:default_status], headers = { Rack::CONTENT_TYPE => content_type })
|
74
|
-
message = ERB::Util.html_escape(message) if headers[Rack::CONTENT_TYPE] == TEXT_HTML
|
75
|
-
Rack::Response.new([message], Rack::Utils.status_code(status), headers)
|
74
|
+
rack_response(status, headers, format_message(message, backtrace, original_exception))
|
76
75
|
end
|
77
76
|
|
78
|
-
def
|
79
|
-
|
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)
|
77
|
+
def default_rescue_handler(exception)
|
78
|
+
error_response(message: exception.message, backtrace: exception.backtrace, original_exception: exception)
|
87
79
|
end
|
88
80
|
|
89
|
-
private
|
90
|
-
|
91
81
|
def rescue_handler_for_base_only_class(klass)
|
92
|
-
error, handler = options[:base_only_rescue_handlers]
|
82
|
+
error, handler = options[:base_only_rescue_handlers]&.find { |err, _handler| klass == err }
|
93
83
|
|
94
84
|
return unless error
|
95
85
|
|
96
|
-
handler || :default_rescue_handler
|
86
|
+
handler || method(:default_rescue_handler)
|
97
87
|
end
|
98
88
|
|
99
89
|
def rescue_handler_for_class_or_its_ancestor(klass)
|
100
|
-
error, handler = options[:rescue_handlers]
|
90
|
+
error, handler = options[:rescue_handlers]&.find { |err, _handler| klass <= err }
|
101
91
|
|
102
92
|
return unless error
|
103
93
|
|
104
|
-
handler || :default_rescue_handler
|
94
|
+
handler || method(:default_rescue_handler)
|
105
95
|
end
|
106
96
|
|
107
97
|
def rescue_handler_for_grape_exception(klass)
|
108
98
|
return unless klass <= Grape::Exceptions::Base
|
109
|
-
return :error_response if klass == Grape::Exceptions::InvalidVersionHeader
|
99
|
+
return method(:error_response) if klass == Grape::Exceptions::InvalidVersionHeader
|
110
100
|
return unless options[:rescue_grape_exceptions] || !options[:rescue_all]
|
111
101
|
|
112
|
-
options[:grape_exceptions_rescue_handler] || :error_response
|
102
|
+
options[:grape_exceptions_rescue_handler] || method(:error_response)
|
113
103
|
end
|
114
104
|
|
115
105
|
def rescue_handler_for_any_class(klass)
|
116
106
|
return unless klass <= StandardError
|
117
107
|
return unless options[:rescue_all] || options[:rescue_grape_exceptions]
|
118
108
|
|
119
|
-
options[:all_rescue_handler] || :default_rescue_handler
|
109
|
+
options[:all_rescue_handler] || method(:default_rescue_handler)
|
120
110
|
end
|
121
111
|
|
122
|
-
def run_rescue_handler(handler, error)
|
112
|
+
def run_rescue_handler(handler, error, endpoint)
|
123
113
|
if handler.instance_of?(Symbol)
|
124
114
|
raise NoMethodError, "undefined method '#{handler}'" unless respond_to?(handler)
|
125
115
|
|
126
116
|
handler = public_method(handler)
|
127
117
|
end
|
128
118
|
|
129
|
-
response =
|
119
|
+
response = catch(:error) do
|
120
|
+
handler.arity.zero? ? endpoint.instance_exec(&handler) : endpoint.instance_exec(error, &handler)
|
121
|
+
end
|
130
122
|
|
131
|
-
if
|
123
|
+
if error?(response)
|
124
|
+
error_response(response)
|
125
|
+
elsif response.is_a?(Rack::Response)
|
132
126
|
response
|
133
127
|
else
|
134
|
-
run_rescue_handler(:default_rescue_handler, Grape::Exceptions::InvalidResponse.new)
|
128
|
+
run_rescue_handler(method(:default_rescue_handler), Grape::Exceptions::InvalidResponse.new, endpoint)
|
135
129
|
end
|
136
130
|
end
|
131
|
+
|
132
|
+
def error!(message, status = options[:default_status], headers = {}, backtrace = [], original_exception = nil)
|
133
|
+
rack_response(
|
134
|
+
status, headers.reverse_merge(Rack::CONTENT_TYPE => content_type),
|
135
|
+
format_message(message, backtrace, original_exception)
|
136
|
+
)
|
137
|
+
end
|
138
|
+
|
139
|
+
def error?(response)
|
140
|
+
return false unless response.is_a?(Hash)
|
141
|
+
|
142
|
+
response.key?(:message) && response.key?(:status) && response.key?(:headers)
|
143
|
+
end
|
137
144
|
end
|
138
145
|
end
|
139
146
|
end
|
@@ -1,11 +1,10 @@
|
|
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
6
|
CHUNKED = 'chunked'
|
7
|
+
FORMAT = 'format'
|
9
8
|
|
10
9
|
def default_options
|
11
10
|
{
|
@@ -26,7 +25,7 @@ module Grape
|
|
26
25
|
status, headers, bodies = *@app_response
|
27
26
|
|
28
27
|
if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status)
|
29
|
-
|
28
|
+
[status, headers, []]
|
30
29
|
else
|
31
30
|
build_formatted_response(status, headers, bodies)
|
32
31
|
end
|
@@ -55,7 +54,7 @@ module Grape
|
|
55
54
|
|
56
55
|
def fetch_formatter(headers, options)
|
57
56
|
api_format = mime_types[headers[Rack::CONTENT_TYPE]] || env[Grape::Env::API_FORMAT]
|
58
|
-
Grape::Formatter.formatter_for(api_format,
|
57
|
+
Grape::Formatter.formatter_for(api_format, options[:formatters])
|
59
58
|
end
|
60
59
|
|
61
60
|
# Set the content type header for the API format if it is not already present.
|
@@ -82,14 +81,14 @@ module Grape
|
|
82
81
|
!request.parseable_data? &&
|
83
82
|
(request.content_length.to_i.positive? || request.env[Grape::Http::Headers::HTTP_TRANSFER_ENCODING] == CHUNKED)
|
84
83
|
|
85
|
-
return unless (input = env[
|
84
|
+
return unless (input = env[Rack::RACK_INPUT])
|
86
85
|
|
87
|
-
input
|
86
|
+
rewind_input input
|
88
87
|
body = env[Grape::Env::API_REQUEST_INPUT] = input.read
|
89
88
|
begin
|
90
89
|
read_rack_input(body) if body && !body.empty?
|
91
90
|
ensure
|
92
|
-
input
|
91
|
+
rewind_input input
|
93
92
|
end
|
94
93
|
end
|
95
94
|
|
@@ -98,17 +97,17 @@ module Grape
|
|
98
97
|
fmt = request.media_type ? mime_types[request.media_type] : options[:default_format]
|
99
98
|
|
100
99
|
throw :error, status: 415, message: "The provided content-type '#{request.media_type}' is not supported." unless content_type_for(fmt)
|
101
|
-
parser = Grape::Parser.parser_for fmt,
|
100
|
+
parser = Grape::Parser.parser_for fmt, options[:parsers]
|
102
101
|
if parser
|
103
102
|
begin
|
104
103
|
body = (env[Grape::Env::API_REQUEST_BODY] = parser.call(body, env))
|
105
104
|
if body.is_a?(Hash)
|
106
|
-
env[
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
env[
|
105
|
+
env[Rack::RACK_REQUEST_FORM_HASH] = if env.key?(Rack::RACK_REQUEST_FORM_HASH)
|
106
|
+
env[Rack::RACK_REQUEST_FORM_HASH].merge(body)
|
107
|
+
else
|
108
|
+
body
|
109
|
+
end
|
110
|
+
env[Rack::RACK_REQUEST_FORM_INPUT] = env[Rack::RACK_INPUT]
|
112
111
|
end
|
113
112
|
rescue Grape::Exceptions::Base => e
|
114
113
|
raise e
|
@@ -141,7 +140,7 @@ module Grape
|
|
141
140
|
end
|
142
141
|
|
143
142
|
def format_from_params
|
144
|
-
fmt = Rack::Utils.parse_nested_query(env[
|
143
|
+
fmt = Rack::Utils.parse_nested_query(env[Rack::QUERY_STRING])[FORMAT]
|
145
144
|
# avoid symbol memory leak on an unknown format
|
146
145
|
return fmt.to_sym if content_type_for(fmt)
|
147
146
|
|
@@ -174,6 +173,10 @@ module Grape
|
|
174
173
|
.sort_by { |_, quality_preference| -(quality_preference ? quality_preference.to_f : 1.0) }
|
175
174
|
.flat_map { |mime, _| [mime, mime.sub(vendor_prefix_pattern, '')] }
|
176
175
|
end
|
176
|
+
|
177
|
+
def rewind_input(input)
|
178
|
+
input.rewind if input.respond_to?(:rewind)
|
179
|
+
end
|
177
180
|
end
|
178
181
|
end
|
179
182
|
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
|
@@ -57,8 +57,8 @@ module Grape
|
|
57
57
|
middlewares.last
|
58
58
|
end
|
59
59
|
|
60
|
-
def [](
|
61
|
-
middlewares[
|
60
|
+
def [](index)
|
61
|
+
middlewares[index]
|
62
62
|
end
|
63
63
|
|
64
64
|
def insert(index, *args, &block)
|
@@ -76,11 +76,10 @@ module Grape
|
|
76
76
|
end
|
77
77
|
ruby2_keywords :insert_after if respond_to?(:ruby2_keywords, true)
|
78
78
|
|
79
|
-
def use(
|
80
|
-
middleware = self.class::Middleware.new(
|
79
|
+
def use(...)
|
80
|
+
middleware = self.class::Middleware.new(...)
|
81
81
|
middlewares.push(middleware)
|
82
82
|
end
|
83
|
-
ruby2_keywords :use if respond_to?(:ruby2_keywords, true)
|
84
83
|
|
85
84
|
def merge_with(middleware_specs)
|
86
85
|
middleware_specs.each do |operation, *args|
|
@@ -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
|
module Versioner
|
@@ -19,45 +17,22 @@ module Grape
|
|
19
17
|
# X-Cascade header to alert Grape::Router to attempt the next matched
|
20
18
|
# route.
|
21
19
|
class AcceptVersionHeader < Base
|
22
|
-
|
23
|
-
potential_version = (env[Grape::Http::Headers::HTTP_ACCEPT_VERSION] || '').strip
|
24
|
-
|
25
|
-
if strict? && potential_version.empty?
|
26
|
-
# If no Accept-Version header:
|
27
|
-
throw :error, status: 406, headers: error_headers, message: 'Accept-Version header must be set.'
|
28
|
-
end
|
20
|
+
include VersionerHelpers
|
29
21
|
|
30
|
-
|
22
|
+
def before
|
23
|
+
potential_version = env[Grape::Http::Headers::HTTP_ACCEPT_VERSION]&.strip
|
24
|
+
not_acceptable!('Accept-Version header must be set.') if strict? && potential_version.blank?
|
31
25
|
|
32
|
-
|
33
|
-
throw :error, status: 406, headers: error_headers, message: 'The requested version is not supported.' unless versions.any? { |v| v.to_s == potential_version }
|
26
|
+
return if potential_version.blank?
|
34
27
|
|
28
|
+
not_acceptable!('The requested version is not supported.') unless potential_version_match?(potential_version)
|
35
29
|
env[Grape::Env::API_VERSION] = potential_version
|
36
30
|
end
|
37
31
|
|
38
32
|
private
|
39
33
|
|
40
|
-
def
|
41
|
-
|
42
|
-
end
|
43
|
-
|
44
|
-
def strict?
|
45
|
-
options[:version_options] && options[:version_options][:strict]
|
46
|
-
end
|
47
|
-
|
48
|
-
# By default those errors contain an `X-Cascade` header set to `pass`, which allows nesting and stacking
|
49
|
-
# of routes (see Grape::Router) for more information). To prevent
|
50
|
-
# this behavior, and not add the `X-Cascade` header, one can set the `:cascade` option to `false`.
|
51
|
-
def cascade?
|
52
|
-
if options[:version_options]&.key?(:cascade)
|
53
|
-
options[:version_options][:cascade]
|
54
|
-
else
|
55
|
-
true
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
def error_headers
|
60
|
-
cascade? ? { Grape::Http::Headers::X_CASCADE => 'pass' } : {}
|
34
|
+
def not_acceptable!(message)
|
35
|
+
throw :error, status: 406, headers: error_headers, message: message
|
61
36
|
end
|
62
37
|
end
|
63
38
|
end
|