grape 2.0.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
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