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 Feb 24, 2016
1 parent 4032ea8 commit 1ab3595
Show file tree
Hide file tree
Showing 28 changed files with 481 additions and 183 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2402,7 +2402,7 @@ Examine the routes at runtime.
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].description # => 'Includes custom settings.'
TwitterAPI::routes[0].route_settings[:custom] # => { key: 'value' }
```

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'
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
54 changes: 32 additions & 22 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,16 +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|
methods_per_path[route.route_path] ||= []
methods_per_path[route.route_path] << route.route_method
# using the :any shorthand produces [nil] for route methods, substitute all manually
routes_map[route.pattern.to_regexp] ||= {}
route_settings = routes_map[route.pattern.to_regexp]
route_settings[:requirements] = route.requirements
route_settings[:path] = route.origin
route_settings[:format] = route.format
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.route_path] = %w(GET PUT POST DELETE PATCH HEAD OPTIONS) if methods_per_path[route.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 @@ -145,28 +155,33 @@ 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

self.class.namespace_stackable(:format, config[:format])

unless self.class.namespace_inheritable(:do_not_route_head)
allowed_methods |= [Grape::Http::Headers::HEAD] if allowed_methods.include?(Grape::Http::Headers::GET)
end

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])
self.class.namespace_stackable(:format, [])
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 @@ -178,18 +193,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

# move options endpoint to top of defined endpoints
self.class.endpoints.unshift self.class.endpoints.pop
@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
115 changes: 55 additions & 60 deletions lib/grape/endpoint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def method_name
end

def routes
@routes ||= endpoints ? endpoints.collect(&:routes).flatten : prepare_routes
@routes ||= endpoints ? endpoints.collect(&:routes).flatten : to_routes
end

def reset_routes!
Expand All @@ -119,29 +119,36 @@ def reset_routes!
@routes = nil
end

def mount_in(route_set)
def mount_in(router)
if endpoints
endpoints.each do |e|
e.mount_in(route_set)
end
endpoints.each { |e| e.mount_in(router) }
else
reset_routes!

routes.each do |route|
methods = [route.route_method]
if !namespace_inheritable(:do_not_route_head) && route.route_method == Grape::Http::Headers::GET
methods = [route.request_method]
if !namespace_inheritable(:do_not_route_head) && route.request_method == Grape::Http::Headers::GET
methods << Grape::Http::Headers::HEAD
end
methods.each do |method|
route_set.add_route(self, {
path_info: route.route_compiled,
request_method: method
}, route_info: route)
unless route.request_method.to_s.upcase == method
route = Grape::Router::Route.new(method, route.origin, route.attributes.to_h)
end
router.append(route.apply(self))
end
end
end
end

def to_routes
route_options = prepare_default_route_attributes
map_routes do |method, path|
path = prepare_path(path)
params = merge_route_options(route_options.merge(suffix: path.suffix))
route = Router::Route.new(method, path.path, params)
route.apply(self)
end.flatten
end

def prepare_routes_requirements
endpoint_requirements = options[:route_options][:requirements] || {}
all_requirements = (namespace_stackable(:namespace).map(&:requirements) << endpoint_requirements)
Expand All @@ -150,41 +157,44 @@ def prepare_routes_requirements
end
end

def prepare_routes_path_params(path)
path_params = {}

# named parameters in the api path
regex = Rack::Mount::RegexpWithNamedGroups.new(path)
named_params = regex.named_captures.map { |nc| nc[0] } - %w(version format)
named_params.each { |named_param| path_params[named_param] = '' }
def prepare_default_route_attributes
{
namespace: namespace,
format: prepare_format,
version: prepare_version,
requirements: prepare_routes_requirements,
prefix: namespace_inheritable(:root_prefix),
anchor: options[:route_options].fetch(:anchor, true),
settings: inheritable_setting.route.except(:saved_declared_params, :saved_validations),
forward_match: options[:forward_match]
}
end

# route parameters declared via desc or appended to the api declaration
route_params = options[:route_options][:params]
path_params.merge! route_params if route_params
def prepare_format
format = []
format << namespace_inheritable(:format) if namespace_inheritable(:format)
format << namespace_stackable(:format) if namespace_stackable(:format).present?
if format.present? && namespace_stackable(:content_types)
content_types = namespace_stackable(:content_types).map do |content_type|
content_type.keys.first
end
format = format.concat(content_types).flatten.compact.uniq
end
format.empty? ? nil : format.uniq
end

path_params
def prepare_version
version = namespace_inheritable(:version) || []
return if version.length == 0
version.length == 1 ? version.first.to_s : version
end

def prepare_routes
options[:method].map do |method|
options[:path].map do |path|
prepared_path = prepare_path(path)
anchor = options[:route_options].fetch(:anchor, true)
path = compile_path(prepared_path, anchor && !options[:app], prepare_routes_requirements)
request_method = (method.to_s.upcase unless method == :any)
def merge_route_options(default = {})
options[:route_options].clone.reverse_merge(default)
end

Route.new(options[:route_options].clone.merge(
prefix: namespace_inheritable(:root_prefix),
version: namespace_inheritable(:version) ? namespace_inheritable(:version).join('|') : nil,
namespace: namespace,
method: request_method,
path: prepared_path,
params: prepare_routes_path_params(path),
compiled: path,
settings: inheritable_setting.route.except(:saved_declared_params, :saved_validations)
))
end
end.flatten
def map_routes
options[:method].map { |method| options[:path].map { |path| yield method, path } }
end

def prepare_path(path)
Expand All @@ -196,13 +206,6 @@ def namespace
@namespace ||= Namespace.joined_space_path(namespace_stackable(:namespace))
end

def compile_path(prepared_path, anchor = true, requirements = {})
endpoint_options = {}
endpoint_options[:version] = /#{namespace_inheritable(:version).join('|')}/ if namespace_inheritable(:version)
endpoint_options.merge!(requirements)
Rack::Mount::Strexp.compile(prepared_path, endpoint_options, %w( / . ? ), anchor)
end

def call(env)
lazy_initialize!
dup.call!(env)
Expand Down Expand Up @@ -275,11 +278,7 @@ def build_stack
(namespace_stackable(:middleware) || []).each do |m|
m = m.dup
block = m.pop if m.last.is_a?(Proc)
if block
b.use(*m, &block)
else
b.use(*m)
end
block ? b.use(*m, &block) : b.use(*m)
end

if namespace_inheritable(:version)
Expand All @@ -305,9 +304,7 @@ def build_stack
def build_helpers
helpers = namespace_stackable(:helpers) || []
Module.new do
helpers.each do |mod_to_include|
include mod_to_include
end
helpers.each { |mod_to_include| include mod_to_include }
end
end

Expand Down Expand Up @@ -348,9 +345,7 @@ def run_validators(validators, request)

def run_filters(filters, type = :other)
ActiveSupport::Notifications.instrument('endpoint_run_filters.grape', endpoint: self, filters: filters, type: type) do
(filters || []).each do |filter|
instance_eval(&filter)
end
(filters || []).each { |filter| instance_eval(&filter) }
end
post_extension = DSL::InsideRoute.post_filter_methods(type)
extend post_extension if post_extension
Expand Down
4 changes: 2 additions & 2 deletions lib/grape/error_formatter/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ def present(message, env)

presenter = env[Grape::Env::API_ENDPOINT].entity_class_for_obj(message, present_options)

unless presenter || env[Grape::Env::RACK_ROUTING_ARGS].nil?
unless presenter || env[Grape::Env::GRAPE_ROUTING_ARGS].nil?
# env['api.endpoint'].route does not work when the error occurs within a middleware
# the Endpoint does not have a valid env at this moment
http_codes = env[Grape::Env::RACK_ROUTING_ARGS][:route_info].route_http_codes || []
http_codes = env[Grape::Env::GRAPE_ROUTING_ARGS][:route_info].http_codes || []
found_code = http_codes.find do |http_code|
(http_code[0].to_i == env[Grape::Env::API_ENDPOINT].status) && http_code[2].respond_to?(:represent)
end if env[Grape::Env::API_ENDPOINT].request
Expand Down
Loading

0 comments on commit 1ab3595

Please sign in to comment.