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.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +96 -1
  3. data/README.md +364 -317
  4. data/UPGRADING.md +205 -7
  5. data/grape.gemspec +7 -7
  6. data/lib/grape/api/instance.rb +14 -11
  7. data/lib/grape/api.rb +19 -10
  8. data/lib/grape/content_types.rb +13 -10
  9. data/lib/grape/cookies.rb +2 -1
  10. data/lib/grape/dry_types.rb +0 -2
  11. data/lib/grape/dsl/desc.rb +22 -20
  12. data/lib/grape/dsl/headers.rb +1 -1
  13. data/lib/grape/dsl/helpers.rb +7 -3
  14. data/lib/grape/dsl/inside_route.rb +51 -15
  15. data/lib/grape/dsl/parameters.rb +5 -4
  16. data/lib/grape/dsl/request_response.rb +14 -18
  17. data/lib/grape/dsl/routing.rb +20 -4
  18. data/lib/grape/dsl/validations.rb +13 -0
  19. data/lib/grape/endpoint.rb +43 -35
  20. data/lib/grape/{util/env.rb → env.rb} +0 -5
  21. data/lib/grape/error_formatter/json.rb +13 -4
  22. data/lib/grape/error_formatter/txt.rb +11 -10
  23. data/lib/grape/error_formatter.rb +13 -25
  24. data/lib/grape/exceptions/base.rb +3 -3
  25. data/lib/grape/exceptions/validation.rb +0 -2
  26. data/lib/grape/exceptions/validation_array_errors.rb +1 -0
  27. data/lib/grape/exceptions/validation_errors.rb +2 -4
  28. data/lib/grape/extensions/hash.rb +5 -1
  29. data/lib/grape/formatter.rb +15 -25
  30. data/lib/grape/http/headers.rb +18 -34
  31. data/lib/grape/{util/json.rb → json.rb} +1 -3
  32. data/lib/grape/locale/en.yml +4 -0
  33. data/lib/grape/middleware/auth/base.rb +0 -2
  34. data/lib/grape/middleware/auth/dsl.rb +0 -2
  35. data/lib/grape/middleware/base.rb +14 -15
  36. data/lib/grape/middleware/error.rb +61 -54
  37. data/lib/grape/middleware/formatter.rb +18 -15
  38. data/lib/grape/middleware/globals.rb +1 -3
  39. data/lib/grape/middleware/stack.rb +4 -5
  40. data/lib/grape/middleware/versioner/accept_version_header.rb +8 -33
  41. data/lib/grape/middleware/versioner/header.rb +62 -123
  42. data/lib/grape/middleware/versioner/param.rb +5 -23
  43. data/lib/grape/middleware/versioner/path.rb +11 -33
  44. data/lib/grape/middleware/versioner.rb +5 -14
  45. data/lib/grape/middleware/versioner_helpers.rb +75 -0
  46. data/lib/grape/namespace.rb +3 -4
  47. data/lib/grape/parser.rb +8 -24
  48. data/lib/grape/path.rb +24 -29
  49. data/lib/grape/request.rb +4 -12
  50. data/lib/grape/router/base_route.rb +39 -0
  51. data/lib/grape/router/greedy_route.rb +20 -0
  52. data/lib/grape/router/pattern.rb +39 -30
  53. data/lib/grape/router/route.rb +22 -59
  54. data/lib/grape/router.rb +32 -37
  55. data/lib/grape/util/base_inheritable.rb +4 -4
  56. data/lib/grape/util/cache.rb +0 -3
  57. data/lib/grape/util/endpoint_configuration.rb +1 -1
  58. data/lib/grape/util/header.rb +13 -0
  59. data/lib/grape/util/inheritable_values.rb +0 -2
  60. data/lib/grape/util/lazy/block.rb +29 -0
  61. data/lib/grape/util/lazy/object.rb +45 -0
  62. data/lib/grape/util/lazy/value.rb +38 -0
  63. data/lib/grape/util/lazy/value_array.rb +21 -0
  64. data/lib/grape/util/lazy/value_enumerable.rb +34 -0
  65. data/lib/grape/util/lazy/value_hash.rb +21 -0
  66. data/lib/grape/util/media_type.rb +70 -0
  67. data/lib/grape/util/reverse_stackable_values.rb +1 -6
  68. data/lib/grape/util/stackable_values.rb +1 -6
  69. data/lib/grape/util/strict_hash_configuration.rb +3 -3
  70. data/lib/grape/validations/attributes_doc.rb +38 -36
  71. data/lib/grape/validations/attributes_iterator.rb +1 -0
  72. data/lib/grape/validations/contract_scope.rb +71 -0
  73. data/lib/grape/validations/params_scope.rb +22 -19
  74. data/lib/grape/validations/types/array_coercer.rb +0 -2
  75. data/lib/grape/validations/types/build_coercer.rb +69 -71
  76. data/lib/grape/validations/types/dry_type_coercer.rb +1 -11
  77. data/lib/grape/validations/types/json.rb +0 -2
  78. data/lib/grape/validations/types/primitive_coercer.rb +0 -2
  79. data/lib/grape/validations/types/set_coercer.rb +0 -3
  80. data/lib/grape/validations/types.rb +0 -3
  81. data/lib/grape/validations/validators/base.rb +1 -0
  82. data/lib/grape/validations/validators/default_validator.rb +5 -1
  83. data/lib/grape/validations/validators/exactly_one_of_validator.rb +1 -1
  84. data/lib/grape/validations/validators/length_validator.rb +49 -0
  85. data/lib/grape/validations/validators/values_validator.rb +6 -1
  86. data/lib/grape/validations.rb +3 -7
  87. data/lib/grape/version.rb +1 -1
  88. data/lib/grape/{util/xml.rb → xml.rb} +1 -1
  89. data/lib/grape.rb +30 -274
  90. metadata +31 -38
  91. data/lib/grape/eager_load.rb +0 -20
  92. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +0 -24
  93. data/lib/grape/router/attribute_translator.rb +0 -63
  94. data/lib/grape/util/lazy_block.rb +0 -27
  95. data/lib/grape/util/lazy_object.rb +0 -43
  96. data/lib/grape/util/lazy_value.rb +0 -91
  97. data/lib/grape/util/registrable.rb +0 -15
@@ -2,34 +2,24 @@
2
2
 
3
3
  module Grape
4
4
  module Formatter
5
- extend Util::Registrable
5
+ module_function
6
6
 
7
- class << self
8
- def builtin_formatters
9
- @builtin_formatters ||= {
10
- json: Grape::Formatter::Json,
11
- jsonapi: Grape::Formatter::Json,
12
- serializable_hash: Grape::Formatter::SerializableHash,
13
- txt: Grape::Formatter::Txt,
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
- def formatters(**options)
19
- builtin_formatters.merge(default_elements).merge!(options[:formatters] || {})
20
- end
15
+ DEFAULT_LAMBDA_FORMATTER = ->(obj, _env) { obj }
21
16
 
22
- def formatter_for(api_format, **options)
23
- spec = formatters(**options)[api_format]
24
- case spec
25
- when nil
26
- ->(obj, _env) { obj }
27
- when Symbol
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
@@ -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
- # https://github.com/rack/rack/blob/master/lib/rack.rb
9
- HTTP_VERSION = 'HTTP_VERSION'
10
- PATH_INFO = 'PATH_INFO'
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
- SUPPORTED_METHODS = [GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS].freeze
35
- SUPPORTED_METHODS_WITHOUT_OPTIONS = Grape::Util::LazyObject.new { [GET, POST, PUT, PATCH, DELETE, HEAD].freeze }
10
+ ALLOW = 'Allow'
11
+ LOCATION = 'Location'
12
+ X_CASCADE = 'X-Cascade'
13
+ TRANSFER_ENCODING = 'Transfer-Encoding'
36
14
 
37
- HTTP_ACCEPT_VERSION = 'HTTP_ACCEPT_VERSION'
38
- HTTP_TRANSFER_ENCODING = 'HTTP_TRANSFER_ENCODING'
39
- HTTP_ACCEPT = 'HTTP_ACCEPT'
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
- FORMAT = 'format'
25
+ SUPPORTED_METHODS_WITHOUT_OPTIONS = (SUPPORTED_METHODS - [Rack::OPTIONS]).freeze
42
26
 
43
- HTTP_HEADERS = Grape::Util::LazyObject.new do
27
+ HTTP_HEADERS = Grape::Util::Lazy::Object.new do
44
28
  common_http_headers = %w[
45
29
  Version
46
30
  Host
@@ -1,9 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
4
-
5
3
  module Grape
6
- if Object.const_defined? :MultiJson
4
+ if defined?(::MultiJson)
7
5
  Json = ::MultiJson
8
6
  else
9
7
  Json = ::JSON
@@ -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,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rack/auth/basic'
4
-
5
3
  module Grape
6
4
  module Middleware
7
5
  module Auth
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rack/auth/basic'
4
-
5
3
  module Grape
6
4
  module Middleware
7
5
  module Auth
@@ -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.merge(options.shift) : 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 content_type_for(format)
67
- HashWithIndifferentAccess.new(content_types)[format]
63
+ def content_types
64
+ @content_types ||= Grape::ContentTypes.content_types_for(options[:content_types])
68
65
  end
69
66
 
70
- def content_types
71
- ContentTypes.content_types_for(options[:content_types])
67
+ def mime_types
68
+ @mime_types ||= Grape::ContentTypes.mime_types_for(content_types)
72
69
  end
73
70
 
74
- def content_type
75
- content_type_for(env[Grape::Env::API_FORMAT] || options[:format]) || TEXT_HTML
71
+ def content_type_for(format)
72
+ content_types_indifferent_access[format]
76
73
  end
77
74
 
78
- def mime_types
79
- @mime_type ||= content_types.each_pair.with_object({}) do |(k, v), types_without_params|
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.send(:include, @options[:helpers]) if @options[:helpers]
29
+ self.class.include(@options[:helpers]) if @options[:helpers]
33
30
  end
34
31
 
35
32
  def call!(env)
36
33
  @env = env
37
- begin
38
- error_response(catch(:error) do
39
- return @app.call(@env)
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
- def error!(message, status = options[:default_status], headers = {}, backtrace = [], original_exception = nil)
54
- headers = headers.reverse_merge(Rack::CONTENT_TYPE => content_type)
55
- rack_response(format_message(message, backtrace, original_exception), status, headers)
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 default_rescue_handler(e)
59
- error_response(message: e.message, backtrace: e.backtrace, original_exception: e)
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
- headers.merge!(error[:headers]) if error[:headers].is_a?(Hash)
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), status, headers)
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 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)
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].find { |err, _handler| klass == err }
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].find { |err, _handler| klass <= err }
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 = handler.arity.zero? ? instance_exec(&handler) : instance_exec(error, &handler)
119
+ response = catch(:error) do
120
+ handler.arity.zero? ? endpoint.instance_exec(&handler) : endpoint.instance_exec(error, &handler)
121
+ end
130
122
 
131
- if response.is_a?(Rack::Response)
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
- @app_response
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, **options)
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[Grape::Env::RACK_INPUT])
84
+ return unless (input = env[Rack::RACK_INPUT])
86
85
 
87
- input.rewind
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.rewind
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, **options
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[Grape::Env::RACK_REQUEST_FORM_HASH] = if env.key?(Grape::Env::RACK_REQUEST_FORM_HASH)
107
- env[Grape::Env::RACK_REQUEST_FORM_HASH].merge(body)
108
- else
109
- body
110
- end
111
- env[Grape::Env::RACK_REQUEST_FORM_INPUT] = env[Grape::Env::RACK_INPUT]
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[Grape::Http::Headers::QUERY_STRING])[Grape::Http::Headers::FORMAT]
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[Grape::Env::RACK_INPUT]
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 [](i)
61
- middlewares[i]
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(*args, &block)
80
- middleware = self.class::Middleware.new(*args, &block)
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
- def before
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
- return if potential_version.empty?
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
- # If the requested version is not supported:
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 versions
41
- options[:versions] || []
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