grape 2.3.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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -0
  3. data/CONTRIBUTING.md +1 -1
  4. data/README.md +36 -14
  5. data/UPGRADING.md +56 -1
  6. data/grape.gemspec +1 -1
  7. data/lib/grape/api/instance.rb +3 -2
  8. data/lib/grape/api.rb +43 -66
  9. data/lib/grape/cookies.rb +31 -25
  10. data/lib/grape/dsl/api.rb +0 -2
  11. data/lib/grape/dsl/headers.rb +1 -1
  12. data/lib/grape/dsl/helpers.rb +1 -1
  13. data/lib/grape/dsl/inside_route.rb +6 -18
  14. data/lib/grape/dsl/parameters.rb +3 -3
  15. data/lib/grape/dsl/routing.rb +9 -1
  16. data/lib/grape/endpoint.rb +30 -33
  17. data/lib/grape/exceptions/conflicting_types.rb +11 -0
  18. data/lib/grape/exceptions/invalid_parameters.rb +11 -0
  19. data/lib/grape/exceptions/too_deep_parameters.rb +11 -0
  20. data/lib/grape/exceptions/unknown_auth_strategy.rb +11 -0
  21. data/lib/grape/exceptions/unknown_params_builder.rb +11 -0
  22. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +2 -5
  23. data/lib/grape/extensions/hash.rb +2 -1
  24. data/lib/grape/extensions/hashie/mash.rb +3 -5
  25. data/lib/grape/locale/en.yml +44 -44
  26. data/lib/grape/middleware/auth/base.rb +11 -32
  27. data/lib/grape/middleware/auth/dsl.rb +23 -29
  28. data/lib/grape/middleware/base.rb +30 -11
  29. data/lib/grape/middleware/error.rb +16 -24
  30. data/lib/grape/middleware/formatter.rb +38 -72
  31. data/lib/grape/middleware/stack.rb +26 -36
  32. data/lib/grape/middleware/versioner/accept_version_header.rb +1 -3
  33. data/lib/grape/middleware/versioner/base.rb +10 -18
  34. data/lib/grape/middleware/versioner/header.rb +1 -1
  35. data/lib/grape/middleware/versioner/param.rb +2 -3
  36. data/lib/grape/params_builder/base.rb +18 -0
  37. data/lib/grape/params_builder/hash.rb +11 -0
  38. data/lib/grape/params_builder/hash_with_indifferent_access.rb +11 -0
  39. data/lib/grape/params_builder/hashie_mash.rb +11 -0
  40. data/lib/grape/params_builder.rb +32 -0
  41. data/lib/grape/request.rb +161 -22
  42. data/lib/grape/router/route.rb +1 -1
  43. data/lib/grape/router.rb +25 -7
  44. data/lib/grape/validations/params_scope.rb +8 -3
  45. data/lib/grape/validations/validators/base.rb +2 -2
  46. data/lib/grape/validations/validators/except_values_validator.rb +1 -1
  47. data/lib/grape/validations/validators/presence_validator.rb +1 -1
  48. data/lib/grape/validations/validators/regexp_validator.rb +1 -1
  49. data/lib/grape/version.rb +1 -1
  50. data/lib/grape.rb +13 -1
  51. metadata +18 -13
  52. data/lib/grape/error_formatter/jsonapi.rb +0 -7
  53. data/lib/grape/http/headers.rb +0 -56
  54. data/lib/grape/middleware/helpers.rb +0 -12
  55. data/lib/grape/parser/jsonapi.rb +0 -7
  56. data/lib/grape/util/lazy/object.rb +0 -45
@@ -3,16 +3,9 @@
3
3
  module Grape
4
4
  module Middleware
5
5
  class Formatter < Base
6
- CHUNKED = 'chunked'
7
- FORMAT = 'format'
8
-
9
- def default_options
10
- {
11
- default_format: :txt,
12
- formatters: {},
13
- parsers: {}
14
- }
15
- end
6
+ DEFAULT_OPTIONS = {
7
+ default_format: :txt
8
+ }.freeze
16
9
 
17
10
  def before
18
11
  negotiate_content_type
@@ -69,34 +62,27 @@ module Grape
69
62
  end
70
63
  end
71
64
 
72
- def request
73
- @request ||= Rack::Request.new(env)
74
- end
75
-
76
- # store read input in env['api.request.input']
77
65
  def read_body_input
78
- return unless
79
- (request.post? || request.put? || request.patch? || request.delete?) &&
80
- (!request.form_data? || !request.media_type) &&
81
- !request.parseable_data? &&
82
- (request.content_length.to_i.positive? || request.env[Grape::Http::Headers::HTTP_TRANSFER_ENCODING] == CHUNKED)
66
+ input = rack_request.body # reads RACK_INPUT
67
+ return if input.nil?
68
+ return unless read_body_input?
83
69
 
84
- return unless (input = env[Rack::RACK_INPUT])
85
-
86
- rewind_input input
70
+ input.try(:rewind)
87
71
  body = env[Grape::Env::API_REQUEST_INPUT] = input.read
88
72
  begin
89
- read_rack_input(body) if body && !body.empty?
73
+ read_rack_input(body)
90
74
  ensure
91
- rewind_input input
75
+ input.try(:rewind)
92
76
  end
93
77
  end
94
78
 
95
- # store parsed input in env['api.request.body']
96
79
  def read_rack_input(body)
97
- fmt = request.media_type ? mime_types[request.media_type] : options[:default_format]
80
+ return if body.empty?
81
+
82
+ media_type = rack_request.media_type
83
+ fmt = media_type ? mime_types[media_type] : options[:default_format]
98
84
 
99
- throw :error, status: 415, message: "The provided content-type '#{request.media_type}' is not supported." unless content_type_for(fmt)
85
+ throw :error, status: 415, message: "The provided content-type '#{media_type}' is not supported." unless content_type_for(fmt)
100
86
  parser = Grape::Parser.parser_for fmt, options[:parsers]
101
87
  if parser
102
88
  begin
@@ -119,63 +105,43 @@ module Grape
119
105
  end
120
106
  end
121
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
+
122
121
  def negotiate_content_type
123
- fmt = format_from_extension || format_from_params || options[:format] || format_from_header || options[:default_format]
122
+ fmt = format_from_extension || query_params['format'] || options[:format] || format_from_header || options[:default_format]
124
123
  if content_type_for(fmt)
125
- env[Grape::Env::API_FORMAT] = fmt
124
+ env[Grape::Env::API_FORMAT] = fmt.to_sym
126
125
  else
127
126
  throw :error, status: 406, message: "The requested format '#{fmt}' is not supported."
128
127
  end
129
128
  end
130
129
 
131
130
  def format_from_extension
132
- parts = request.path.split('.')
133
-
134
- if parts.size > 1
135
- extension = parts.last
136
- # avoid symbol memory leak on an unknown format
137
- return extension.to_sym if content_type_for(extension)
138
- end
139
- nil
140
- end
141
-
142
- def format_from_params
143
- fmt = Rack::Utils.parse_nested_query(env[Rack::QUERY_STRING])[FORMAT]
144
- # avoid symbol memory leak on an unknown format
145
- return fmt.to_sym if content_type_for(fmt)
131
+ request_path = rack_request.path.try(:scrub)
132
+ dot_pos = request_path.rindex('.')
133
+ return unless dot_pos
146
134
 
147
- fmt
135
+ extension = request_path[dot_pos + 1..]
136
+ extension if content_type_for(extension)
148
137
  end
149
138
 
150
139
  def format_from_header
151
- mime_array.each do |t|
152
- return mime_types[t] if mime_types.key?(t)
153
- end
154
- nil
155
- end
156
-
157
- def mime_array
158
- accept = env[Grape::Http::Headers::HTTP_ACCEPT]
159
- return [] unless accept
160
-
161
- accept_into_mime_and_quality = %r{
162
- (
163
- \w+/[\w+.-]+) # eg application/vnd.example.myformat+xml
164
- (?:
165
- (?:;[^,]*?)? # optionally multiple formats in a row
166
- ;\s*q=([\w.]+) # optional "quality" preference (eg q=0.5)
167
- )?
168
- }x
169
-
170
- vendor_prefix_pattern = /vnd\.[^+]+\+/
171
-
172
- accept.scan(accept_into_mime_and_quality)
173
- .sort_by { |_, quality_preference| -(quality_preference ? quality_preference.to_f : 1.0) }
174
- .flat_map { |mime, _| [mime, mime.sub(vendor_prefix_pattern, '')] }
175
- end
140
+ accept_header = env['HTTP_ACCEPT'].try(:scrub)
141
+ return if accept_header.blank?
176
142
 
177
- def rewind_input(input)
178
- input.rewind if input.respond_to?(:rewind)
143
+ media_type = Rack::Utils.best_q_match(accept_header, mime_types.keys)
144
+ mime_types[media_type] if media_type
179
145
  end
180
146
  end
181
147
  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, *args, &block)
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 use_in(builder)
35
- builder.use(@klass, *@args, &@block)
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 each(&block)
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 [](index)
61
- middlewares[index]
62
- end
63
-
64
- def insert(index, *args, &block)
55
+ def insert(index, klass, *args, &block)
65
56
  index = assert_index(index, :before)
66
- middleware = self.class::Middleware.new(*args, &block)
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,38 +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(...)
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
71
 
84
72
  def merge_with(middleware_specs)
85
- middleware_specs.each do |operation, *args|
73
+ middleware_specs.each do |operation, klass, *args|
86
74
  if args.last.is_a?(Proc)
87
75
  last_proc = args.pop
88
- public_send(operation, *args, &last_proc)
76
+ public_send(operation, klass, *args, &last_proc)
89
77
  else
90
- public_send(operation, *args)
78
+ public_send(operation, klass, *args)
91
79
  end
92
80
  end
93
81
  end
94
82
 
95
83
  # @return [Rack::Builder] the builder object with our middlewares applied
96
- def build(builder = Rack::Builder.new)
97
- others.shift(others.size).each { |m| merge_with(m) }
98
- middlewares.each do |m|
99
- m.use_in(builder)
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
100
90
  end
101
- builder
102
91
  end
103
92
 
104
93
  # @description Add middlewares with :use operation to the stack. Store others with :insert_* operation for later
105
94
  # @param [Array] other_specs An array of middleware specifications (e.g. [[:use, klass], [:insert_before, *args]])
106
95
  def concat(other_specs)
107
- @others << Array(other_specs).reject { |o| o.first == :use }
108
- merge_with(Array(other_specs).select { |o| o.first == :use })
96
+ use, not_use = other_specs.partition { |o| o.first == :use }
97
+ others << not_use
98
+ merge_with(use)
109
99
  end
110
100
 
111
101
  protected
@@ -18,9 +18,7 @@ module Grape
18
18
  # route.
19
19
  class AcceptVersionHeader < Base
20
20
  def before
21
- potential_version = env[Grape::Http::Headers::HTTP_ACCEPT_VERSION]
22
- potential_version = potential_version.scrub unless potential_version.nil?
23
-
21
+ potential_version = env['HTTP_ACCEPT_VERSION'].try(:scrub)
24
22
  not_acceptable!('Accept-Version header must be set.') if strict? && potential_version.blank?
25
23
 
26
24
  return if potential_version.blank?
@@ -4,28 +4,20 @@ module Grape
4
4
  module Middleware
5
5
  module Versioner
6
6
  class Base < Grape::Middleware::Base
7
- DEFAULT_PATTERN = /.*/i.freeze
8
- DEFAULT_PARAMETER = 'apiver'
7
+ DEFAULT_OPTIONS = {
8
+ pattern: /.*/i.freeze,
9
+ version_options: {
10
+ strict: false,
11
+ cascade: true,
12
+ parameter: 'apiver'
13
+ }.freeze
14
+ }.freeze
9
15
 
10
16
  def self.inherited(klass)
11
17
  super
12
18
  Versioner.register(klass)
13
19
  end
14
20
 
15
- def default_options
16
- {
17
- versions: nil,
18
- prefix: nil,
19
- mount_path: nil,
20
- pattern: DEFAULT_PATTERN,
21
- version_options: {
22
- strict: false,
23
- cascade: true,
24
- parameter: DEFAULT_PARAMETER
25
- }
26
- }
27
- end
28
-
29
21
  def versions
30
22
  options[:versions]
31
23
  end
@@ -66,7 +58,7 @@ module Grape
66
58
  end
67
59
 
68
60
  def error_headers
69
- cascade? ? { Grape::Http::Headers::X_CASCADE => 'pass' } : {}
61
+ cascade? ? { 'X-Cascade' => 'pass' } : {}
70
62
  end
71
63
 
72
64
  def potential_version_match?(potential_version)
@@ -74,7 +66,7 @@ module Grape
74
66
  end
75
67
 
76
68
  def version_not_found!
77
- throw :error, status: 404, message: '404 API Version Not Found', headers: { Grape::Http::Headers::X_CASCADE => 'pass' }
69
+ throw :error, status: 404, message: '404 API Version Not Found', headers: { 'X-Cascade' => 'pass' }
78
70
  end
79
71
  end
80
72
  end
@@ -49,7 +49,7 @@ module Grape
49
49
  end
50
50
 
51
51
  def accept_header
52
- env[Grape::Http::Headers::HTTP_ACCEPT]
52
+ env['HTTP_ACCEPT']
53
53
  end
54
54
 
55
55
  def strict_header_checks!
@@ -20,12 +20,11 @@ module Grape
20
20
  # env['api.version'] => 'v1'
21
21
  class Param < Base
22
22
  def before
23
- potential_version = Rack::Utils.parse_nested_query(env[Rack::QUERY_STRING])[parameter_key]
23
+ potential_version = query_params[parameter_key]
24
24
  return if potential_version.blank?
25
25
 
26
26
  version_not_found! unless potential_version_match?(potential_version)
27
- env[Grape::Env::API_VERSION] = potential_version
28
- env[Rack::RACK_REQUEST_QUERY_HASH].delete(parameter_key) if env.key? Rack::RACK_REQUEST_QUERY_HASH
27
+ env[Grape::Env::API_VERSION] = env[Rack::RACK_REQUEST_QUERY_HASH].delete(parameter_key)
29
28
  end
30
29
  end
31
30
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module ParamsBuilder
5
+ class Base
6
+ class << self
7
+ def call(_params)
8
+ raise NotImplementedError
9
+ end
10
+
11
+ def inherited(klass)
12
+ super
13
+ ParamsBuilder.register(klass)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module ParamsBuilder
5
+ class Hash < Base
6
+ def self.call(params)
7
+ params.deep_symbolize_keys
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module ParamsBuilder
5
+ class HashWithIndifferentAccess < Base
6
+ def self.call(params)
7
+ params.with_indifferent_access
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module ParamsBuilder
5
+ class HashieMash < Base
6
+ def self.call(params)
7
+ ::Hashie::Mash.new(params)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module ParamsBuilder
5
+ extend Grape::Util::Registry
6
+
7
+ SHORT_NAME_LOOKUP = {
8
+ 'Grape::Extensions::Hash::ParamBuilder' => :hash,
9
+ 'Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder' => :hash_with_indifferent_access,
10
+ 'Grape::Extensions::Hashie::Mash::ParamBuilder' => :hashie_mash
11
+ }.freeze
12
+
13
+ module_function
14
+
15
+ def params_builder_for(short_name)
16
+ verified_short_name = verify_short_name!(short_name)
17
+
18
+ raise Grape::Exceptions::UnknownParamsBuilder, verified_short_name unless registry.key?(verified_short_name)
19
+
20
+ registry[verified_short_name]
21
+ end
22
+
23
+ def verify_short_name!(short_name)
24
+ return short_name if short_name.is_a?(Symbol)
25
+
26
+ class_name = short_name.name
27
+ SHORT_NAME_LOOKUP[class_name].tap do |real_short_name|
28
+ Grape.deprecator.warn "#{class_name} has been deprecated. Use short name :#{real_short_name} instead."
29
+ end
30
+ end
31
+ end
32
+ end
data/lib/grape/request.rb CHANGED
@@ -2,50 +2,189 @@
2
2
 
3
3
  module Grape
4
4
  class Request < Rack::Request
5
- HTTP_PREFIX = 'HTTP_'
5
+ # Based on rack 3 KNOWN_HEADERS
6
+ # https://github.com/rack/rack/blob/4f15e7b814922af79605be4b02c5b7c3044ba206/lib/rack/headers.rb#L10
7
+
8
+ KNOWN_HEADERS = %w[
9
+ Accept
10
+ Accept-CH
11
+ Accept-Encoding
12
+ Accept-Language
13
+ Accept-Patch
14
+ Accept-Ranges
15
+ Accept-Version
16
+ Access-Control-Allow-Credentials
17
+ Access-Control-Allow-Headers
18
+ Access-Control-Allow-Methods
19
+ Access-Control-Allow-Origin
20
+ Access-Control-Expose-Headers
21
+ Access-Control-Max-Age
22
+ Age
23
+ Allow
24
+ Alt-Svc
25
+ Authorization
26
+ Cache-Control
27
+ Client-Ip
28
+ Connection
29
+ Content-Disposition
30
+ Content-Encoding
31
+ Content-Language
32
+ Content-Length
33
+ Content-Location
34
+ Content-MD5
35
+ Content-Range
36
+ Content-Security-Policy
37
+ Content-Security-Policy-Report-Only
38
+ Content-Type
39
+ Cookie
40
+ Date
41
+ Delta-Base
42
+ Dnt
43
+ ETag
44
+ Expect-CT
45
+ Expires
46
+ Feature-Policy
47
+ Forwarded
48
+ Host
49
+ If-Modified-Since
50
+ If-None-Match
51
+ IM
52
+ Last-Modified
53
+ Link
54
+ Location
55
+ NEL
56
+ P3P
57
+ Permissions-Policy
58
+ Pragma
59
+ Preference-Applied
60
+ Proxy-Authenticate
61
+ Public-Key-Pins
62
+ Range
63
+ Referer
64
+ Referrer-Policy
65
+ Refresh
66
+ Report-To
67
+ Retry-After
68
+ Sec-Fetch-Dest
69
+ Sec-Fetch-Mode
70
+ Sec-Fetch-Site
71
+ Sec-Fetch-User
72
+ Server
73
+ Set-Cookie
74
+ Status
75
+ Strict-Transport-Security
76
+ Timing-Allow-Origin
77
+ Tk
78
+ Trailer
79
+ Transfer-Encoding
80
+ Upgrade
81
+ Upgrade-Insecure-Requests
82
+ User-Agent
83
+ Vary
84
+ Version
85
+ Via
86
+ Warning
87
+ WWW-Authenticate
88
+ X-Accel-Buffering
89
+ X-Accel-Charset
90
+ X-Accel-Expires
91
+ X-Accel-Limit-Rate
92
+ X-Accel-Mapping
93
+ X-Accel-Redirect
94
+ X-Access-Token
95
+ X-Auth-Request-Access-Token
96
+ X-Auth-Request-Email
97
+ X-Auth-Request-Groups
98
+ X-Auth-Request-Preferred-Username
99
+ X-Auth-Request-Redirect
100
+ X-Auth-Request-Token
101
+ X-Auth-Request-User
102
+ X-Cascade
103
+ X-Client-Ip
104
+ X-Content-Duration
105
+ X-Content-Security-Policy
106
+ X-Content-Type-Options
107
+ X-Correlation-Id
108
+ X-Download-Options
109
+ X-Forwarded-Access-Token
110
+ X-Forwarded-Email
111
+ X-Forwarded-For
112
+ X-Forwarded-Groups
113
+ X-Forwarded-Host
114
+ X-Forwarded-Port
115
+ X-Forwarded-Preferred-Username
116
+ X-Forwarded-Proto
117
+ X-Forwarded-Scheme
118
+ X-Forwarded-Ssl
119
+ X-Forwarded-Uri
120
+ X-Forwarded-User
121
+ X-Frame-Options
122
+ X-HTTP-Method-Override
123
+ X-Permitted-Cross-Domain-Policies
124
+ X-Powered-By
125
+ X-Real-IP
126
+ X-Redirect-By
127
+ X-Request-Id
128
+ X-Requested-With
129
+ X-Runtime
130
+ X-Sendfile
131
+ X-Sendfile-Type
132
+ X-UA-Compatible
133
+ X-WebKit-CS
134
+ X-XSS-Protection
135
+ ].each_with_object({}) do |header, response|
136
+ response["HTTP_#{header.upcase.tr('-', '_')}"] = header
137
+ end.freeze
6
138
 
7
139
  alias rack_params params
140
+ alias rack_cookies cookies
8
141
 
9
142
  def initialize(env, build_params_with: nil)
10
- extend build_params_with || Grape.config.param_builder
11
143
  super(env)
144
+ @params_builder = Grape::ParamsBuilder.params_builder_for(build_params_with || Grape.config.param_builder)
12
145
  end
13
146
 
14
147
  def params
15
- @params ||= build_params
16
- rescue EOFError
17
- raise Grape::Exceptions::EmptyMessageBody.new(content_type)
18
- rescue Rack::Multipart::MultipartPartLimitError
19
- raise Grape::Exceptions::TooManyMultipartFiles.new(Rack::Utils.multipart_part_limit)
148
+ @params ||= make_params
20
149
  end
21
150
 
22
151
  def headers
23
152
  @headers ||= build_headers
24
153
  end
25
154
 
26
- private
155
+ def cookies
156
+ @cookies ||= Grape::Cookies.new(-> { rack_cookies })
157
+ end
27
158
 
159
+ # needs to be public until extensions param_builder are removed
28
160
  def grape_routing_args
29
- args = env[Grape::Env::GRAPE_ROUTING_ARGS].dup
30
161
  # preserve version from query string parameters
31
- args.delete(:version)
32
- args.delete(:route_info)
33
- args
162
+ env[Grape::Env::GRAPE_ROUTING_ARGS]&.except(:version, :route_info) || {}
34
163
  end
35
164
 
36
- def build_headers
37
- Grape::Util::Lazy::Object.new do
38
- env.each_pair.with_object(Grape::Util::Header.new) do |(k, v), headers|
39
- next unless k.to_s.start_with? HTTP_PREFIX
165
+ private
40
166
 
41
- transformed_header = Grape::Http::Headers::HTTP_HEADERS[k] || transform_header(k)
42
- headers[transformed_header] = v
43
- end
44
- end
167
+ def make_params
168
+ @params_builder.call(rack_params).deep_merge!(grape_routing_args)
169
+ rescue EOFError
170
+ raise Grape::Exceptions::EmptyMessageBody.new(content_type)
171
+ rescue Rack::Multipart::MultipartPartLimitError, Rack::Multipart::MultipartTotalPartLimitError
172
+ raise Grape::Exceptions::TooManyMultipartFiles.new(Rack::Utils.multipart_part_limit)
173
+ rescue Rack::QueryParser::ParamsTooDeepError
174
+ raise Grape::Exceptions::TooDeepParameters.new(Rack::Utils.param_depth_limit)
175
+ rescue Rack::Utils::ParameterTypeError
176
+ raise Grape::Exceptions::ConflictingTypes
177
+ rescue Rack::Utils::InvalidParameterError
178
+ raise Grape::Exceptions::InvalidParameters
45
179
  end
46
180
 
47
- def transform_header(header)
48
- -header[5..].tr('_', '-').downcase
181
+ def build_headers
182
+ each_header.with_object(Grape::Util::Header.new) do |(k, v), headers|
183
+ next unless k.start_with? 'HTTP_'
184
+
185
+ transformed_header = KNOWN_HEADERS.fetch(k) { -k[5..].tr('_', '-').downcase }
186
+ headers[transformed_header] = v
187
+ end
49
188
  end
50
189
  end
51
190
  end
@@ -55,7 +55,7 @@ module Grape
55
55
 
56
56
  def upcase_method(method)
57
57
  method_s = method.to_s
58
- Grape::Http::Headers::SUPPORTED_METHODS.detect { |m| m.casecmp(method_s).zero? } || method_s.upcase
58
+ Grape::HTTP_SUPPORTED_METHODS.detect { |m| m.casecmp(method_s).zero? } || method_s.upcase
59
59
  end
60
60
  end
61
61
  end