Class: Hanami::Router

Inherits:
Object
  • Object
show all
Defined in:
lib/hanami/router.rb,
lib/hanami/router/node.rb,
lib/hanami/router/trie.rb,
lib/hanami/router/block.rb,
lib/hanami/router/route.rb,
lib/hanami/router/errors.rb,
lib/hanami/router/params.rb,
lib/hanami/router/prefix.rb,
lib/hanami/router/segment.rb,
lib/hanami/router/version.rb,
lib/hanami/router/redirect.rb,
lib/hanami/router/constants.rb,
lib/hanami/router/inspector.rb,
lib/hanami/router/url_helpers.rb,
lib/hanami/router/formatter/csv.rb,
lib/hanami/router/recognized_route.rb,
lib/hanami/router/formatter/human_friendly.rb

Overview

Rack compatible, lightweight and fast HTTP Router.

Since:

  • 0.1.0

Defined Under Namespace

Modules: Formatter Classes: Block, Error, Inspector, InvalidRouteExpansionError, MissingEndpointError, MissingRouteError, Node, NotRoutableEndpointError, Params, Prefix, RecognizedRoute, Redirect, Route, Segment, Trie, UnknownHTTPStatusCodeError, UrlHelpers

Constant Summary collapse

VERSION =

Returns the hanami-router version.

Returns:

  • (String)

Since:

  • 0.1.0

"2.0.0"
ROUTER_PARSED_BODY =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Since:

  • 2.0.0

"router.parsed_body"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(base_url: DEFAULT_BASE_URL, prefix: DEFAULT_PREFIX, resolver: DEFAULT_RESOLVER, not_found: NOT_FOUND, block_context: nil, inspector: nil, &blk) ⇒ Hanami::Router

Initialize the router

Examples:

Base usage

require "hanami/router"

Hanami::Router.new do
  get "/", to: ->(*) { [200, {}, ["OK"]] }
end

Parameters:

  • base_url (String) (defaults to: DEFAULT_BASE_URL)

    the base URL where the HTTP application is deployed

  • prefix (String) (defaults to: DEFAULT_PREFIX)

    the relative URL prefix where the HTTP application is deployed

  • resolver (#call(path, to)) (defaults to: DEFAULT_RESOLVER)

    a resolver for route endpoints

  • block_context (Hanami::Router::Block::Context) (defaults to: nil)
  • not_found (#call(env)) (defaults to: NOT_FOUND)

    default handler when route is not matched

  • blk (Proc)

    the route definitions

Since:

  • 0.1.0



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/hanami/router.rb', line 75

def initialize(base_url: DEFAULT_BASE_URL, prefix: DEFAULT_PREFIX, resolver: DEFAULT_RESOLVER, not_found: NOT_FOUND, block_context: nil, inspector: nil, &blk) # rubocop:disable Layout/LineLength
  # TODO: verify if Prefix can handle both name and path prefix
  @path_prefix = Prefix.new(prefix)
  @name_prefix = Prefix.new("")
  @url_helpers = UrlHelpers.new(base_url)
  @base_url = base_url
  @resolver = resolver
  @not_found = not_found
  @block_context = block_context
  @fixed = {}
  @variable = {}
  @globbed = {}
  @mounted = {}
  @blk = blk
  @inspector = inspector
  instance_eval(&blk) if blk
end

Instance Attribute Details

#inspectorHanami::Router::Inspector (readonly)

Routes inspector

Returns:

Since:

  • 2.0.0



35
36
37
# File 'lib/hanami/router.rb', line 35

def inspector
  @inspector
end

#url_helpersObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

URL helpers for other Hanami integrations

Since:

  • 2.0.0



28
29
30
# File 'lib/hanami/router.rb', line 28

def url_helpers
  @url_helpers
end

Class Method Details

.define(&blk) ⇒ Proc

Returns the given block as it is.

Examples:

# apps/web/config/routes.rb
Hanami::Router.define do
  get "/", to: ->(*) { ... }
end

Parameters:

  • blk (Proc)

    a set of route definitions

Returns:

  • (Proc)

    the given block

Since:

  • 0.5.0



50
51
52
# File 'lib/hanami/router.rb', line 50

def self.define(&blk)
  blk
end

Instance Method Details

#call(env) ⇒ Array

Resolve the given Rack env to a registered endpoint and invokes it.

Parameters:

  • env (Hash)

    a Rack env

Returns:

  • (Array)

    a finalized Rack env response

Since:

  • 0.1.0



100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/hanami/router.rb', line 100

def call(env)
  endpoint, params = lookup(env)

  unless endpoint
    return not_allowed(env) ||
           not_found(env)
  end

  endpoint.call(
    _params(env, params)
  ).to_a
end

#delete(path, to: nil, as: nil, **constraints, &blk) ⇒ Object

Defines a route that accepts DELETE requests for the given path.

Parameters:

  • path (String)

    the relative URL to be matched

  • to (#call) (defaults to: nil)

    the Rack endpoint

  • as (Symbol) (defaults to: nil)

    a unique name for the route

  • constraints (Hash)

    a set of constraints for path variables

  • blk (Proc)

    the anonymous proc to be used as endpoint for the route

See Also:

Since:

  • 0.1.0



275
276
277
# File 'lib/hanami/router.rb', line 275

def delete(path, to: nil, as: nil, **constraints, &blk)
  add_route(::Rack::DELETE, path, to, as, constraints, &blk)
end

#fixed(env) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Since:

  • 2.0.0



612
613
614
# File 'lib/hanami/router.rb', line 612

def fixed(env)
  @fixed.dig(env[::Rack::REQUEST_METHOD], env[::Rack::PATH_INFO])
end

#get(path, to: nil, as: nil, **constraints, &blk) ⇒ Object

Defines a route that accepts GET requests for the given path. It also defines a route to accept HEAD requests.

Examples:

Proc endpoint

require "hanami/router"

Hanami::Router.new do
  get "/", to: ->(*) { [200, {}, ["OK"]] }
end

Block endpoint

require "hanami/router"

Hanami::Router.new do
  get "/" do
    "OK"
  end
end

Named route

require "hanami/router"

router = Hanami::Router.new do
  get "/", to: ->(*) { [200, {}, ["OK"]] }, as: :welcome
end

router.path(:welcome) # => "/"
router.url(:welcome)  # => "http://localhost/"

Constraints

require "hanami/router"

Hanami::Router.new do
  get "/users/:id", to: ->(*) { [200, {}, ["OK"]] }, id: /\d+/
end

Parameters:

  • path (String)

    the relative URL to be matched

  • to (#call) (defaults to: nil)

    the Rack endpoint

  • as (Symbol) (defaults to: nil)

    a unique name for the route

  • constraints (Hash)

    a set of constraints for path variables

  • blk (Proc)

    the anonymous proc to be used as endpoint for the route

See Also:

Since:

  • 0.1.0



202
203
204
205
# File 'lib/hanami/router.rb', line 202

def get(path, to: nil, as: nil, **constraints, &blk)
  add_route(::Rack::GET, path, to, as, constraints, &blk)
  add_route(::Rack::HEAD, path, to, as, constraints, &blk)
end

#globbed(env) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Since:

  • 2.0.0



624
625
626
627
628
629
630
631
632
# File 'lib/hanami/router.rb', line 624

def globbed(env)
  @globbed[env[::Rack::REQUEST_METHOD]]&.each do |path, to|
    if (match = path.match(env[::Rack::PATH_INFO]))
      return [to, match.named_captures]
    end
  end

  nil
end

Defines a route that accepts LINK requests for the given path.

Parameters:

  • path (String)

    the relative URL to be matched

  • to (#call) (defaults to: nil)

    the Rack endpoint

  • as (Symbol) (defaults to: nil)

    a unique name for the route

  • constraints (Hash)

    a set of constraints for path variables

  • blk (Proc)

    the anonymous proc to be used as endpoint for the route

See Also:

Since:

  • 0.1.0



329
330
331
# File 'lib/hanami/router.rb', line 329

def link(path, to: nil, as: nil, **constraints, &blk)
  add_route(::Rack::LINK, path, to, as, constraints, &blk)
end

#mount(app, at:, **constraints) ⇒ Object

Mount a Rack application at the specified path. All the requests starting with the specified path, will be forwarded to the given application.

All the other methods (eg ‘#get`) support callable objects, but they restrict the range of the acceptable HTTP verb. Mounting an application with #mount doesn’t apply this kind of restriction at the router level, but let the application to decide.

Examples:

require "hanami/router"

Hanami::Router.new do
  mount MyRackApp.new, at: "/foo"
end

Parameters:

  • app (#call)

    a class or an object that responds to #call

  • at (String)

    the relative path where to mount the app

  • constraints (Hash)

    a set of constraints for path variables

Since:

  • 0.1.1



423
424
425
426
427
428
429
430
431
# File 'lib/hanami/router.rb', line 423

def mount(app, at:, **constraints)
  path = prefixed_path(at)
  prefix = Segment.fabricate(path, **constraints)

  @mounted[prefix] = @resolver.call(path, app)
  if inspect?
    @inspector.add_route(Route.new(http_method: "*", path: at, to: app, constraints: constraints))
  end
end

#mounted(env) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Since:

  • 2.0.0



636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
# File 'lib/hanami/router.rb', line 636

def mounted(env)
  @mounted.each do |prefix, app|
    next unless (match = prefix.peek_match(env[::Rack::PATH_INFO]))

    # TODO: ensure compatibility with existing env[::Rack::SCRIPT_NAME]
    # TODO: cleanup this code
    env[::Rack::SCRIPT_NAME] = env[::Rack::SCRIPT_NAME].to_s + prefix.to_s
    env[::Rack::PATH_INFO] = env[::Rack::PATH_INFO].sub(prefix.to_s, EMPTY_STRING)
    env[::Rack::PATH_INFO] = DEFAULT_PREFIX if env[::Rack::PATH_INFO] == EMPTY_STRING

    return [app, match.named_captures]
  end

  nil
end

#not_allowed(env) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Since:

  • 2.0.0



654
655
656
657
658
659
# File 'lib/hanami/router.rb', line 654

def not_allowed(env)
  (_not_allowed_fixed(env) ||
  _not_allowed_variable(env)) and return [HTTP_STATUS_NOT_ALLOWED,
                                          {::Rack::CONTENT_LENGTH => HTTP_BODY_NOT_ALLOWED_LENGTH},
                                          [HTTP_BODY_NOT_ALLOWED]]
end

#not_found(env) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Since:

  • 2.0.0



663
664
665
# File 'lib/hanami/router.rb', line 663

def not_found(env)
  @not_found.call(env)
end

#options(path, to: nil, as: nil, **constraints, &blk) ⇒ Object

Defines a route that accepts OPTIONS requests for the given path.

Parameters:

  • path (String)

    the relative URL to be matched

  • to (#call) (defaults to: nil)

    the Rack endpoint

  • as (Symbol) (defaults to: nil)

    a unique name for the route

  • constraints (Hash)

    a set of constraints for path variables

  • blk (Proc)

    the anonymous proc to be used as endpoint for the route

See Also:

Since:

  • 0.1.0



311
312
313
# File 'lib/hanami/router.rb', line 311

def options(path, to: nil, as: nil, **constraints, &blk)
  add_route(::Rack::OPTIONS, path, to, as, constraints, &blk)
end

#patch(path, to: nil, as: nil, **constraints, &blk) ⇒ Object

Defines a route that accepts PATCH requests for the given path.

Parameters:

  • path (String)

    the relative URL to be matched

  • to (#call) (defaults to: nil)

    the Rack endpoint

  • as (Symbol) (defaults to: nil)

    a unique name for the route

  • constraints (Hash)

    a set of constraints for path variables

  • blk (Proc)

    the anonymous proc to be used as endpoint for the route

See Also:

Since:

  • 0.1.0



239
240
241
# File 'lib/hanami/router.rb', line 239

def patch(path, to: nil, as: nil, **constraints, &blk)
  add_route(::Rack::PATCH, path, to, as, constraints, &blk)
end

#path(name, variables = {}) ⇒ String

Generate an relative URL for a specified named route. The additional arguments will be used to compose the relative URL - in

case it has tokens to match - and for compose the query string.

Examples:

require "hanami/router"

router = Hanami::Router.new(base_url: "https://hanamirb.org") do
  get "/login", to: ->(*) { ... }, as: :login
  get "/:name", to: ->(*) { ... }, as: :framework
end

router.path(:login)                          # => "/login"
router.path(:login, return_to: "/dashboard") # => "/login?return_to=%2Fdashboard"
router.path(:framework, name: "router")      # => "/router"

Parameters:

  • name (Symbol)

    the route name

Returns:

  • (String)

Raises:

See Also:

Since:

  • 0.1.0



459
460
461
# File 'lib/hanami/router.rb', line 459

def path(name, variables = {})
  url_helpers.path(name, variables)
end

#post(path, to: nil, as: nil, **constraints, &blk) ⇒ Object

Defines a route that accepts POST requests for the given path.

Parameters:

  • path (String)

    the relative URL to be matched

  • to (#call) (defaults to: nil)

    the Rack endpoint

  • as (Symbol) (defaults to: nil)

    a unique name for the route

  • constraints (Hash)

    a set of constraints for path variables

  • blk (Proc)

    the anonymous proc to be used as endpoint for the route

See Also:

Since:

  • 0.1.0



221
222
223
# File 'lib/hanami/router.rb', line 221

def post(path, to: nil, as: nil, **constraints, &blk)
  add_route(::Rack::POST, path, to, as, constraints, &blk)
end

#put(path, to: nil, as: nil, **constraints, &blk) ⇒ Object

Defines a route that accepts PUT requests for the given path.

Parameters:

  • path (String)

    the relative URL to be matched

  • to (#call) (defaults to: nil)

    the Rack endpoint

  • as (Symbol) (defaults to: nil)

    a unique name for the route

  • constraints (Hash)

    a set of constraints for path variables

  • blk (Proc)

    the anonymous proc to be used as endpoint for the route

See Also:

Since:

  • 0.1.0



257
258
259
# File 'lib/hanami/router.rb', line 257

def put(path, to: nil, as: nil, **constraints, &blk)
  add_route(::Rack::PUT, path, to, as, constraints, &blk)
end

#recognize(env, params = {}, options = {}) ⇒ Hanami::Routing::RecognizedRoute

Recognize the given env, path, or name and return a route for testing inspection.

If the route cannot be recognized, it still returns an object for testing inspection.

Examples:

Successful Path Recognition

require "hanami/router"

router = Hanami::Router.new do
  get "/books/:id", to: ->(*) { ... }, as: :book
end

route = router.recognize("/books/23")
route.verb      # => "GET" (default)
route.routable? # => true
route.params    # => {:id=>"23"}

Successful Rack Env Recognition

require "hanami/router"

router = Hanami::Router.new do
  get "/books/:id", to: ->(*) { ... }, as: :book
end

route = router.recognize(Rack::MockRequest.env_for("/books/23"))
route.verb      # => "GET" (default)
route.routable? # => true
route.params    # => {:id=>"23"}

Successful Named Route Recognition

require "hanami/router"

router = Hanami::Router.new do
  get "/books/:id", to: ->(*) { ... }, as: :book
end

route = router.recognize(:book, id: 23)
route.verb      # => "GET" (default)
route.routable? # => true
route.params    # => {:id=>"23"}

Failing Recognition For Unknown Path

require "hanami/router"

router = Hanami::Router.new do
  get "/books/:id", to: ->(*) { ... }, as: :book
end

route = router.recognize("/books")
route.verb      # => "GET" (default)
route.routable? # => false

Failing Recognition For Path With Wrong HTTP Verb

require "hanami/router"

router = Hanami::Router.new do
  get "/books/:id", to: ->(*) { ... }, as: :book
end

route = router.recognize("/books/23", method: :post)
route.verb      # => "POST"
route.routable? # => false

Failing Recognition For Rack Env With Wrong HTTP Verb

require "hanami/router"

router = Hanami::Router.new do
  get "/books/:id", to: ->(*) { ... }, as: :book
end

route = router.recognize(Rack::MockRequest.env_for("/books/23", method: :post))
route.verb      # => "POST"
route.routable? # => false

Failing Recognition Named Route With Wrong Params

require "hanami/router"

router = Hanami::Router.new do
  get "/books/:id", to: ->(*) { ... }, as: :book
end

route = router.recognize(:book)
route.verb      # => "GET" (default)
route.routable? # => false

Failing Recognition Named Route With Wrong HTTP Verb

require "hanami/router"

router = Hanami::Router.new do
  get "/books/:id", to: ->(*) { ... }, as: :book
end

route = router.recognize(:book, {method: :post}, {id: 1})
route.verb      # => "POST"
route.routable? # => false
route.params    # => {:id=>"1"}

Parameters:

  • env (Hash, String, Symbol)

    Rack env, path or route name

  • options (Hash) (defaults to: {})

    a set of options for Rack env or route params

  • params (Hash) (defaults to: {})

    a set of params

Returns:

  • (Hanami::Routing::RecognizedRoute)

    the recognized route

See Also:

  • #env_for
  • Hanami::Routing::RecognizedRoute

Since:

  • 0.5.0



601
602
603
604
605
606
607
608
# File 'lib/hanami/router.rb', line 601

def recognize(env, params = {}, options = {})
  require "hanami/router/recognized_route"

  env = env_for(env, params, options)
  endpoint, params = lookup(env)

  RecognizedRoute.new(endpoint, _params(env, params))
end

#redirect(path, to: nil, as: nil, code: DEFAULT_REDIRECT_CODE) ⇒ Object

Defines a route that redirects the incoming request to another path.

Parameters:

  • path (String)

    the relative URL to be matched

  • to (#call) (defaults to: nil)

    the Rack endpoint

  • as (Symbol) (defaults to: nil)

    a unique name for the route

  • code (Integer) (defaults to: DEFAULT_REDIRECT_CODE)

    a HTTP status code to use for the redirect

Raises:

See Also:

Since:

  • 0.1.0



364
365
366
# File 'lib/hanami/router.rb', line 364

def redirect(path, to: nil, as: nil, code: DEFAULT_REDIRECT_CODE)
  get(path, to: _redirect(to, code), as: as)
end

#root(to: nil, &blk) ⇒ Object

Defines a named root route (a GET route for “/”)

Examples:

Proc endpoint

require "hanami/router"

router = Hanami::Router.new do
  root to: ->(env) { [200, {}, ["Hello from Hanami!"]] }
end

Block endpoint

require "hanami/router"

router = Hanami::Router.new do
  root do
    "Hello from Hanami!"
  end
end

URL helpers

require "hanami/router"

router = Hanami::Router.new(base_url: "https://hanamirb.org") do
  root do
    "Hello from Hanami!"
  end
end

router.path(:root) # => "/"
router.url(:root)  # => "https://hanamirb.org"

Parameters:

  • to (#call) (defaults to: nil)

    the Rack endpoint

  • blk (Proc)

    the anonymous proc to be used as endpoint for the route

See Also:

Since:

  • 0.7.0



151
152
153
# File 'lib/hanami/router.rb', line 151

def root(to: nil, &blk)
  get(ROOT_PATH, to: to, as: :root, &blk)
end

#scope(path, &blk) ⇒ Object

Defines a routing scope. Routes defined in the context of a scope, inherit the given path as path prefix and as a named routes prefix.

Examples:

require "hanami/router"

router = Hanami::Router.new do
  scope "v1" do
    get "/users", to: ->(*) { ... }, as: :users
  end
end

router.path(:v1_users) # => "/v1/users"

Parameters:

  • path (String)

    the scope path to be used as a path prefix

  • blk (Proc)

    the routes definitions withing the scope

See Also:

Since:

  • 2.0.0



388
389
390
391
392
393
394
395
396
397
398
399
400
# File 'lib/hanami/router.rb', line 388

def scope(path, &blk)
  path_prefix = @path_prefix
  name_prefix = @name_prefix

  begin
    @path_prefix = @path_prefix.join(path.to_s)
    @name_prefix = @name_prefix.join(path.to_s)
    instance_eval(&blk)
  ensure
    @path_prefix = path_prefix
    @name_prefix = name_prefix
  end
end

#trace(path, to: nil, as: nil, **constraints, &blk) ⇒ Object

Defines a route that accepts TRACE requests for the given path.

Parameters:

  • path (String)

    the relative URL to be matched

  • to (#call) (defaults to: nil)

    the Rack endpoint

  • as (Symbol) (defaults to: nil)

    a unique name for the route

  • constraints (Hash)

    a set of constraints for path variables

  • blk (Proc)

    the anonymous proc to be used as endpoint for the route

See Also:

Since:

  • 0.1.0



293
294
295
# File 'lib/hanami/router.rb', line 293

def trace(path, to: nil, as: nil, **constraints, &blk)
  add_route(::Rack::TRACE, path, to, as, constraints, &blk)
end

Defines a route that accepts UNLINK requests for the given path.

Parameters:

  • path (String)

    the relative URL to be matched

  • to (#call) (defaults to: nil)

    the Rack endpoint

  • as (Symbol) (defaults to: nil)

    a unique name for the route

  • constraints (Hash)

    a set of constraints for path variables

  • blk (Proc)

    the anonymous proc to be used as endpoint for the route

See Also:

Since:

  • 0.1.0



347
348
349
# File 'lib/hanami/router.rb', line 347

def unlink(path, to: nil, as: nil, **constraints, &blk)
  add_route(::Rack::UNLINK, path, to, as, constraints, &blk)
end

#url(name, variables = {}) ⇒ String

Generate an absolute URL for a specified named route. The additional arguments will be used to compose the relative URL - in

case it has tokens to match - and for compose the query string.

Examples:

require "hanami/router"

router = Hanami::Router.new(base_url: "https://hanamirb.org") do
  get "/login", to: ->(*) { ... }, as: :login
  get "/:name", to: ->(*) { ... }, as: :framework
end

router.url(:login)                          # => "https://hanamirb.org/login"
router.url(:login, return_to: "/dashboard") # => "https://hanamirb.org/login?return_to=%2Fdashboard"
router.url(:framework, name: "router")      # => "https://hanamirb.org/router"

Parameters:

  • name (Symbol)

    the route name

Returns:

  • (String)

Raises:

See Also:

Since:

  • 0.1.0



489
490
491
# File 'lib/hanami/router.rb', line 489

def url(name, variables = {})
  url_helpers.url(name, variables)
end

#variable(env) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Since:

  • 2.0.0



618
619
620
# File 'lib/hanami/router.rb', line 618

def variable(env)
  @variable[env[::Rack::REQUEST_METHOD]]&.find(env[::Rack::PATH_INFO])
end