Skip to content

Commit

Permalink
Merge branch 'master' into use_regex_match_block
Browse files Browse the repository at this point in the history
  • Loading branch information
dblock authored Dec 12, 2023
2 parents d71d98e + 4e3b5fd commit 1a542a7
Show file tree
Hide file tree
Showing 12 changed files with 514 additions and 308 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@
#### Features

* [#2371](https://github.com/ruby-grape/grape/pull/2371): Use a param value as the `default` value of other param - [@jcagarcia](https://github.com/jcagarcia).
* [#2377](https://github.com/ruby-grape/grape/pull/2377): Allow to use instance variables values inside `rescue_from` - [@jcagarcia](https://github.com/jcagarcia).
* [#2379](https://github.com/ruby-grape/grape/pull/2379): Take into account the `route_param` type in `recognize_path` - [@jcagarcia](https://github.com/jcagarcia).
* [#2383](https://github.com/ruby-grape/grape/pull/2383): Use regex block instead of if - [@ericproulx](https://github.com/ericproulx).
* Your contribution here.

#### Fixes

* [#2370](https://github.com/ruby-grape/grape/pull/2370): Remove route_xyz method_missing deprecation - [@ericproulx](https://github.com/ericproulx).
* [#2372](https://github.com/ruby-grape/grape/pull/2372): Fix `declared` method for hash params with overlapping names - [@jcagarcia](https://github.com/jcagarcia).
* [#2373](https://github.com/ruby-grape/grape/pull/2373): Fix markdown files for following 1-line format - [@jcagarcia](https://github.com/jcagarcia).
* [#2382](https://github.com/ruby-grape/grape/pull/2382): Fix values validator for params wrapped in `with` block - [@numbata](https://github.com/numbata).
* Your contribution here.

### 2.0.0 (2023/11/11)
Expand Down
479 changes: 225 additions & 254 deletions README.md

Large diffs are not rendered by default.

103 changes: 97 additions & 6 deletions UPGRADING.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,100 @@
Upgrading Grape
===============

### Upgrading to >= 2.1.0

#### Grape::Router::Route.route_xxx methods have been removed

- `route_method` is accessible through `request_method`
- `route_path` is accessible through `path`
- Any other `route_xyz` are accessible through `options[xyz]`

#### Instance variables scope

Due to the changes done in [#2377](https://github.com/ruby-grape/grape/pull/2377), the instance variables defined inside each of the endpoints (or inside a `before` validator) are now accessible inside the `rescue_from`. The behavior of the instance variables was undefined until `2.1.0`.

If you were using the same variable name defined inside an endpoint or `before` validator inside a `rescue_from` handler, you need to take in mind that you can start getting different values or you can be overriding values.

Before:
```ruby
class TwitterAPI < Grape::API
before do
@var = 1
end

get '/' do
puts @var # => 1
raise
end

rescue_from :all do
puts @var # => nil
end
end
```

After:
```ruby
class TwitterAPI < Grape::API
before do
@var = 1
end

get '/' do
puts @var # => 1
raise
end

rescue_from :all do
puts @var # => 1
end
end
```

#### Recognizing Path

Grape now considers the types of the configured `route_params` in order to determine the endpoint that matches with the performed request.

So taking into account this `Grape::API` class

```ruby
class Books < Grape::API
resource :books do
route_param :id, type: Integer do
# GET /books/:id
get do
#...
end
end

resource :share do
# POST /books/share
post do
# ....
end
end
end
end
```

Before:
```ruby
API.recognize_path '/books/1' # => /books/:id
API.recognize_path '/books/share' # => /books/:id
API.recognize_path '/books/other' # => /books/:id
```

After:
```ruby
API.recognize_path '/books/1' # => /books/:id
API.recognize_path '/books/share' # => /books/share
API.recognize_path '/books/other' # => nil
```

This implies that before this changes, when you performed `/books/other` and it matched with the `/books/:id` endpoint, you get a `400 Bad Request` response because the type of the provided `:id` param was not an `Integer`. However, after upgrading to version `2.1.0` you will get a `404 Not Found` response, because there is not a defined endpoint that matches with `/books/other`.

See [#2379](https://github.com/ruby-grape/grape/pull/2379) for more information.

### Upgrading to >= 2.0.0

#### Headers
Expand Down Expand Up @@ -474,8 +568,7 @@ end

##### `name` (and other caveats) of the mounted API

After the patch, the mounted API is no longer a Named class inheriting from `Grape::API`, it is an anonymous class
which inherit from `Grape::API::Instance`.
After the patch, the mounted API is no longer a Named class inheriting from `Grape::API`, it is an anonymous class which inherit from `Grape::API::Instance`.

What this means in practice, is:

Expand Down Expand Up @@ -855,8 +948,7 @@ See [#1114](https://github.com/ruby-grape/grape/pull/1114) for more information.

#### Bypasses formatters when status code indicates no content

To be consistent with rack and it's handling of standard responses associated with no content, both default and custom formatters will now
be bypassed when processing responses for status codes defined [by rack](https://github.com/rack/rack/blob/master/lib/rack/utils.rb#L567)
To be consistent with rack and it's handling of standard responses associated with no content, both default and custom formatters will now be bypassed when processing responses for status codes defined [by rack](https://github.com/rack/rack/blob/master/lib/rack/utils.rb#L567)

See [#1190](https://github.com/ruby-grape/grape/pull/1190) for more information.

Expand Down Expand Up @@ -1297,8 +1389,7 @@ As replacement can be used
* `Grape::Middleware::Auth::Digest` => [`Rack::Auth::Digest::MD5`](https://github.com/rack/rack/blob/master/lib/rack/auth/digest/md5.rb)
* `Grape::Middleware::Auth::OAuth2` => [warden-oauth2](https://github.com/opperator/warden-oauth2) or [rack-oauth2](https://github.com/nov/rack-oauth2)

If this is not possible you can extract the middleware files from [grape v0.7.0](https://github.com/ruby-grape/grape/tree/v0.7.0/lib/grape/middleware/auth)
and host these files within your application
If this is not possible you can extract the middleware files from [grape v0.7.0](https://github.com/ruby-grape/grape/tree/v0.7.0/lib/grape/middleware/auth) and host these files within your application

See [#703](https://github.com/ruby-grape/Grape/pull/703) for more information.

Expand Down
2 changes: 1 addition & 1 deletion grape.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Gem::Specification.new do |s|
s.add_runtime_dependency 'activesupport', '>= 5'
s.add_runtime_dependency 'builder'
s.add_runtime_dependency 'dry-types', '>= 1.1'
s.add_runtime_dependency 'mustermann-grape', '~> 1.0.0'
s.add_runtime_dependency 'mustermann-grape', '~> 1.1.0'
s.add_runtime_dependency 'rack', '>= 1.3.0'
s.add_runtime_dependency 'rack-accept'

Expand Down
17 changes: 17 additions & 0 deletions lib/grape/dsl/inside_route.rb
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,23 @@ def error!(message, status = nil, additional_headers = nil)
throw :error, message: message, status: self.status, headers: headers
end

# Creates a Rack response based on the provided message, status, and headers.
# The content type in the headers is set to the default content type unless provided.
# The message is HTML-escaped if the content type is 'text/html'.
#
# @param message [String] The content of the response.
# @param status [Integer] The HTTP status code.
# @params headers [Hash] (optional) Headers for the response
# (default: {Rack::CONTENT_TYPE => content_type}).
#
# Returns:
# A Rack::Response object containing the specified message, status, and headers.
#
def rack_response(message, status = 200, headers = { Rack::CONTENT_TYPE => content_type })
message = ERB::Util.html_escape(message) if headers[Rack::CONTENT_TYPE] == 'text/html'
Rack::Response.new([message], Rack::Utils.status_code(status), headers)
end

# Redirect to a new url.
#
# @param url [String] The url to be redirect.
Expand Down
16 changes: 12 additions & 4 deletions lib/grape/middleware/error.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def call!(env)
rescue_handler_for_any_class(e.class) ||
raise

run_rescue_handler(handler, e)
run_rescue_handler(handler, e, @env[Grape::Env::API_ENDPOINT])
end
end

Expand Down Expand Up @@ -119,21 +119,29 @@ def rescue_handler_for_any_class(klass)
options[:all_rescue_handler] || :default_rescue_handler
end

def run_rescue_handler(handler, error)
def run_rescue_handler(handler, error, endpoint)
if handler.instance_of?(Symbol)
raise NoMethodError, "undefined method '#{handler}'" unless respond_to?(handler)

handler = public_method(handler)
end

response = handler.arity.zero? ? instance_exec(&handler) : instance_exec(error, &handler)
response = (catch(:error) do
handler.arity.zero? ? endpoint.instance_exec(&handler) : endpoint.instance_exec(error, &handler)
end)

response = error!(response[:message], response[:status], response[:headers]) if error?(response)

if response.is_a?(Rack::Response)
response
else
run_rescue_handler(:default_rescue_handler, Grape::Exceptions::InvalidResponse.new)
run_rescue_handler(:default_rescue_handler, Grape::Exceptions::InvalidResponse.new, endpoint)
end
end

def error?(response)
response.is_a?(Hash) && response[:message] && response[:status] && response[:headers]
end
end
end
end
2 changes: 2 additions & 0 deletions lib/grape/router/pattern.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ def initialize(pattern, **options)

def pattern_options(options)
capture = extract_capture(**options)
params = options[:params]
options = DEFAULT_PATTERN_OPTIONS.dup
options[:capture] = capture if capture.present?
options[:params] = params if params.present?
options
end

Expand Down
37 changes: 0 additions & 37 deletions lib/grape/router/route.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,10 @@
require 'grape/router/pattern'
require 'grape/router/attribute_translator'
require 'forwardable'
require 'pathname'

module Grape
class Router
class Route
ROUTE_ATTRIBUTE_REGEXP = /route_([_a-zA-Z]\w*)/.freeze
SOURCE_LOCATION_REGEXP = /^(.*?):(\d+?)(?::in `.+?')?$/.freeze
FIXED_NAMED_CAPTURES = %w[format version].freeze

attr_accessor :pattern, :translator, :app, :index, :options
Expand All @@ -20,31 +17,6 @@ class Route
def_delegators :pattern, :path, :origin
delegate Grape::Router::AttributeTranslator::ROUTE_ATTRIBUTES => :attributes

def method_missing(method_id, *arguments)
match = ROUTE_ATTRIBUTE_REGEXP.match(method_id.to_s)
if match
method_name = match.captures.last.to_sym
warn_route_methods(method_name, caller(1).shift)
@options[method_name]
else
super
end
end

def respond_to_missing?(method_id, _)
ROUTE_ATTRIBUTE_REGEXP.match?(method_id.to_s)
end

def route_method
warn_route_methods(:method, caller(1).shift, :request_method)
request_method
end

def route_path
warn_route_methods(:path, caller(1).shift)
pattern.path
end

def initialize(method, pattern, **options)
method_s = method.to_s
method_upcase = Grape::Http::Headers.find_supported_method(method_s) || method_s.upcase
Expand Down Expand Up @@ -77,15 +49,6 @@ def params(input = nil)
parsed ? parsed.delete_if { |_, value| value.nil? }.symbolize_keys : {}
end
end

private

def warn_route_methods(name, location, expected = nil)
path, line = *location.scan(SOURCE_LOCATION_REGEXP).first
path = File.realpath(path) if Pathname.new(path).relative?
expected ||= name
Grape.deprecator.warn("#{path}:#{line}: The route_xxx methods such as route_#{name} have been deprecated, please use #{expected}.")
end
end
end
end
7 changes: 6 additions & 1 deletion lib/grape/validations/validators/values_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,12 @@ def except_message
end

def required_for_root_scope?
@required && @scope.root?
return false unless @required

scope = @scope
scope = scope.parent while scope.lateral?

scope.root?
end

def validation_exception(attr_name, message)
Expand Down
55 changes: 55 additions & 0 deletions spec/grape/api/recognize_path_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,60 @@
subject.get {}
expect(subject.recognize_path('/bar/1234')).to be_nil
end

context 'when parametrized route with type specified together with a static route' do
subject do
Class.new(described_class) do
resource :books do
route_param :id, type: Integer do
get do
end

resource :loans do
route_param :loan_id, type: Integer do
get do
end
end

resource :print do
post do
end
end
end
end

resource :share do
post do
end
end
end
end
end

it 'recognizes the static route when the parameter does not match with the specified type' do
actual = subject.recognize_path('/books/share').routes[0].origin
expect(actual).to eq('/books/share')
end

it 'does not recognize any endpoint when there is not other endpoint that matches with the requested path' do
actual = subject.recognize_path('/books/other')
expect(actual).to be_nil
end

it 'recognizes the parametrized route when the parameter matches with the specified type' do
actual = subject.recognize_path('/books/1').routes[0].origin
expect(actual).to eq('/books/:id')
end

it 'recognizes the static nested route when the parameter does not match with the specified type' do
actual = subject.recognize_path('/books/1/loans/print').routes[0].origin
expect(actual).to eq('/books/:id/loans/print')
end

it 'recognizes the nested parametrized route when the parameter matches with the specified type' do
actual = subject.recognize_path('/books/1/loans/33').routes[0].origin
expect(actual).to eq('/books/:id/loans/:loan_id')
end
end
end
end
Loading

0 comments on commit 1a542a7

Please sign in to comment.