Skip to content

Commit 6ccc635

Browse files
committed
Merge master
2 parents 73f7bbd + a80d2d4 commit 6ccc635

39 files changed

+581
-362
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
* [#2540](https://github.com/ruby-grape/grape/pull/2540): Introduce Params builder with symbolized short name - [@ericproulx](https://github.com/ericproulx).
1010
* [#2550](https://github.com/ruby-grape/grape/pull/2550): Drop ActiveSupport 6.0 - [@ericproulx](https://github.com/ericproulx).
1111
* [#2549](https://github.com/ruby-grape/grape/pull/2549): Delegate cookies management to `Grape::Request` - [@ericproulx](https://github.com/ericproulx).
12+
* [#2554](https://github.com/ruby-grape/grape/pull/2554): Remove `Grape::Http::Headers` and `Grape::Util::Lazy::Object` - [@ericproulx](https://github.com/ericproulx).
13+
* [#2556](https://github.com/ruby-grape/grape/pull/2556): Remove unused `Grape::Request::DEFAULT_PARAMS_BUILDER` constant - [@eriklovmo](https://github.com/eriklovmo).
1214
* Your contribution here.
1315

1416
#### Fixes
@@ -17,6 +19,9 @@
1719
* [#2543](https://github.com/ruby-grape/grape/pull/2543): Fix array allocation on mount - [@ericproulx](https://github.com/ericproulx).
1820
* [#2546](https://github.com/ruby-grape/grape/pull/2546): Fix middleware with keywords - [@ericproulx](https://github.com/ericproulx).
1921
* [#2547](https://github.com/ruby-grape/grape/pull/2547): Remove jsonapi related code - [@ericproulx](https://github.com/ericproulx).
22+
* [#2548](https://github.com/ruby-grape/grape/pull/2548): Formatting from header acts like versioning from header - [@ericproulx](https://github.com/ericproulx).
23+
* [#2552](https://github.com/ruby-grape/grape/pull/2552): Fix declared params optional array - [@ericproulx](https://github.com/ericproulx).
24+
* [#2553](https://github.com/ruby-grape/grape/pull/2553): Improve performance of query params parsing - [@ericproulx](https://github.com/ericproulx).
2025
* Your contribution here.
2126

2227
### 2.3.0 (2025-02-08)

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ Grape's [deprecator](https://api.rubyonrails.org/v7.1.0/classes/ActiveSupport/De
269269
### All
270270

271271

272-
By default Grape will compile the routes on the first route, it is possible to pre-load routes using the `compile!` method.
272+
By default Grape will compile the routes on the first route, but it is possible to pre-load routes using the `compile!` method.
273273

274274
```ruby
275275
Twitter::API.compile!

UPGRADING.md

+35-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,46 @@
11
Upgrading Grape
22
===============
33

4+
### Upgrading to >= 2.4.0
5+
6+
#### Grape::Http::Headers, Grape::Util::Lazy::Object
7+
8+
Both have been removed. See [2554](https://github.com/ruby-grape/grape/pull/2554).
9+
Here are the notable changes:
10+
11+
- Constants like `HTTP_ACCEPT` have been replaced by their literal value.
12+
- `SUPPORTED_METHODS` has been moved to `Grape` module.
13+
- `HTTP_HEADERS` has been moved to `Grape::Request` and renamed `KNOWN_HEADERS`. The last has been refreshed with new headers, and it's not lazy anymore.
14+
- `SUPPORTED_METHODS_WITHOUT_OPTIONS` and `find_supported_method` have been removed.
15+
16+
### Grape::Middleware::Base
17+
18+
- Constant `TEXT_HTML` has been removed in favor of using literal string 'text/html'.
19+
- `rack_request` and `query_params` have been added. Feel free to call these in your middlewares.
20+
421
#### Params Builder
522

623
- Passing a class to `build_with` or `Grape.config.param_builder` has been deprecated in favor of a symbolized short_name. See `SHORTNAME_LOOKUP` in [params_builder](lib/grape/params_builder.rb).
724
- Including Grape's extensions like `Grape::Extensions::Hashie::Mash::ParamBuilder` has been deprecated in favor of using `build_with` at the route level.
825

9-
### Upgrading to >= 2.4.0
26+
#### Accept Header Negotiation Harmonized
27+
28+
[Accept](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Accept) header is now fully interpreted through `Rack::Utils.best_q_match` which is following [RFC2616 14.1](https://datatracker.ietf.org/doc/html/rfc2616#section-14.1). Since [Grape 2.1.0](https://github.com/ruby-grape/grape/blob/master/CHANGELOG.md#210-20240615), the [header versioning strategy](https://github.com/ruby-grape/grape?tab=readme-ov-file#header) was adhering to it, but `Grape::Middleware::Formatter` never did.
29+
30+
Your API might act differently since it will strictly follow the [RFC2616 14.1](https://datatracker.ietf.org/doc/html/rfc2616#section-14.1) when interpreting the `Accept` header. Here are the differences:
31+
32+
###### Invalid or missing quality ranking
33+
The following used to yield `application/xml` and now will yield `application/json` as the preferred media type:
34+
- `application/json;q=invalid,application/xml;q=0.5`
35+
- `application/json,application/xml;q=1.0`
36+
37+
For the invalid case, the value `invalid` was automatically `to_f` and `invalid.to_f` equals `0.0`. Now, since it doesn't match [Rack's regex](https://github.com/rack/rack/blob/3-1-stable/lib/rack/utils.rb#L138), its interpreted as non provided and its quality ranking equals 1.0.
38+
39+
For the non provided case, 1.0 was automatically assigned and in a case of multiple best matches, the first was returned based on Ruby's sort_by `quality`. Now, 1.0 is still assigned and the last is returned in case of multiple best matches. See [Rack's implementation](https://github.com/rack/rack/blob/e8f47608668d507e0f231a932fa37c9ca551c0a5/lib/rack/utils.rb#L167) of the RFC.
40+
41+
###### Considering the closest generic when vendor tree
42+
Excluding the [header versioning strategy](https://github.com/ruby-grape/grape?tab=readme-ov-file#header), whenever a media type with the [vendor tree](https://datatracker.ietf.org/doc/html/rfc6838#section-3.2) leading facet `vnd.` like `application/vnd.api+json` was provided, Grape would also consider its closest generic when negotiating. In that case, `application/json` was added to the negotiation. Now, it will just consider the provided media types without considering any closest generics, and you'll need to [register](https://github.com/ruby-grape/grape?tab=readme-ov-file#api-formats) it.
43+
You can find the official vendor tree registrations on [IANA](https://www.iana.org/assignments/media-types/media-types.xhtml)
1044

1145
#### Custom Validators
1246

lib/grape.rb

+10
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,16 @@
5959
module Grape
6060
include ActiveSupport::Configurable
6161

62+
HTTP_SUPPORTED_METHODS = [
63+
Rack::GET,
64+
Rack::POST,
65+
Rack::PUT,
66+
Rack::PATCH,
67+
Rack::DELETE,
68+
Rack::HEAD,
69+
Rack::OPTIONS
70+
].freeze
71+
6272
def self.deprecator
6373
@deprecator ||= ActiveSupport::Deprecation.new('2.0', 'Grape')
6474
end

lib/grape/api/instance.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ def call(env)
175175
status, headers, response = @router.call(env)
176176
unless cascade?
177177
headers = Grape::Util::Header.new.merge(headers)
178-
headers.delete(Grape::Http::Headers::X_CASCADE)
178+
headers.delete('X-Cascade')
179179
end
180180

181181
[status, headers, response]

lib/grape/dsl/headers.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ module Headers
1010
# 4. Delete a specifc header key-value pair
1111
def header(key = nil, val = nil)
1212
if key
13-
val ? header[key.to_s] = val : header.delete(key.to_s)
13+
val ? header[key] = val : header.delete(key)
1414
else
1515
@header ||= Grape::Util::Header.new
1616
end

lib/grape/dsl/inside_route.rb

+4-4
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def declared_hash_attr(passed_params, options, declared_param, params_nested_pat
7575
else
7676
# If it is not a Hash then it does not have children.
7777
# Find its value or set it to nil.
78-
return unless options[:include_missing] || passed_params.key?(declared_param)
78+
return unless options[:include_missing] || passed_params.try(:key?, declared_param)
7979

8080
rename_path = params_nested_path + [declared_param.to_s]
8181
renamed_param_name = renamed_params[rename_path]
@@ -209,7 +209,7 @@ def redirect(url, permanent: false, body: nil)
209209
status 302
210210
body_message ||= "This resource has been moved temporarily to #{url}."
211211
end
212-
header Grape::Http::Headers::LOCATION, url
212+
header 'Location', url
213213
content_type 'text/plain'
214214
body body_message
215215
end
@@ -326,7 +326,7 @@ def stream(value = nil)
326326
return if value.nil? && @stream.nil?
327327

328328
header Rack::CONTENT_LENGTH, nil
329-
header Grape::Http::Headers::TRANSFER_ENCODING, nil
329+
header 'Transfer-Encoding', nil
330330
header Rack::CACHE_CONTROL, 'no-cache' # Skips ETag generation (reading the response up front)
331331
if value.is_a?(String)
332332
file_body = Grape::ServeStream::FileBody.new(value)
@@ -435,7 +435,7 @@ def entity_representation_for(entity_class, object, options)
435435
end
436436

437437
def http_version
438-
env.fetch(Grape::Http::Headers::HTTP_VERSION) { env[Rack::SERVER_PROTOCOL] }
438+
env.fetch('HTTP_VERSION') { env[Rack::SERVER_PROTOCOL] }
439439
end
440440

441441
def api_format(format)

lib/grape/dsl/routing.rb

+6-6
Original file line numberDiff line numberDiff line change
@@ -154,13 +154,13 @@ def route(methods, paths = ['/'], route_options = {}, &block)
154154
reset_validations!
155155
end
156156

157-
Grape::Http::Headers::SUPPORTED_METHODS.each do |supported_method|
158-
define_method supported_method.downcase do |*args, &block|
159-
options = args.extract_options!
160-
paths = args.first || ['/']
161-
route(supported_method, paths, options, &block)
157+
Grape::HTTP_SUPPORTED_METHODS.each do |supported_method|
158+
define_method supported_method.downcase do |*args, &block|
159+
options = args.extract_options!
160+
paths = args.first || ['/']
161+
route(supported_method, paths, options, &block)
162+
end
162163
end
163-
end
164164

165165
# Declare a "namespace", which prefixes all subordinate routes with its
166166
# name. Any endpoints within a namespace, group, resource or segment,

lib/grape/endpoint.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ def run
258258
header['Allow'] = env[Grape::Env::GRAPE_ALLOWED_METHODS].join(', ')
259259
raise Grape::Exceptions::MethodNotAllowed.new(header) unless options?
260260

261-
header Grape::Http::Headers::ALLOW, header['Allow']
261+
header 'Allow', header['Allow']
262262
response_object = ''
263263
status 204
264264
else
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# frozen_string_literal: true
2+
3+
module Grape
4+
module Exceptions
5+
class ConflictingTypes < Base
6+
def initialize
7+
super(message: compose_message(:conflicting_types), status: 400)
8+
end
9+
end
10+
end
11+
end
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# frozen_string_literal: true
2+
3+
module Grape
4+
module Exceptions
5+
class InvalidParameters < Base
6+
def initialize
7+
super(message: compose_message(:invalid_parameters), status: 400)
8+
end
9+
end
10+
end
11+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# frozen_string_literal: true
2+
3+
module Grape
4+
module Exceptions
5+
class TooDeepParameters < Base
6+
def initialize(limit)
7+
super(message: compose_message(:too_deep_parameters, limit: limit), status: 400)
8+
end
9+
end
10+
end
11+
end

lib/grape/http/headers.rb

-56
This file was deleted.

lib/grape/locale/en.yml

+3-1
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,6 @@ en:
5858
problem: 'invalid version header'
5959
resolution: '%{message}'
6060
invalid_response: 'Invalid response'
61-
query_parsing: 'query params are not parsable'
61+
conflicting_types: 'query params contains conflicting types'
62+
invalid_parameters: 'query params contains invalid format or byte sequence'
63+
too_deep_parameters: 'query params are recursively nested over the specified limit (%{limit})'

lib/grape/middleware/base.rb

+13-3
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ class Base
88

99
attr_reader :app, :env, :options
1010

11-
TEXT_HTML = 'text/html'
12-
1311
# @param [Rack Application] app The standard argument for a Rack middleware.
1412
# @param [Hash] options A hash of options, simply stored for use by subclasses.
1513
def initialize(app, *options)
@@ -54,6 +52,10 @@ def before; end
5452
# @return [Response, nil] a Rack SPEC response or nil to call the application afterwards.
5553
def after; end
5654

55+
def rack_request
56+
@rack_request ||= Rack::Request.new(env)
57+
end
58+
5759
def response
5860
return @app_response if @app_response.is_a?(Rack::Response)
5961

@@ -73,7 +75,15 @@ def content_type_for(format)
7375
end
7476

7577
def content_type
76-
content_type_for(env[Grape::Env::API_FORMAT] || options[:format]) || TEXT_HTML
78+
content_type_for(env[Grape::Env::API_FORMAT] || options[:format]) || 'text/html'
79+
end
80+
81+
def query_params
82+
rack_request.GET
83+
rescue Rack::QueryParser::ParamsTooDeepError
84+
raise Grape::Exceptions::TooDeepParameters.new(Rack::Utils.param_depth_limit)
85+
rescue Rack::Utils::ParameterTypeError
86+
raise Grape::Exceptions::ConflictingTypes
7787
end
7888

7989
private

lib/grape/middleware/error.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def call!(env)
3939
private
4040

4141
def rack_response(status, headers, message)
42-
message = Rack::Utils.escape_html(message) if headers[Rack::CONTENT_TYPE] == TEXT_HTML
42+
message = Rack::Utils.escape_html(message) if headers[Rack::CONTENT_TYPE] == 'text/html'
4343
Rack::Response.new(Array.wrap(message), Rack::Utils.status_code(status), Grape::Util::Header.new.merge(headers))
4444
end
4545

0 commit comments

Comments
 (0)