Skip to content

Commit

Permalink
Replace rack-mount with new router
Browse files Browse the repository at this point in the history
  • Loading branch information
namusyaka committed Mar 14, 2016
1 parent 41808e3 commit 9f4ba67
Show file tree
Hide file tree
Showing 31 changed files with 612 additions and 189 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
0.15.1 (Next)
=============

#### Features

* [#1276](https://github.com/ruby-grape/grape/pull/1276): Replace rack-mount with new router - [@namusyaka](https://github.com/namusyaka).
* Your contribution here.

#### Fixes

0.15.0 (3/8/2016)
=================

Expand Down Expand Up @@ -32,7 +37,6 @@
* [#1197](https://github.com/ruby-grape/grape/pull/1290): Fix using JSON and Array[JSON] as groups when parameter is optional - [@lukeivers](https://github.com/lukeivers).

0.14.0 (12/07/2015)
===================

#### Features

Expand Down
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2568,11 +2568,15 @@ Examine the routes at runtime.
```ruby
TwitterAPI::versions # yields [ 'v1', 'v2' ]
TwitterAPI::routes # yields an array of Grape::Route objects
TwitterAPI::routes[0].route_version # => 'v1'
TwitterAPI::routes[0].route_description # => 'Includes custom settings.'
TwitterAPI::routes[0].route_settings[:custom] # => { key: 'value' }
TwitterAPI::routes[0].version # => 'v1'
TwitterAPI::routes[0].description # => 'Includes custom settings.'
TwitterAPI::routes[0].settings[:custom] # => { key: 'value' }
```

Note that `Route#route_xyz` methods have been deprecated since 0.15.0.

Please use `Route#xyz` instead.

## Current Route and Endpoint

It's possible to retrieve the information about the current route from within an API call with `route`.
Expand Down
22 changes: 22 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,28 @@
Upgrading Grape
===============

### Upgrading to >= 0.16.0

#### Replace rack-mount with new router

The `Route#route_xyz` methods have been deprecated since 0.15.1.

Please use `Route#xyz` instead.

Note that the `Route#route_method` was replaced by `Route#request_method`.

The following code would work correctly.

```ruby
TwitterAPI::versions # yields [ 'v1', 'v2' ]
TwitterAPI::routes # yields an array of Grape::Route objects
TwitterAPI::routes[0].version # => 'v1'
TwitterAPI::routes[0].description # => 'Includes custom settings.'
TwitterAPI::routes[0].settings[:custom] # => { key: 'value' }

TwitterAPI::routes[0].request_method # => 'GET'
```

### Upgrading to >= 0.15.0

#### Changes to availability of `:with` option of `rescue_from` method
Expand Down
2 changes: 1 addition & 1 deletion grape.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Gem::Specification.new do |s|
s.license = 'MIT'

s.add_runtime_dependency 'rack', '>= 1.3.0'
s.add_runtime_dependency 'rack-mount'
s.add_runtime_dependency 'mustermann19', '~> 0.4.2'
s.add_runtime_dependency 'rack-accept'
s.add_runtime_dependency 'activesupport'
s.add_runtime_dependency 'multi_json', '>= 1.3.2'
Expand Down
3 changes: 1 addition & 2 deletions lib/grape.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
require 'logger'
require 'rack'
require 'rack/mount'
require 'rack/builder'
require 'rack/accept'
require 'rack/auth/basic'
Expand Down Expand Up @@ -33,8 +32,8 @@ module Grape
eager_autoload do
autoload :API
autoload :Endpoint
autoload :Router

autoload :Route
autoload :Namespace

autoload :Path
Expand Down
52 changes: 29 additions & 23 deletions lib/grape/api.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
require 'grape/router'

module Grape
# The API class is the primary entry point for creating Grape APIs. Users
# should subclass this class in order to build an API.
class API
include Grape::DSL::API

class << self
attr_reader :instance
attr_reader :instance, :router

# A class-level lock to ensure the API is not compiled by multiple
# threads simultaneously within the same process.
Expand Down Expand Up @@ -87,24 +89,25 @@ def inherit_settings(other_settings)
# Builds the routes from the defined endpoints, effectively compiling
# this API into a usable form.
def initialize
@route_set = Rack::Mount::RouteSet.new
@router = Router.new
add_head_not_allowed_methods_and_options_methods
self.class.endpoints.each do |endpoint|
endpoint.mount_in(@route_set)
endpoint.mount_in(@router)
end

@route_set.freeze
@router.compile!
@router.freeze
end

# Handle a request. See Rack documentation for what `env` is.
def call(env)
result = @route_set.call(env)
result = @router.call(env)
result[1].delete(Grape::Http::Headers::X_CASCADE) unless cascade?
result
end

# Some requests may return a HTTP 404 error if grape cannot find a matching
# route. In this case, Rack::Mount adds a X-Cascade header to the response
# route. In this case, Grape::Router adds a X-Cascade header to the response
# and sets it to 'pass', indicating to grape's parents they should keep
# looking for a matching route on other resources.
#
Expand All @@ -126,20 +129,23 @@ def cascade?
# will return an HTTP 405 response for any HTTP method that the resource
# cannot handle.
def add_head_not_allowed_methods_and_options_methods
methods_per_path = {}
routes_map = {}

self.class.endpoints.each do |endpoint|
routes = endpoint.routes
routes.each do |route|
route_path = route.route_path
.gsub(/\(.*\)/, '') # ignore any optional portions
.gsub(%r{\:[^\/.?]+}, ':x') # substitute variable names to avoid conflicts

methods_per_path[route_path] ||= []
methods_per_path[route_path] << route.route_method
# using the :any shorthand produces [nil] for route methods, substitute all manually
route_key = route.pattern.to_regexp
routes_map[route_key] ||= {}
route_settings = routes_map[route_key]
route_settings[:requirements] = route.requirements
route_settings[:path] = route.origin
route_settings[:methods] ||= []
route_settings[:methods] << route.request_method
route_settings[:endpoint] = route.app

# using the :any shorthand produces [nil] for route methods, substitute all manually
methods_per_path[route_path] = %w(GET PUT POST DELETE PATCH HEAD OPTIONS) if methods_per_path[route_path].compact.empty?
route_settings[:methods] = %w(GET PUT POST DELETE PATCH HEAD OPTIONS) if route_settings[:methods].include?('ANY')
end
end

Expand All @@ -149,7 +155,9 @@ def add_head_not_allowed_methods_and_options_methods
# informations again.
without_root_prefix do
without_versioning do
methods_per_path.each do |path, methods|
routes_map.each do |regexp, config|
methods = config[:methods]
path = config[:path]
allowed_methods = methods.dup

unless self.class.namespace_inheritable(:do_not_route_head)
Expand All @@ -159,18 +167,18 @@ def add_head_not_allowed_methods_and_options_methods
allow_header = (self.class.namespace_inheritable(:do_not_route_options) ? allowed_methods : [Grape::Http::Headers::OPTIONS] | allowed_methods).join(', ')

unless self.class.namespace_inheritable(:do_not_route_options)
generate_options_method(path, allow_header) unless allowed_methods.include?(Grape::Http::Headers::OPTIONS)
generate_options_method(path, allow_header, config) unless allowed_methods.include?(Grape::Http::Headers::OPTIONS)
end

generate_not_allowed_method(path, allowed_methods, allow_header)
generate_not_allowed_method(regexp, allowed_methods, allow_header, config[:endpoint])
end
end
end
end

# Generate an 'OPTIONS' route for a pre-exisiting user defined route
def generate_options_method(path, allow_header)
self.class.options(path, {}) do
def generate_options_method(path, allow_header, options = {})
self.class.options(path, options) do
header 'Allow', allow_header
status 204
''
Expand All @@ -179,15 +187,13 @@ def generate_options_method(path, allow_header)

# Generate a route that returns an HTTP 405 response for a user defined
# path on methods not specified
def generate_not_allowed_method(path, allowed_methods, allow_header)
def generate_not_allowed_method(path, allowed_methods, allow_header, endpoint = nil)
not_allowed_methods = %w(GET PUT POST DELETE PATCH HEAD) - allowed_methods
not_allowed_methods << Grape::Http::Headers::OPTIONS if self.class.namespace_inheritable(:do_not_route_options)

return if not_allowed_methods.empty?

self.class.route(not_allowed_methods, path) do
fail Grape::Exceptions::MethodNotAllowed, header.merge('Allow' => allow_header)
end
@router.associate_routes(path, not_allowed_methods, allow_header, endpoint)
end

# Allows definition of endpoints that ignore the versioning configuration
Expand Down
4 changes: 2 additions & 2 deletions lib/grape/dsl/inside_route.rb
Original file line number Diff line number Diff line change
Expand Up @@ -269,10 +269,10 @@ def present(*args)
#
# desc "Returns the route description."
# get '/' do
# route.route_description
# route.description
# end
def route
env[Grape::Env::RACK_ROUTING_ARGS][:route_info]
env[Grape::Env::GRAPE_ROUTING_ARGS][:route_info]
end

# Attempt to locate the Entity class for a given object, if not given
Expand Down
3 changes: 2 additions & 1 deletion lib/grape/dsl/routing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def mount(mounts)
in_setting = inheritable_setting

if app.respond_to?(:inheritable_setting, true)
mount_path = Rack::Mount::Utils.normalize_path(path)
mount_path = Grape::Router.normalize_path(path)
app.top_level_setting.namespace_stackable[:mount_path] = mount_path

app.inherit_settings(inheritable_setting)
Expand All @@ -98,6 +98,7 @@ def mount(mounts)
method: :any,
path: path,
app: app,
forward_match: !app.respond_to?(:inheritable_setting),
for: self
)
end
Expand Down
Loading

0 comments on commit 9f4ba67

Please sign in to comment.