hanami-router 1.3.1 → 2.0.0.alpha4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +44 -0
- data/LICENSE.md +1 -1
- data/README.md +97 -443
- data/hanami-router.gemspec +23 -19
- data/lib/hanami/middleware/body_parser.rb +21 -15
- data/lib/hanami/middleware/body_parser/class_interface.rb +62 -56
- data/lib/hanami/middleware/body_parser/errors.rb +7 -4
- data/lib/hanami/middleware/body_parser/json_parser.rb +9 -7
- data/lib/hanami/middleware/body_parser/parser.rb +58 -0
- data/lib/hanami/middleware/error.rb +16 -0
- data/lib/hanami/router.rb +548 -957
- data/lib/hanami/router/block.rb +88 -0
- data/lib/hanami/router/error.rb +67 -0
- data/lib/hanami/router/node.rb +91 -0
- data/lib/hanami/router/params.rb +35 -0
- data/lib/hanami/router/prefix.rb +67 -0
- data/lib/hanami/router/recognized_route.rb +92 -0
- data/lib/hanami/router/redirect.rb +28 -0
- data/lib/hanami/router/segment.rb +19 -0
- data/lib/hanami/router/trie.rb +63 -0
- data/lib/hanami/router/url_helpers.rb +40 -0
- data/lib/hanami/router/version.rb +4 -1
- metadata +61 -41
- data/lib/hanami-router.rb +0 -1
- data/lib/hanami/routing/endpoint.rb +0 -195
- data/lib/hanami/routing/endpoint_resolver.rb +0 -238
- data/lib/hanami/routing/error.rb +0 -7
- data/lib/hanami/routing/force_ssl.rb +0 -212
- data/lib/hanami/routing/http_router.rb +0 -220
- data/lib/hanami/routing/http_router_monkey_patch.rb +0 -38
- data/lib/hanami/routing/namespace.rb +0 -98
- data/lib/hanami/routing/parsers.rb +0 -113
- data/lib/hanami/routing/parsing/json_parser.rb +0 -33
- data/lib/hanami/routing/parsing/parser.rb +0 -61
- data/lib/hanami/routing/recognized_route.rb +0 -219
- data/lib/hanami/routing/resource.rb +0 -119
- data/lib/hanami/routing/resource/action.rb +0 -402
- data/lib/hanami/routing/resource/nested.rb +0 -41
- data/lib/hanami/routing/resource/options.rb +0 -74
- data/lib/hanami/routing/resources.rb +0 -48
- data/lib/hanami/routing/resources/action.rb +0 -156
- data/lib/hanami/routing/route.rb +0 -71
- data/lib/hanami/routing/routes_inspector.rb +0 -221
data/hanami-router.gemspec
CHANGED
@@ -1,30 +1,34 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require
|
5
|
+
require "hanami/router/version"
|
5
6
|
|
6
7
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name =
|
8
|
+
spec.name = "hanami-router"
|
8
9
|
spec.version = Hanami::Router::VERSION
|
9
|
-
spec.authors = [
|
10
|
-
spec.email = [
|
11
|
-
spec.description =
|
12
|
-
spec.summary =
|
13
|
-
spec.homepage =
|
14
|
-
spec.license =
|
10
|
+
spec.authors = ["Luca Guidi"]
|
11
|
+
spec.email = ["me@lucaguidi.com"]
|
12
|
+
spec.description = "Rack compatible HTTP router for Ruby"
|
13
|
+
spec.summary = "Rack compatible HTTP router for Ruby and Hanami"
|
14
|
+
spec.homepage = "http://hanamirb.org"
|
15
|
+
spec.license = "MIT"
|
15
16
|
|
16
17
|
spec.files = `git ls-files -- lib/* CHANGELOG.md LICENSE.md README.md hanami-router.gemspec`.split($/)
|
17
18
|
spec.executables = []
|
18
19
|
spec.test_files = spec.files.grep(%r{^(test)/})
|
19
|
-
spec.require_paths = [
|
20
|
-
spec.required_ruby_version =
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
spec.required_ruby_version = ">= 2.6.0"
|
22
|
+
|
23
|
+
spec.add_dependency "rack", "~> 2.0"
|
24
|
+
spec.add_dependency "mustermann", "~> 1.0"
|
25
|
+
spec.add_dependency "mustermann-contrib", "~> 1.0"
|
21
26
|
|
22
|
-
spec.
|
23
|
-
spec.
|
24
|
-
spec.
|
27
|
+
spec.add_development_dependency "bundler", ">= 1.6", "< 3"
|
28
|
+
spec.add_development_dependency "rake", "~> 13"
|
29
|
+
spec.add_development_dependency "rack-test", "~> 1.0"
|
30
|
+
spec.add_development_dependency "rspec", "~> 3.8"
|
25
31
|
|
26
|
-
spec.add_development_dependency
|
27
|
-
spec.add_development_dependency
|
28
|
-
spec.add_development_dependency 'rack-test', '~> 1.0'
|
29
|
-
spec.add_development_dependency 'rspec', '~> 3.7'
|
32
|
+
spec.add_development_dependency "rubocop", "0.91"
|
33
|
+
spec.add_development_dependency "rubocop-performance", "1.8.1"
|
30
34
|
end
|
@@ -1,14 +1,19 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "hanami/router/params"
|
4
|
+
require "hanami/middleware/error"
|
3
5
|
|
4
6
|
module Hanami
|
5
7
|
module Middleware
|
6
8
|
# @since 1.3.0
|
7
9
|
# @api private
|
8
10
|
class BodyParser
|
11
|
+
require_relative "body_parser/class_interface"
|
12
|
+
require_relative "body_parser/parser"
|
13
|
+
|
9
14
|
# @since 1.3.0
|
10
15
|
# @api private
|
11
|
-
CONTENT_TYPE =
|
16
|
+
CONTENT_TYPE = "CONTENT_TYPE"
|
12
17
|
|
13
18
|
# @since 1.3.0
|
14
19
|
# @api private
|
@@ -16,17 +21,17 @@ module Hanami
|
|
16
21
|
|
17
22
|
# @since 1.3.0
|
18
23
|
# @api private
|
19
|
-
RACK_INPUT =
|
24
|
+
RACK_INPUT = "rack.input"
|
20
25
|
|
21
26
|
# @since 1.3.0
|
22
27
|
# @api private
|
23
|
-
ROUTER_PARAMS =
|
28
|
+
ROUTER_PARAMS = "router.params"
|
24
29
|
|
25
30
|
# @api private
|
26
|
-
ROUTER_PARSED_BODY =
|
31
|
+
ROUTER_PARSED_BODY = "router.parsed_body"
|
27
32
|
|
28
33
|
# @api private
|
29
|
-
FALLBACK_KEY =
|
34
|
+
FALLBACK_KEY = "_"
|
30
35
|
|
31
36
|
extend ClassInterface
|
32
37
|
|
@@ -55,21 +60,21 @@ module Hanami
|
|
55
60
|
parser_names = Array(parser_names)
|
56
61
|
return {} if parser_names.empty?
|
57
62
|
|
58
|
-
parser_names.each_with_object({})
|
63
|
+
parser_names.each_with_object({}) do |name, parsers|
|
59
64
|
parser = self.class.for(name)
|
60
65
|
|
61
66
|
parser.mime_types.each do |mime|
|
62
67
|
parsers[mime] = parser
|
63
68
|
end
|
64
|
-
|
69
|
+
end
|
65
70
|
end
|
66
71
|
|
67
72
|
# @api private
|
68
73
|
def _symbolize(body)
|
69
|
-
if body.is_a?(Hash)
|
70
|
-
|
74
|
+
if body.is_a?(::Hash)
|
75
|
+
Router::Params.deep_symbolize(body)
|
71
76
|
else
|
72
|
-
{
|
77
|
+
{FALLBACK_KEY => body}
|
73
78
|
end
|
74
79
|
end
|
75
80
|
|
@@ -82,9 +87,10 @@ module Hanami
|
|
82
87
|
|
83
88
|
# @api private
|
84
89
|
def media_type(env)
|
85
|
-
|
86
|
-
|
87
|
-
|
90
|
+
ct = content_type(env)
|
91
|
+
return unless ct
|
92
|
+
|
93
|
+
ct.split(MEDIA_TYPE_MATCHER, 2).first.downcase
|
88
94
|
end
|
89
95
|
|
90
96
|
# @api private
|
@@ -1,56 +1,62 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require_relative
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "errors"
|
4
|
+
|
5
|
+
module Hanami
|
6
|
+
module Middleware
|
7
|
+
# HTTP request body parser
|
8
|
+
class BodyParser
|
9
|
+
# @api private
|
10
|
+
# @since 1.3.0
|
11
|
+
module ClassInterface
|
12
|
+
# @api private
|
13
|
+
# @since 1.3.0
|
14
|
+
def for(parser)
|
15
|
+
parser =
|
16
|
+
case parser
|
17
|
+
when String, Symbol
|
18
|
+
require_parser(parser)
|
19
|
+
when Class
|
20
|
+
parser.new
|
21
|
+
else
|
22
|
+
parser
|
23
|
+
end
|
24
|
+
|
25
|
+
ensure_parser parser
|
26
|
+
|
27
|
+
parser
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
# @api private
|
33
|
+
# @since 1.3.0
|
34
|
+
PARSER_METHODS = %i[mime_types parse].freeze
|
35
|
+
|
36
|
+
# @api private
|
37
|
+
# @since 1.3.0
|
38
|
+
def ensure_parser(parser)
|
39
|
+
raise InvalidParserError.new(parser) unless PARSER_METHODS.all? { |method| parser.respond_to?(method) }
|
40
|
+
end
|
41
|
+
|
42
|
+
# @api private
|
43
|
+
# @since 1.3.0
|
44
|
+
def require_parser(parser)
|
45
|
+
require "hanami/middleware/body_parser/#{parser}_parser"
|
46
|
+
|
47
|
+
load_parser!("#{classify(parser)}Parser").new
|
48
|
+
rescue LoadError, NameError
|
49
|
+
raise UnknownParserError.new(parser)
|
50
|
+
end
|
51
|
+
|
52
|
+
def classify(parser)
|
53
|
+
parser.to_s.split(/_/).map(&:capitalize).join
|
54
|
+
end
|
55
|
+
|
56
|
+
def load_parser!(class_name)
|
57
|
+
Hanami::Middleware::BodyParser.const_get(class_name, false)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Hanami
|
4
4
|
module Middleware
|
@@ -9,14 +9,17 @@ module Hanami
|
|
9
9
|
# This is raised when parser fails to parse the body
|
10
10
|
#
|
11
11
|
# @since 1.3.0
|
12
|
-
class BodyParsingError < Hanami::
|
12
|
+
class BodyParsingError < Hanami::Middleware::Error
|
13
13
|
end
|
14
14
|
|
15
15
|
# @since 1.3.0
|
16
|
-
class UnknownParserError < Hanami::
|
16
|
+
class UnknownParserError < Hanami::Middleware::Error
|
17
|
+
def initialize(name)
|
18
|
+
super("Unknown body parser: `#{name.inspect}'")
|
19
|
+
end
|
17
20
|
end
|
18
21
|
|
19
|
-
class InvalidParserError < Hanami::
|
22
|
+
class InvalidParserError < Hanami::Middleware::Error
|
20
23
|
end
|
21
24
|
end
|
22
25
|
end
|
@@ -1,16 +1,18 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require_relative "parser"
|
3
5
|
|
4
6
|
module Hanami
|
5
7
|
module Middleware
|
6
8
|
class BodyParser
|
7
9
|
# @since 1.3.0
|
8
10
|
# @api private
|
9
|
-
class JsonParser
|
11
|
+
class JsonParser < Parser
|
10
12
|
# @since 1.3.0
|
11
13
|
# @api private
|
12
14
|
def mime_types
|
13
|
-
[
|
15
|
+
["application/json", "application/vnd.api+json"]
|
14
16
|
end
|
15
17
|
|
16
18
|
# Parse a json string
|
@@ -24,9 +26,9 @@ module Hanami
|
|
24
26
|
# @since 1.3.0
|
25
27
|
# @api private
|
26
28
|
def parse(body)
|
27
|
-
|
28
|
-
rescue
|
29
|
-
raise BodyParsingError.new(
|
29
|
+
JSON.parse(body)
|
30
|
+
rescue StandardError => exception
|
31
|
+
raise BodyParsingError.new(exception.message)
|
30
32
|
end
|
31
33
|
end
|
32
34
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hanami
|
4
|
+
module Middleware
|
5
|
+
class BodyParser
|
6
|
+
# Body parser abstract class
|
7
|
+
#
|
8
|
+
# @since 2.0.0
|
9
|
+
class Parser
|
10
|
+
# Declare supported MIME types
|
11
|
+
#
|
12
|
+
# @return [Array<String>] supported MIME types
|
13
|
+
#
|
14
|
+
# @abstract
|
15
|
+
# @since 2.0.0
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# require "hanami/middleware/body_parser"
|
19
|
+
#
|
20
|
+
# class XMLParser < Hanami::Middleware::BodyParser::Parser
|
21
|
+
# def mime_types
|
22
|
+
# ["application/xml", "text/xml"]
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
def mime_types
|
26
|
+
raise NotImplementedError
|
27
|
+
end
|
28
|
+
|
29
|
+
# Parse raw HTTP request body
|
30
|
+
#
|
31
|
+
# @param body [String] HTTP request body
|
32
|
+
#
|
33
|
+
# @return [Hash] the result of the parsing
|
34
|
+
#
|
35
|
+
# @raise [Hanami::Middleware::BodyParser::BodyParsingError] the error
|
36
|
+
# that must be raised if the parsing cannot be accomplished
|
37
|
+
#
|
38
|
+
# @abstract
|
39
|
+
# @since 2.0.0
|
40
|
+
#
|
41
|
+
# @example
|
42
|
+
# require "hanami/middleware/body_parser"
|
43
|
+
#
|
44
|
+
# class XMLParser < Hanami::Middleware::BodyParser::Parser
|
45
|
+
# def parse(body)
|
46
|
+
# # XML parsing
|
47
|
+
# # ...
|
48
|
+
# rescue => exception
|
49
|
+
# raise Hanami::Middleware::BodyParser::BodyParsingError.new(exception.message)
|
50
|
+
# end
|
51
|
+
# end
|
52
|
+
def parse(body)
|
53
|
+
raise NotImplementedError
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hanami
|
4
|
+
# Hanami Rack middleware
|
5
|
+
#
|
6
|
+
# @since 1.3.0
|
7
|
+
module Middleware
|
8
|
+
unless defined?(Error)
|
9
|
+
# Base error for Rack middleware
|
10
|
+
#
|
11
|
+
# @since 2.0.0
|
12
|
+
class Error < ::StandardError
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/hanami/router.rb
CHANGED
@@ -1,131 +1,30 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
|
5
|
-
require 'hanami/routing/resources'
|
6
|
-
require 'hanami/routing/error'
|
7
|
-
|
8
|
-
# Hanami
|
9
|
-
#
|
10
|
-
# @since 0.1.0
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rack/utils"
|
4
|
+
|
11
5
|
module Hanami
|
12
6
|
# Rack compatible, lightweight and fast HTTP Router.
|
13
7
|
#
|
14
8
|
# @since 0.1.0
|
15
|
-
#
|
16
|
-
# @example It offers an intuitive DSL, that supports most of the HTTP verbs:
|
17
|
-
# require 'hanami/router'
|
18
|
-
#
|
19
|
-
# endpoint = ->(env) { [200, {}, ['Welcome to Hanami::Router!']] }
|
20
|
-
# router = Hanami::Router.new do
|
21
|
-
# get '/', to: endpoint # => get and head requests
|
22
|
-
# post '/', to: endpoint
|
23
|
-
# put '/', to: endpoint
|
24
|
-
# patch '/', to: endpoint
|
25
|
-
# delete '/', to: endpoint
|
26
|
-
# options '/', to: endpoint
|
27
|
-
# trace '/', to: endpoint
|
28
|
-
# end
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
# @example Specify an endpoint with `:to` (Rack compatible object)
|
33
|
-
# require 'hanami/router'
|
34
|
-
#
|
35
|
-
# endpoint = ->(env) { [200, {}, ['Welcome to Hanami::Router!']] }
|
36
|
-
# router = Hanami::Router.new do
|
37
|
-
# get '/', to: endpoint
|
38
|
-
# end
|
39
|
-
#
|
40
|
-
# # :to is mandatory for the default resolver (`Hanami::Routing::EndpointResolver.new`),
|
41
|
-
# # This behavior can be changed by passing a custom resolver to `Hanami::Router#initialize`
|
42
|
-
#
|
43
|
-
#
|
44
|
-
#
|
45
|
-
# @example Specify an endpoint with `:to` (controller and action string)
|
46
|
-
# require 'hanami/router'
|
47
|
-
#
|
48
|
-
# router = Hanami::Router.new do
|
49
|
-
# get '/', to: 'articles#show' # => Articles::Show
|
50
|
-
# end
|
51
|
-
#
|
52
|
-
# # This is a builtin feature for a Hanami::Controller convention.
|
53
|
-
#
|
54
|
-
#
|
55
|
-
#
|
56
|
-
# @example Specify a named route with `:as`
|
57
|
-
# require 'hanami/router'
|
58
|
-
#
|
59
|
-
# endpoint = ->(env) { [200, {}, ['Welcome to Hanami::Router!']] }
|
60
|
-
# router = Hanami::Router.new(scheme: 'https', host: 'hanamirb.org') do
|
61
|
-
# get '/', to: endpoint, as: :root
|
62
|
-
# end
|
63
|
-
#
|
64
|
-
# router.path(:root) # => '/'
|
65
|
-
# router.url(:root) # => 'https://hanamirb.org/'
|
66
|
-
#
|
67
|
-
# # This isn't mandatory for the default route class (`Hanami::Routing::Route`),
|
68
|
-
# # This behavior can be changed by passing a custom route to `Hanami::Router#initialize`
|
69
|
-
#
|
70
|
-
# @example Mount an application
|
71
|
-
# require 'hanami/router'
|
72
|
-
#
|
73
|
-
# router = Hanami::Router.new do
|
74
|
-
# mount Api::App, at: '/api'
|
75
|
-
# end
|
76
|
-
#
|
77
|
-
# # All the requests starting with "/api" will be forwarded to Api::App
|
78
9
|
class Router
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
# @api private
|
91
|
-
REQUEST_METHOD = 'REQUEST_METHOD'.freeze
|
92
|
-
|
93
|
-
# @since 0.5.0
|
94
|
-
# @api private
|
95
|
-
PATH_INFO = 'PATH_INFO'.freeze
|
96
|
-
|
97
|
-
# @since 0.5.0
|
98
|
-
def initialize(env)
|
99
|
-
super %(Cannot find routable endpoint for #{ env[REQUEST_METHOD] } "#{ env[PATH_INFO] }")
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
# Defines root path
|
10
|
+
require "hanami/router/version"
|
11
|
+
require "hanami/router/error"
|
12
|
+
require "hanami/router/segment"
|
13
|
+
require "hanami/router/redirect"
|
14
|
+
require "hanami/router/prefix"
|
15
|
+
require "hanami/router/params"
|
16
|
+
require "hanami/router/trie"
|
17
|
+
require "hanami/router/block"
|
18
|
+
require "hanami/router/url_helpers"
|
19
|
+
|
20
|
+
# URL helpers for other Hanami integrations
|
104
21
|
#
|
105
|
-
# @since 0.7.0
|
106
22
|
# @api private
|
107
|
-
#
|
108
|
-
|
109
|
-
ROOT_PATH = '/'.freeze
|
23
|
+
# @since 2.0.0
|
24
|
+
attr_reader :url_helpers
|
110
25
|
|
111
26
|
# Returns the given block as it is.
|
112
27
|
#
|
113
|
-
# When Hanami::Router is used as a standalone gem and the routes are defined
|
114
|
-
# into a configuration file, some systems could raise an exception.
|
115
|
-
#
|
116
|
-
# Imagine the following file into a Ruby on Rails application:
|
117
|
-
#
|
118
|
-
# get '/', to: 'api#index'
|
119
|
-
#
|
120
|
-
# Because Ruby on Rails in production mode use to eager load code and the
|
121
|
-
# routes file uses top level method calls, it crashes the application.
|
122
|
-
#
|
123
|
-
# If we wrap these routes with <tt>Hanami::Router.define</tt>, the block
|
124
|
-
# doesn't get yielded but just returned to the caller as it is.
|
125
|
-
#
|
126
|
-
# Usually the receiver of this block is <tt>Hanami::Router#initialize</tt>,
|
127
|
-
# which finally evaluates the block.
|
128
|
-
#
|
129
28
|
# @param blk [Proc] a set of route definitions
|
130
29
|
#
|
131
30
|
# @return [Proc] the given block
|
@@ -135,888 +34,440 @@ module Hanami
|
|
135
34
|
# @example
|
136
35
|
# # apps/web/config/routes.rb
|
137
36
|
# Hanami::Router.define do
|
138
|
-
# get
|
37
|
+
# get "/", to: ->(*) { ... }
|
139
38
|
# end
|
140
39
|
def self.define(&blk)
|
141
40
|
blk
|
142
41
|
end
|
143
42
|
|
144
|
-
# Initialize the router
|
145
|
-
#
|
146
|
-
# @param options [Hash] the options to initialize the router
|
147
|
-
#
|
148
|
-
# @option options [String] :scheme The HTTP scheme (defaults to `"http"`)
|
149
|
-
# @option options [String] :host The URL host (defaults to `"localhost"`)
|
150
|
-
# @option options [String] :port The URL port (defaults to `"80"`)
|
151
|
-
# @option options [Object, #resolve, #find, #action_separator] :resolver
|
152
|
-
# the route resolver (defaults to `Hanami::Routing::EndpointResolver.new`)
|
153
|
-
# @option options [Object, #generate] :route the route class
|
154
|
-
# (defaults to `Hanami::Routing::Route`)
|
155
|
-
# @option options [String] :action_separator the separator between controller
|
156
|
-
# and action name (eg. 'dashboard#show', where '#' is the :action_separator)
|
157
|
-
# @option options [Array<Symbol,String,Object #mime_types, parse>] :parsers
|
158
|
-
# the body parsers for mime types
|
159
|
-
#
|
160
|
-
# @param blk [Proc] the optional block to define the routes
|
43
|
+
# Initialize the router
|
161
44
|
#
|
162
|
-
# @
|
45
|
+
# @param base_url [String] the base URL where the HTTP application is
|
46
|
+
# deployed
|
47
|
+
# @param prefix [String] the relative URL prefix where the HTTP application
|
48
|
+
# is deployed
|
49
|
+
# @param resolver [#call(path, to)] a resolver for route entpoints
|
50
|
+
# @param block_context [Hanami::Router::Block::Context)
|
51
|
+
# @param not_found [#call(env)] default handler when route is not matched
|
52
|
+
# @param blk [Proc] the route definitions
|
163
53
|
#
|
164
54
|
# @since 0.1.0
|
165
55
|
#
|
166
|
-
# @
|
167
|
-
# require 'hanami/router'
|
168
|
-
#
|
169
|
-
# endpoint = ->(env) { [200, {}, ['Welcome to Hanami::Router!']] }
|
170
|
-
#
|
171
|
-
# router = Hanami::Router.new
|
172
|
-
# router.get '/', to: endpoint
|
173
|
-
#
|
174
|
-
# # or
|
175
|
-
#
|
176
|
-
# router = Hanami::Router.new do
|
177
|
-
# get '/', to: endpoint
|
178
|
-
# end
|
179
|
-
#
|
180
|
-
# @example Body parsers
|
181
|
-
# require 'json'
|
182
|
-
# require 'hanami/router'
|
183
|
-
#
|
184
|
-
# # It parses JSON body and makes the attributes available to the params
|
185
|
-
#
|
186
|
-
# endpoint = ->(env) { [200, {},[env['router.params'].inspect]] }
|
187
|
-
#
|
188
|
-
# router = Hanami::Router.new(parsers: [:json]) do
|
189
|
-
# patch '/books/:id', to: endpoint
|
190
|
-
# end
|
191
|
-
#
|
192
|
-
# # From the shell
|
193
|
-
#
|
194
|
-
# curl http://localhost:2300/books/1 \
|
195
|
-
# -H "Content-Type: application/json" \
|
196
|
-
# -H "Accept: application/json" \
|
197
|
-
# -d '{"published":"true"}' \
|
198
|
-
# -X PATCH
|
199
|
-
#
|
200
|
-
# # It returns
|
56
|
+
# @return [Hanami::Router]
|
201
57
|
#
|
202
|
-
#
|
58
|
+
# @example Base usage
|
59
|
+
# require "hanami/router"
|
203
60
|
#
|
204
|
-
#
|
205
|
-
#
|
206
|
-
#
|
207
|
-
#
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
# endpoint = ->(env) { [200, {},[env['router.params'].inspect]] }
|
221
|
-
#
|
222
|
-
# router = Hanami::Router.new(parsers: [XmlParser.new]) do
|
223
|
-
# patch '/authors/:id', to: endpoint
|
224
|
-
# end
|
225
|
-
#
|
226
|
-
# # From the shell
|
227
|
-
#
|
228
|
-
# curl http://localhost:2300/authors/1 \
|
229
|
-
# -H "Content-Type: application/xml" \
|
230
|
-
# -H "Accept: application/xml" \
|
231
|
-
# -d '<name>LG</name>' \
|
232
|
-
# -X PATCH
|
233
|
-
#
|
234
|
-
# # It returns
|
235
|
-
#
|
236
|
-
# [200, {}, ["{:name=>\"LG\",:id=>\"1\"}"]]
|
237
|
-
def initialize(options = {}, &blk)
|
238
|
-
@router = Routing::HttpRouter.new(options)
|
239
|
-
define(&blk)
|
61
|
+
# Hanami::Router.new do
|
62
|
+
# get "/", to: ->(*) { [200, {}, ["OK"]] }
|
63
|
+
# end
|
64
|
+
def initialize(base_url: DEFAULT_BASE_URL, prefix: DEFAULT_PREFIX, resolver: DEFAULT_RESOLVER, not_found: NOT_FOUND, block_context: nil, &blk) # rubocop:disable Layout/LineLength
|
65
|
+
# TODO: verify if Prefix can handle both name and path prefix
|
66
|
+
@path_prefix = Prefix.new(prefix)
|
67
|
+
@name_prefix = Prefix.new("")
|
68
|
+
@url_helpers = UrlHelpers.new(base_url)
|
69
|
+
@resolver = resolver
|
70
|
+
@not_found = not_found
|
71
|
+
@block_context = block_context
|
72
|
+
@fixed = {}
|
73
|
+
@variable = {}
|
74
|
+
@globbed = {}
|
75
|
+
@mounted = {}
|
76
|
+
instance_eval(&blk) if blk
|
240
77
|
end
|
241
78
|
|
242
|
-
#
|
79
|
+
# Resolve the given Rack env to a registered endpoint and invokes it.
|
243
80
|
#
|
244
|
-
#
|
245
|
-
# It's used by `Hanami::Routing::RoutesInspector` to inspect both apps and
|
246
|
-
# routers.
|
81
|
+
# @param env [Hash] a Rack env
|
247
82
|
#
|
248
|
-
# @return [
|
83
|
+
# @return [Array] a finalized Rack env response
|
249
84
|
#
|
250
|
-
# @since 0.
|
251
|
-
|
252
|
-
|
253
|
-
|
85
|
+
# @since 0.1.0
|
86
|
+
def call(env)
|
87
|
+
endpoint, params = lookup(env)
|
88
|
+
|
89
|
+
unless endpoint
|
90
|
+
return not_allowed(env) ||
|
91
|
+
not_found(env)
|
92
|
+
end
|
93
|
+
|
94
|
+
endpoint.call(
|
95
|
+
_params(env, params)
|
96
|
+
).to_a
|
254
97
|
end
|
255
98
|
|
256
|
-
#
|
257
|
-
#
|
258
|
-
# @param blk [Proc] the block to define the routes
|
99
|
+
# Defines a named root route (a GET route for "/")
|
259
100
|
#
|
260
|
-
# @
|
101
|
+
# @param to [#call] the Rack endpoint
|
102
|
+
# @param blk [Proc] the anonymous proc to be used as endpoint for the route
|
261
103
|
#
|
262
|
-
# @since 0.
|
104
|
+
# @since 0.7.0
|
263
105
|
#
|
264
|
-
# @
|
265
|
-
#
|
266
|
-
#
|
267
|
-
# routes 'config/routes'
|
268
|
-
# end
|
269
|
-
# end
|
106
|
+
# @see #get
|
107
|
+
# @see #path
|
108
|
+
# @see #url
|
270
109
|
#
|
271
|
-
#
|
110
|
+
# @example Proc endpoint
|
111
|
+
# require "hanami/router"
|
272
112
|
#
|
273
|
-
#
|
274
|
-
#
|
113
|
+
# router = Hanami::Router.new do
|
114
|
+
# root to: ->(env) { [200, {}, ["Hello from Hanami!"]] }
|
275
115
|
# end
|
276
|
-
def define(&blk)
|
277
|
-
instance_eval(&blk) if block_given?
|
278
|
-
end
|
279
|
-
|
280
|
-
# Check if there are defined routes
|
281
116
|
#
|
282
|
-
# @
|
117
|
+
# @example Block endpoint
|
118
|
+
# require "hanami/router"
|
283
119
|
#
|
284
|
-
#
|
285
|
-
#
|
120
|
+
# router = Hanami::Router.new do
|
121
|
+
# root do
|
122
|
+
# "Hello from Hanami!"
|
123
|
+
# end
|
124
|
+
# end
|
286
125
|
#
|
287
|
-
# @example
|
126
|
+
# @example URL helpers
|
127
|
+
# require "hanami/router"
|
288
128
|
#
|
289
|
-
# router = Hanami::Router.new
|
290
|
-
#
|
129
|
+
# router = Hanami::Router.new(base_url: "https://hanamirb.org") do
|
130
|
+
# root do
|
131
|
+
# "Hello from Hanami!"
|
132
|
+
# end
|
133
|
+
# end
|
291
134
|
#
|
292
|
-
# router
|
293
|
-
# router.
|
294
|
-
def
|
295
|
-
|
135
|
+
# router.path(:root) # => "/"
|
136
|
+
# router.url(:root) # => "https://hanamirb.org"
|
137
|
+
def root(to: nil, &blk)
|
138
|
+
get("/", to: to, as: :root, &blk)
|
296
139
|
end
|
297
140
|
|
298
|
-
# Defines a route that accepts
|
141
|
+
# Defines a route that accepts GET requests for the given path.
|
142
|
+
# It also defines a route to accept HEAD requests.
|
299
143
|
#
|
300
144
|
# @param path [String] the relative URL to be matched
|
301
|
-
#
|
302
|
-
# @param
|
303
|
-
# @
|
304
|
-
#
|
145
|
+
# @param to [#call] the Rack endpoint
|
146
|
+
# @param as [Symbol] a unique name for the route
|
147
|
+
# @param constraints [Hash] a set of constraints for path variables
|
305
148
|
# @param blk [Proc] the anonymous proc to be used as endpoint for the route
|
306
149
|
#
|
307
|
-
# @return [Hanami::Routing::Route] this may vary according to the :route
|
308
|
-
# option passed to the constructor
|
309
|
-
#
|
310
150
|
# @since 0.1.0
|
311
151
|
#
|
312
|
-
# @
|
313
|
-
#
|
314
|
-
#
|
315
|
-
# router = Hanami::Router.new
|
316
|
-
# router.get '/hanami', to: ->(env) { [200, {}, ['Hello from Hanami!']] }
|
317
|
-
#
|
318
|
-
# @example String matching with variables
|
319
|
-
# require 'hanami/router'
|
320
|
-
#
|
321
|
-
# router = Hanami::Router.new
|
322
|
-
# router.get '/flowers/:id',
|
323
|
-
# to: ->(env) {
|
324
|
-
# [
|
325
|
-
# 200,
|
326
|
-
# {},
|
327
|
-
# ["Hello from Flower no. #{ env['router.params'][:id] }!"]
|
328
|
-
# ]
|
329
|
-
# }
|
152
|
+
# @see #initialize
|
153
|
+
# @see #path
|
154
|
+
# @see #url
|
330
155
|
#
|
331
|
-
# @example
|
332
|
-
# require
|
156
|
+
# @example Proc endpoint
|
157
|
+
# require "hanami/router"
|
333
158
|
#
|
334
|
-
#
|
335
|
-
#
|
336
|
-
# id: /\d+/,
|
337
|
-
# to: ->(env) { [200, {}, [":id must be a number!"]] }
|
338
|
-
#
|
339
|
-
# @example String matching with globbling
|
340
|
-
# require 'hanami/router'
|
341
|
-
#
|
342
|
-
# router = Hanami::Router.new
|
343
|
-
# router.get '/*',
|
344
|
-
# to: ->(env) {
|
345
|
-
# [
|
346
|
-
# 200,
|
347
|
-
# {},
|
348
|
-
# ["This is catch all: #{ env['router.params'].inspect }!"]
|
349
|
-
# ]
|
350
|
-
# }
|
351
|
-
#
|
352
|
-
# @example String matching with optional tokens
|
353
|
-
# require 'hanami/router'
|
354
|
-
#
|
355
|
-
# router = Hanami::Router.new
|
356
|
-
# router.get '/hanami(.:format)',
|
357
|
-
# to: ->(env) {
|
358
|
-
# [200, {}, ["You've requested #{ env['router.params'][:format] }!"]]
|
359
|
-
# }
|
360
|
-
#
|
361
|
-
# @example Named routes
|
362
|
-
# require 'hanami/router'
|
363
|
-
#
|
364
|
-
# router = Hanami::Router.new(scheme: 'https', host: 'hanamirb.org')
|
365
|
-
# router.get '/hanami',
|
366
|
-
# to: ->(env) { [200, {}, ['Hello from Hanami!']] },
|
367
|
-
# as: :hanami
|
368
|
-
#
|
369
|
-
# router.path(:hanami) # => "/hanami"
|
370
|
-
# router.url(:hanami) # => "https://hanamirb.org/hanami"
|
371
|
-
#
|
372
|
-
# @example Duck typed endpoints (Rack compatible objects)
|
373
|
-
# require 'hanami/router'
|
374
|
-
#
|
375
|
-
# router = Hanami::Router.new
|
376
|
-
#
|
377
|
-
# router.get '/hanami', to: ->(env) { [200, {}, ['Hello from Hanami!']] }
|
378
|
-
# router.get '/middleware', to: Middleware
|
379
|
-
# router.get '/rack-app', to: RackApp.new
|
380
|
-
# router.get '/method', to: ActionControllerSubclass.action(:new)
|
381
|
-
#
|
382
|
-
# # Everything that responds to #call is invoked as it is
|
383
|
-
#
|
384
|
-
# @example Duck typed endpoints (strings)
|
385
|
-
# require 'hanami/router'
|
386
|
-
#
|
387
|
-
# class RackApp
|
388
|
-
# def call(env)
|
389
|
-
# # ...
|
390
|
-
# end
|
159
|
+
# Hanami::Router.new do
|
160
|
+
# get "/", to: ->(*) { [200, {}, ["OK"]] }
|
391
161
|
# end
|
392
162
|
#
|
393
|
-
#
|
394
|
-
#
|
395
|
-
#
|
396
|
-
# @example Duck typed endpoints (string: controller + action)
|
397
|
-
# require 'hanami/router'
|
163
|
+
# @example Block endpoint
|
164
|
+
# require "hanami/router"
|
398
165
|
#
|
399
|
-
#
|
400
|
-
#
|
401
|
-
#
|
402
|
-
# # ...
|
403
|
-
# end
|
166
|
+
# Hanami::Router.new do
|
167
|
+
# get "/" do
|
168
|
+
# "OK"
|
404
169
|
# end
|
405
170
|
# end
|
406
171
|
#
|
407
|
-
#
|
408
|
-
#
|
409
|
-
#
|
410
|
-
# # It will map to Flowers::Index.new, which is the
|
411
|
-
# # Hanami::Controller convention.
|
412
|
-
def get(path, options = {}, &blk)
|
413
|
-
@router.get(path, options, &blk)
|
414
|
-
end
|
415
|
-
|
416
|
-
# Defines a route that accepts a POST request for the given path.
|
417
|
-
#
|
418
|
-
# @param path [String] the relative URL to be matched
|
419
|
-
#
|
420
|
-
# @param options [Hash] the options to customize the route
|
421
|
-
# @option options [String,Proc,Class,Object#call] :to the endpoint
|
172
|
+
# @example Named route
|
173
|
+
# require "hanami/router"
|
422
174
|
#
|
423
|
-
#
|
175
|
+
# router = Hanami::Router.new do
|
176
|
+
# get "/", to: ->(*) { [200, {}, ["OK"]] }, as: :welcome
|
177
|
+
# end
|
424
178
|
#
|
425
|
-
#
|
426
|
-
#
|
179
|
+
# router.path(:welcome) # => "/"
|
180
|
+
# router.url(:welcome) # => "http://localhost/"
|
427
181
|
#
|
428
|
-
# @
|
182
|
+
# @example Constraints
|
183
|
+
# require "hanami/router"
|
429
184
|
#
|
430
|
-
#
|
431
|
-
|
432
|
-
|
185
|
+
# Hanami::Router.new do
|
186
|
+
# get "/users/:id", to: ->(*) { [200, {}, ["OK"]] }, id: /\d+/
|
187
|
+
# end
|
188
|
+
def get(path, to: nil, as: nil, **constraints, &blk)
|
189
|
+
add_route("GET", path, to, as, constraints, &blk)
|
190
|
+
add_route("HEAD", path, to, as, constraints, &blk)
|
433
191
|
end
|
434
192
|
|
435
|
-
# Defines a route that accepts
|
193
|
+
# Defines a route that accepts POST requests for the given path.
|
436
194
|
#
|
437
195
|
# @param path [String] the relative URL to be matched
|
438
|
-
#
|
439
|
-
# @param
|
440
|
-
# @
|
441
|
-
#
|
196
|
+
# @param to [#call] the Rack endpoint
|
197
|
+
# @param as [Symbol] a unique name for the route
|
198
|
+
# @param constraints [Hash] a set of constraints for path variables
|
442
199
|
# @param blk [Proc] the anonymous proc to be used as endpoint for the route
|
443
200
|
#
|
444
|
-
# @return [Hanami::Routing::Route] this may vary according to the :route
|
445
|
-
# option passed to the constructor
|
446
|
-
#
|
447
|
-
# @see Hanami::Router#get
|
448
|
-
#
|
449
201
|
# @since 0.1.0
|
450
|
-
|
451
|
-
|
202
|
+
#
|
203
|
+
# @see #get
|
204
|
+
# @see #initialize
|
205
|
+
# @see #path
|
206
|
+
# @see #url
|
207
|
+
def post(path, to: nil, as: nil, **constraints, &blk)
|
208
|
+
add_route("POST", path, to, as, constraints, &blk)
|
452
209
|
end
|
453
210
|
|
454
|
-
# Defines a route that accepts
|
211
|
+
# Defines a route that accepts PATCH requests for the given path.
|
455
212
|
#
|
456
213
|
# @param path [String] the relative URL to be matched
|
457
|
-
#
|
458
|
-
# @param
|
459
|
-
# @
|
460
|
-
#
|
214
|
+
# @param to [#call] the Rack endpoint
|
215
|
+
# @param as [Symbol] a unique name for the route
|
216
|
+
# @param constraints [Hash] a set of constraints for path variables
|
461
217
|
# @param blk [Proc] the anonymous proc to be used as endpoint for the route
|
462
218
|
#
|
463
|
-
# @return [Hanami::Routing::Route] this may vary according to the :route
|
464
|
-
# option passed to the constructor
|
465
|
-
#
|
466
|
-
# @see Hanami::Router#get
|
467
|
-
#
|
468
219
|
# @since 0.1.0
|
469
|
-
|
470
|
-
|
220
|
+
#
|
221
|
+
# @see #get
|
222
|
+
# @see #initialize
|
223
|
+
# @see #path
|
224
|
+
# @see #url
|
225
|
+
def patch(path, to: nil, as: nil, **constraints, &blk)
|
226
|
+
add_route("PATCH", path, to, as, constraints, &blk)
|
471
227
|
end
|
472
228
|
|
473
|
-
# Defines a route that accepts
|
229
|
+
# Defines a route that accepts PUT requests for the given path.
|
474
230
|
#
|
475
231
|
# @param path [String] the relative URL to be matched
|
476
|
-
#
|
477
|
-
# @param
|
478
|
-
# @
|
479
|
-
#
|
232
|
+
# @param to [#call] the Rack endpoint
|
233
|
+
# @param as [Symbol] a unique name for the route
|
234
|
+
# @param constraints [Hash] a set of constraints for path variables
|
480
235
|
# @param blk [Proc] the anonymous proc to be used as endpoint for the route
|
481
236
|
#
|
482
|
-
# @return [Hanami::Routing::Route] this may vary according to the :route
|
483
|
-
# option passed to the constructor
|
484
|
-
#
|
485
|
-
# @see Hanami::Router#get
|
486
|
-
#
|
487
237
|
# @since 0.1.0
|
488
|
-
|
489
|
-
|
238
|
+
#
|
239
|
+
# @see #get
|
240
|
+
# @see #initialize
|
241
|
+
# @see #path
|
242
|
+
# @see #url
|
243
|
+
def put(path, to: nil, as: nil, **constraints, &blk)
|
244
|
+
add_route("PUT", path, to, as, constraints, &blk)
|
490
245
|
end
|
491
246
|
|
492
|
-
# Defines a route that accepts
|
247
|
+
# Defines a route that accepts DELETE requests for the given path.
|
493
248
|
#
|
494
249
|
# @param path [String] the relative URL to be matched
|
495
|
-
#
|
496
|
-
# @param
|
497
|
-
# @
|
498
|
-
#
|
250
|
+
# @param to [#call] the Rack endpoint
|
251
|
+
# @param as [Symbol] a unique name for the route
|
252
|
+
# @param constraints [Hash] a set of constraints for path variables
|
499
253
|
# @param blk [Proc] the anonymous proc to be used as endpoint for the route
|
500
254
|
#
|
501
|
-
# @return [Hanami::Routing::Route] this may vary according to the :route
|
502
|
-
# option passed to the constructor
|
503
|
-
#
|
504
|
-
# @see Hanami::Router#get
|
505
|
-
#
|
506
255
|
# @since 0.1.0
|
507
|
-
|
508
|
-
|
256
|
+
#
|
257
|
+
# @see #get
|
258
|
+
# @see #initialize
|
259
|
+
# @see #path
|
260
|
+
# @see #url
|
261
|
+
def delete(path, to: nil, as: nil, **constraints, &blk)
|
262
|
+
add_route("DELETE", path, to, as, constraints, &blk)
|
509
263
|
end
|
510
264
|
|
511
|
-
# Defines a route that accepts
|
265
|
+
# Defines a route that accepts TRACE requests for the given path.
|
512
266
|
#
|
513
267
|
# @param path [String] the relative URL to be matched
|
514
|
-
#
|
515
|
-
# @param
|
516
|
-
# @
|
517
|
-
#
|
268
|
+
# @param to [#call] the Rack endpoint
|
269
|
+
# @param as [Symbol] a unique name for the route
|
270
|
+
# @param constraints [Hash] a set of constraints for path variables
|
518
271
|
# @param blk [Proc] the anonymous proc to be used as endpoint for the route
|
519
272
|
#
|
520
|
-
# @
|
521
|
-
# option passed to the constructor
|
522
|
-
#
|
523
|
-
# @see Hanami::Router#get
|
273
|
+
# @since 0.1.0
|
524
274
|
#
|
525
|
-
# @
|
526
|
-
|
527
|
-
|
275
|
+
# @see #get
|
276
|
+
# @see #initialize
|
277
|
+
# @see #path
|
278
|
+
# @see #url
|
279
|
+
def trace(path, to: nil, as: nil, **constraints, &blk)
|
280
|
+
add_route("TRACE", path, to, as, constraints, &blk)
|
528
281
|
end
|
529
282
|
|
530
|
-
# Defines a route that accepts
|
283
|
+
# Defines a route that accepts OPTIONS requests for the given path.
|
531
284
|
#
|
532
285
|
# @param path [String] the relative URL to be matched
|
533
|
-
#
|
534
|
-
# @param
|
535
|
-
# @
|
536
|
-
#
|
286
|
+
# @param to [#call] the Rack endpoint
|
287
|
+
# @param as [Symbol] a unique name for the route
|
288
|
+
# @param constraints [Hash] a set of constraints for path variables
|
537
289
|
# @param blk [Proc] the anonymous proc to be used as endpoint for the route
|
538
290
|
#
|
539
|
-
# @
|
540
|
-
# option passed to the constructor
|
541
|
-
#
|
542
|
-
# @see Hanami::Router#get
|
291
|
+
# @since 0.1.0
|
543
292
|
#
|
544
|
-
# @
|
545
|
-
|
546
|
-
|
293
|
+
# @see #get
|
294
|
+
# @see #initialize
|
295
|
+
# @see #path
|
296
|
+
# @see #url
|
297
|
+
def options(path, to: nil, as: nil, **constraints, &blk)
|
298
|
+
add_route("OPTIONS", path, to, as, constraints, &blk)
|
547
299
|
end
|
548
300
|
|
549
|
-
# Defines a
|
550
|
-
#
|
551
|
-
# @param options [Hash] the options to customize the route
|
552
|
-
# @option options [String,Proc,Class,Object#call] :to the endpoint
|
301
|
+
# Defines a route that accepts LINK requests for the given path.
|
553
302
|
#
|
303
|
+
# @param path [String] the relative URL to be matched
|
304
|
+
# @param to [#call] the Rack endpoint
|
305
|
+
# @param as [Symbol] a unique name for the route
|
306
|
+
# @param constraints [Hash] a set of constraints for path variables
|
554
307
|
# @param blk [Proc] the anonymous proc to be used as endpoint for the route
|
555
308
|
#
|
556
|
-
# @
|
557
|
-
# option passed to the constructor
|
558
|
-
#
|
559
|
-
# @since 0.7.0
|
560
|
-
#
|
561
|
-
# @example Fixed matching string
|
562
|
-
# require 'hanami/router'
|
563
|
-
#
|
564
|
-
# router = Hanami::Router.new
|
565
|
-
# router.root to: ->(env) { [200, {}, ['Hello from Hanami!']] }
|
566
|
-
#
|
567
|
-
# @example Included names as `root` (for path and url helpers)
|
568
|
-
# require 'hanami/router'
|
569
|
-
#
|
570
|
-
# router = Hanami::Router.new(scheme: 'https', host: 'hanamirb.org')
|
571
|
-
# router.root to: ->(env) { [200, {}, ['Hello from Hanami!']] }
|
309
|
+
# @since 0.1.0
|
572
310
|
#
|
573
|
-
#
|
574
|
-
#
|
575
|
-
|
576
|
-
|
311
|
+
# @see #get
|
312
|
+
# @see #initialize
|
313
|
+
# @see #path
|
314
|
+
# @see #url
|
315
|
+
def link(path, to: nil, as: nil, **constraints, &blk)
|
316
|
+
add_route("LINK", path, to, as, constraints, &blk)
|
577
317
|
end
|
578
318
|
|
579
|
-
# Defines a route that accepts
|
319
|
+
# Defines a route that accepts UNLINK requests for the given path.
|
580
320
|
#
|
581
321
|
# @param path [String] the relative URL to be matched
|
582
|
-
#
|
583
|
-
# @param
|
584
|
-
# @
|
585
|
-
#
|
322
|
+
# @param to [#call] the Rack endpoint
|
323
|
+
# @param as [Symbol] a unique name for the route
|
324
|
+
# @param constraints [Hash] a set of constraints for path variables
|
586
325
|
# @param blk [Proc] the anonymous proc to be used as endpoint for the route
|
587
326
|
#
|
588
|
-
# @return [Hanami::Routing::Route] this may vary according to the :route
|
589
|
-
# option passed to the constructor
|
590
|
-
#
|
591
|
-
# @see Hanami::Router#get
|
592
|
-
#
|
593
|
-
# @since 0.1.0
|
594
|
-
def options(path, options = {}, &blk)
|
595
|
-
@router.options(path, options, &blk)
|
596
|
-
end
|
597
|
-
|
598
|
-
# Defines an HTTP redirect
|
599
|
-
#
|
600
|
-
# @param path [String] the path that needs to be redirected
|
601
|
-
# @param options [Hash] the options to customize the redirect behavior
|
602
|
-
# @option options [Fixnum] the HTTP status to return (defaults to `301`)
|
603
|
-
#
|
604
|
-
# @return [Hanami::Routing::Route] the generated route.
|
605
|
-
# This may vary according to the `:route` option passed to the initializer
|
606
|
-
#
|
607
327
|
# @since 0.1.0
|
608
328
|
#
|
609
|
-
# @see
|
610
|
-
#
|
611
|
-
# @
|
612
|
-
#
|
613
|
-
|
614
|
-
|
615
|
-
# redirect '/legacy', to: '/new_endpoint'
|
616
|
-
# redirect '/legacy2', to: '/new_endpoint2', code: 302
|
617
|
-
# end
|
618
|
-
#
|
619
|
-
# @example
|
620
|
-
# require 'hanami/router'
|
621
|
-
#
|
622
|
-
# router = Hanami::Router.new
|
623
|
-
# router.redirect '/legacy', to: '/new_endpoint'
|
624
|
-
def redirect(path, options = {}, &endpoint)
|
625
|
-
destination_path = @router.find(options)
|
626
|
-
get(path).redirect(destination_path, options[:code] || 301).tap do |route|
|
627
|
-
route.dest = Hanami::Routing::RedirectEndpoint.new(destination_path, route.dest)
|
628
|
-
end
|
329
|
+
# @see #get
|
330
|
+
# @see #initialize
|
331
|
+
# @see #path
|
332
|
+
# @see #url
|
333
|
+
def unlink(path, to: nil, as: nil, **constraints, &blk)
|
334
|
+
add_route("UNLINK", path, to, as, constraints, &blk)
|
629
335
|
end
|
630
336
|
|
631
|
-
# Defines a
|
632
|
-
# with the given relative path.
|
633
|
-
#
|
634
|
-
# Namespaces blocks can be nested multiple times.
|
337
|
+
# Defines a route that redirects the incoming request to another path.
|
635
338
|
#
|
636
|
-
# @param
|
637
|
-
#
|
638
|
-
# @param
|
639
|
-
#
|
640
|
-
# @return [Hanami::Routing::Namespace] the generated namespace.
|
339
|
+
# @param path [String] the relative URL to be matched
|
340
|
+
# @param to [#call] the Rack endpoint
|
341
|
+
# @param as [Symbol] a unique name for the route
|
342
|
+
# @param code [Integer] a HTTP status code to use for the redirect
|
641
343
|
#
|
642
344
|
# @since 0.1.0
|
643
345
|
#
|
644
|
-
# @see
|
645
|
-
#
|
646
|
-
|
647
|
-
|
648
|
-
#
|
649
|
-
# Hanami::Router.new do
|
650
|
-
# namespace 'trees' do
|
651
|
-
# get '/sequoia', to: endpoint # => '/trees/sequoia'
|
652
|
-
# end
|
653
|
-
# end
|
654
|
-
#
|
655
|
-
# @example Nested namespaces
|
656
|
-
# require 'hanami/router'
|
657
|
-
#
|
658
|
-
# Hanami::Router.new do
|
659
|
-
# namespace 'animals' do
|
660
|
-
# namespace 'mammals' do
|
661
|
-
# get '/cats', to: endpoint # => '/animals/mammals/cats'
|
662
|
-
# end
|
663
|
-
# end
|
664
|
-
# end
|
665
|
-
#
|
666
|
-
# @example
|
667
|
-
# require 'hanami/router'
|
668
|
-
#
|
669
|
-
# router = Hanami::Router.new
|
670
|
-
# router.namespace 'trees' do
|
671
|
-
# get '/sequoia', to: endpoint # => '/trees/sequoia'
|
672
|
-
# end
|
673
|
-
def namespace(namespace, &blk)
|
674
|
-
Routing::Namespace.new(self, namespace, &blk)
|
346
|
+
# @see #get
|
347
|
+
# @see #initialize
|
348
|
+
def redirect(path, to: nil, as: nil, code: DEFAULT_REDIRECT_CODE)
|
349
|
+
get(path, to: _redirect(to, code), as: as)
|
675
350
|
end
|
676
351
|
|
677
|
-
# Defines a
|
678
|
-
#
|
679
|
-
#
|
680
|
-
# @param name [String] the name of the resource
|
681
|
-
# @param options [Hash] a set of options to customize the routes
|
682
|
-
# @option options [Array<Symbol>] :only a subset of the default routes
|
683
|
-
# that we want to generate
|
684
|
-
# @option options [Array<Symbol>] :except prevent the given routes to be
|
685
|
-
# generated
|
686
|
-
# @param blk [Proc] a block of code to generate additional routes
|
687
|
-
#
|
688
|
-
# @return [Hanami::Routing::Resource]
|
689
|
-
#
|
690
|
-
# @since 0.1.0
|
691
|
-
#
|
692
|
-
# @see Hanami::Routing::Resource
|
693
|
-
# @see Hanami::Routing::Resource::Action
|
694
|
-
# @see Hanami::Routing::Resource::Options
|
695
|
-
#
|
696
|
-
# @example Default usage
|
697
|
-
# require 'hanami/router'
|
698
|
-
#
|
699
|
-
# Hanami::Router.new do
|
700
|
-
# resource 'identity'
|
701
|
-
# end
|
702
|
-
#
|
703
|
-
# # It generates:
|
704
|
-
# #
|
705
|
-
# # +--------+----------------+-------------------+----------+----------------+
|
706
|
-
# # | Verb | Path | Action | Name | Named Route |
|
707
|
-
# # +--------+----------------+-------------------+----------+----------------+
|
708
|
-
# # | GET | /identity | Identity::Show | :show | :identity |
|
709
|
-
# # | GET | /identity/new | Identity::New | :new | :new_identity |
|
710
|
-
# # | POST | /identity | Identity::Create | :create | :identity |
|
711
|
-
# # | GET | /identity/edit | Identity::Edit | :edit | :edit_identity |
|
712
|
-
# # | PATCH | /identity | Identity::Update | :update | :identity |
|
713
|
-
# # | DELETE | /identity | Identity::Destroy | :destroy | :identity |
|
714
|
-
# # +--------+----------------+-------------------+----------+----------------+
|
715
|
-
#
|
716
|
-
#
|
717
|
-
#
|
718
|
-
# @example Limit the generated routes with :only
|
719
|
-
# require 'hanami/router'
|
720
|
-
#
|
721
|
-
# Hanami::Router.new do
|
722
|
-
# resource 'identity', only: [:show, :new, :create]
|
723
|
-
# end
|
724
|
-
#
|
725
|
-
# # It generates:
|
726
|
-
# #
|
727
|
-
# # +--------+----------------+------------------+----------+----------------+
|
728
|
-
# # | Verb | Path | Action | Name | Named Route |
|
729
|
-
# # +--------+----------------+------------------+----------+----------------+
|
730
|
-
# # | GET | /identity | Identity::Show | :show | :identity |
|
731
|
-
# # | GET | /identity/new | Identity::New | :new | :new_identity |
|
732
|
-
# # | POST | /identity | Identity::Create | :create | :identity |
|
733
|
-
# # +--------+----------------+------------------+----------+----------------+
|
734
|
-
#
|
735
|
-
#
|
736
|
-
#
|
737
|
-
# @example Limit the generated routes with :except
|
738
|
-
# require 'hanami/router'
|
739
|
-
#
|
740
|
-
# Hanami::Router.new do
|
741
|
-
# resource 'identity', except: [:edit, :update, :destroy]
|
742
|
-
# end
|
352
|
+
# Defines a routing scope. Routes defined in the context of a scope,
|
353
|
+
# inherit the given path as path prefix and as a named routes prefix.
|
743
354
|
#
|
744
|
-
#
|
745
|
-
#
|
746
|
-
# # +--------+----------------+------------------+----------+----------------+
|
747
|
-
# # | Verb | Path | Action | Name | Named Route |
|
748
|
-
# # +--------+----------------+------------------+----------+----------------+
|
749
|
-
# # | GET | /identity | Identity::Show | :show | :identity |
|
750
|
-
# # | GET | /identity/new | Identity::New | :new | :new_identity |
|
751
|
-
# # | POST | /identity | Identity::Create | :create | :identity |
|
752
|
-
# # +--------+----------------+------------------+----------+----------------+
|
355
|
+
# @param path [String] the scope path to be used as a path prefix
|
356
|
+
# @param blk [Proc] the routes definitions withing the scope
|
753
357
|
#
|
358
|
+
# @since 2.0.0
|
754
359
|
#
|
360
|
+
# @see #path
|
755
361
|
#
|
756
|
-
# @example
|
757
|
-
# require
|
758
|
-
#
|
759
|
-
# Hanami::Router.new do
|
760
|
-
# resource 'identity', only: [] do
|
761
|
-
# member do
|
762
|
-
# patch 'activate'
|
763
|
-
# end
|
764
|
-
# end
|
765
|
-
# end
|
766
|
-
#
|
767
|
-
# # It generates:
|
768
|
-
# #
|
769
|
-
# # +--------+--------------------+--------------------+------+--------------------+
|
770
|
-
# # | Verb | Path | Action | Name | Named Route |
|
771
|
-
# # +--------+--------------------+--------------------+------+--------------------+
|
772
|
-
# # | PATCH | /identity/activate | Identity::Activate | | :activate_identity |
|
773
|
-
# # +--------+--------------------+--------------------+------+--------------------+
|
774
|
-
#
|
775
|
-
#
|
776
|
-
#
|
777
|
-
# @example Additional collection routes
|
778
|
-
# require 'hanami/router'
|
362
|
+
# @example
|
363
|
+
# require "hanami/router"
|
779
364
|
#
|
780
|
-
# Hanami::Router.new do
|
781
|
-
#
|
782
|
-
#
|
783
|
-
# get 'keys'
|
784
|
-
# end
|
365
|
+
# router = Hanami::Router.new do
|
366
|
+
# scope "v1" do
|
367
|
+
# get "/users", to: ->(*) { ... }, as: :users
|
785
368
|
# end
|
786
369
|
# end
|
787
370
|
#
|
788
|
-
# #
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
# # +------+----------------+----------------+------+----------------+
|
793
|
-
# # | GET | /identity/keys | Identity::Keys | | :keys_identity |
|
794
|
-
# # +------+----------------+----------------+------+----------------+
|
795
|
-
def resource(name, options = {}, &blk)
|
796
|
-
Routing::Resource.new(self, name, options.merge(separator: @router.action_separator), &blk)
|
797
|
-
end
|
371
|
+
# router.path(:v1_users) # => "/v1/users"
|
372
|
+
def scope(path, &blk)
|
373
|
+
path_prefix = @path_prefix
|
374
|
+
name_prefix = @name_prefix
|
798
375
|
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
# generated
|
808
|
-
# @param blk [Proc] a block of code to generate additional routes
|
809
|
-
#
|
810
|
-
# @return [Hanami::Routing::Resources]
|
811
|
-
#
|
812
|
-
# @since 0.1.0
|
813
|
-
#
|
814
|
-
# @see Hanami::Routing::Resources
|
815
|
-
# @see Hanami::Routing::Resources::Action
|
816
|
-
# @see Hanami::Routing::Resource::Options
|
817
|
-
#
|
818
|
-
# @example Default usage
|
819
|
-
# require 'hanami/router'
|
820
|
-
#
|
821
|
-
# Hanami::Router.new do
|
822
|
-
# resources 'articles'
|
823
|
-
# end
|
824
|
-
#
|
825
|
-
# # It generates:
|
826
|
-
# #
|
827
|
-
# # +--------+--------------------+-------------------+----------+----------------+
|
828
|
-
# # | Verb | Path | Action | Name | Named Route |
|
829
|
-
# # +--------+--------------------+-------------------+----------+----------------+
|
830
|
-
# # | GET | /articles | Articles::Index | :index | :articles |
|
831
|
-
# # | GET | /articles/:id | Articles::Show | :show | :articles |
|
832
|
-
# # | GET | /articles/new | Articles::New | :new | :new_articles |
|
833
|
-
# # | POST | /articles | Articles::Create | :create | :articles |
|
834
|
-
# # | GET | /articles/:id/edit | Articles::Edit | :edit | :edit_articles |
|
835
|
-
# # | PATCH | /articles/:id | Articles::Update | :update | :articles |
|
836
|
-
# # | DELETE | /articles/:id | Articles::Destroy | :destroy | :articles |
|
837
|
-
# # +--------+--------------------+-------------------+----------+----------------+
|
838
|
-
#
|
839
|
-
#
|
840
|
-
#
|
841
|
-
# @example Limit the generated routes with :only
|
842
|
-
# require 'hanami/router'
|
843
|
-
#
|
844
|
-
# Hanami::Router.new do
|
845
|
-
# resources 'articles', only: [:index]
|
846
|
-
# end
|
847
|
-
#
|
848
|
-
# # It generates:
|
849
|
-
# #
|
850
|
-
# # +------+-----------+-----------------+--------+-------------+
|
851
|
-
# # | Verb | Path | Action | Name | Named Route |
|
852
|
-
# # +------+-----------+-----------------+--------+-------------+
|
853
|
-
# # | GET | /articles | Articles::Index | :index | :articles |
|
854
|
-
# # +------+-----------+-----------------+--------+-------------+
|
855
|
-
#
|
856
|
-
#
|
857
|
-
#
|
858
|
-
# @example Limit the generated routes with :except
|
859
|
-
# require 'hanami/router'
|
860
|
-
#
|
861
|
-
# Hanami::Router.new do
|
862
|
-
# resources 'articles', except: [:edit, :update]
|
863
|
-
# end
|
864
|
-
#
|
865
|
-
# # It generates:
|
866
|
-
# #
|
867
|
-
# # +--------+--------------------+-------------------+----------+----------------+
|
868
|
-
# # | Verb | Path | Action | Name | Named Route |
|
869
|
-
# # +--------+--------------------+-------------------+----------+----------------+
|
870
|
-
# # | GET | /articles | Articles::Index | :index | :articles |
|
871
|
-
# # | GET | /articles/:id | Articles::Show | :show | :articles |
|
872
|
-
# # | GET | /articles/new | Articles::New | :new | :new_articles |
|
873
|
-
# # | POST | /articles | Articles::Create | :create | :articles |
|
874
|
-
# # | DELETE | /articles/:id | Articles::Destroy | :destroy | :articles |
|
875
|
-
# # +--------+--------------------+-------------------+----------+----------------+
|
876
|
-
#
|
877
|
-
#
|
878
|
-
#
|
879
|
-
# @example Additional single routes
|
880
|
-
# require 'hanami/router'
|
881
|
-
#
|
882
|
-
# Hanami::Router.new do
|
883
|
-
# resources 'articles', only: [] do
|
884
|
-
# member do
|
885
|
-
# patch 'publish'
|
886
|
-
# end
|
887
|
-
# end
|
888
|
-
# end
|
889
|
-
#
|
890
|
-
# # It generates:
|
891
|
-
# #
|
892
|
-
# # +--------+-----------------------+-------------------+------+-------------------+
|
893
|
-
# # | Verb | Path | Action | Name | Named Route |
|
894
|
-
# # +--------+-----------------------+-------------------+------+-------------------+
|
895
|
-
# # | PATCH | /articles/:id/publish | Articles::Publish | | :publish_articles |
|
896
|
-
# # +--------+-----------------------+-------------------+------+-------------------+
|
897
|
-
#
|
898
|
-
#
|
899
|
-
#
|
900
|
-
# @example Additional collection routes
|
901
|
-
# require 'hanami/router'
|
902
|
-
#
|
903
|
-
# Hanami::Router.new do
|
904
|
-
# resources 'articles', only: [] do
|
905
|
-
# collection do
|
906
|
-
# get 'search'
|
907
|
-
# end
|
908
|
-
# end
|
909
|
-
# end
|
910
|
-
#
|
911
|
-
# # It generates:
|
912
|
-
# #
|
913
|
-
# # +------+------------------+------------------+------+------------------+
|
914
|
-
# # | Verb | Path | Action | Name | Named Route |
|
915
|
-
# # +------+------------------+------------------+------+------------------+
|
916
|
-
# # | GET | /articles/search | Articles::Search | | :search_articles |
|
917
|
-
# # +------+------------------+------------------+------+------------------+
|
918
|
-
def resources(name, options = {}, &blk)
|
919
|
-
Routing::Resources.new(self, name, options.merge(separator: @router.action_separator), &blk)
|
376
|
+
begin
|
377
|
+
@path_prefix = @path_prefix.join(path.to_s)
|
378
|
+
@name_prefix = @name_prefix.join(path.to_s)
|
379
|
+
instance_eval(&blk)
|
380
|
+
ensure
|
381
|
+
@path_prefix = path_prefix
|
382
|
+
@name_prefix = name_prefix
|
383
|
+
end
|
920
384
|
end
|
921
385
|
|
922
386
|
# Mount a Rack application at the specified path.
|
923
387
|
# All the requests starting with the specified path, will be forwarded to
|
924
388
|
# the given application.
|
925
389
|
#
|
926
|
-
# All the other methods (eg
|
390
|
+
# All the other methods (eg `#get`) support callable objects, but they
|
927
391
|
# restrict the range of the acceptable HTTP verb. Mounting an application
|
928
392
|
# with #mount doesn't apply this kind of restriction at the router level,
|
929
393
|
# but let the application to decide.
|
930
394
|
#
|
931
395
|
# @param app [#call] a class or an object that responds to #call
|
932
|
-
# @param
|
933
|
-
# @
|
396
|
+
# @param at [String] the relative path where to mount the app
|
397
|
+
# @param constraints [Hash] a set of constraints for path variables
|
934
398
|
#
|
935
399
|
# @since 0.1.1
|
936
400
|
#
|
937
|
-
# @example
|
938
|
-
# require
|
401
|
+
# @example
|
402
|
+
# require "hanami/router"
|
939
403
|
#
|
940
404
|
# Hanami::Router.new do
|
941
|
-
# mount
|
405
|
+
# mount MyRackApp.new, at: "/foo"
|
942
406
|
# end
|
407
|
+
def mount(app, at:, **constraints)
|
408
|
+
path = prefixed_path(at)
|
409
|
+
prefix = Segment.fabricate(path, **constraints)
|
410
|
+
@mounted[prefix] = @resolver.call(path, app)
|
411
|
+
end
|
412
|
+
|
413
|
+
# Generate an relative URL for a specified named route.
|
414
|
+
# The additional arguments will be used to compose the relative URL - in
|
415
|
+
# case it has tokens to match - and for compose the query string.
|
943
416
|
#
|
944
|
-
#
|
945
|
-
# #
|
946
|
-
# # GET /api # => 200
|
947
|
-
# # GET /api/articles # => 200
|
948
|
-
# # POST /api/articles # => 200
|
949
|
-
# # GET /api/unknown # => 404
|
950
|
-
#
|
951
|
-
# @example Difference between #get and #mount
|
952
|
-
# require 'hanami/router'
|
417
|
+
# @param name [Symbol] the route name
|
953
418
|
#
|
954
|
-
#
|
955
|
-
# get '/rack1', to: RackOne.new
|
956
|
-
# mount RackTwo.new, at: '/rack2'
|
957
|
-
# end
|
419
|
+
# @return [String]
|
958
420
|
#
|
959
|
-
#
|
960
|
-
#
|
961
|
-
# # # /rack1 will only accept GET
|
962
|
-
# # GET /rack1 # => 200 (RackOne.new)
|
963
|
-
# # POST /rack1 # => 405
|
964
|
-
# #
|
965
|
-
# # # /rack2 accepts all the verbs and delegate the decision to RackTwo
|
966
|
-
# # GET /rack2 # => 200 (RackTwo.new)
|
967
|
-
# # POST /rack2 # => 200 (RackTwo.new)
|
968
|
-
#
|
969
|
-
# @example Types of mountable applications
|
970
|
-
# require 'hanami/router'
|
971
|
-
#
|
972
|
-
# class RackOne
|
973
|
-
# def self.call(env)
|
974
|
-
# end
|
975
|
-
# end
|
421
|
+
# @raise [Hanami::Routing::InvalidRouteException] when the router fails to
|
422
|
+
# recognize a route, because of the given arguments.
|
976
423
|
#
|
977
|
-
#
|
978
|
-
# def call(env)
|
979
|
-
# end
|
980
|
-
# end
|
424
|
+
# @since 0.1.0
|
981
425
|
#
|
982
|
-
#
|
983
|
-
# def call(env)
|
984
|
-
# end
|
985
|
-
# end
|
426
|
+
# @see #url
|
986
427
|
#
|
987
|
-
#
|
988
|
-
#
|
989
|
-
# def call(env)
|
990
|
-
# end
|
991
|
-
# end
|
992
|
-
# end
|
428
|
+
# @example
|
429
|
+
# require "hanami/router"
|
993
430
|
#
|
994
|
-
# Hanami::Router.new do
|
995
|
-
#
|
996
|
-
#
|
997
|
-
# mount RackThree.new, at: '/rack3'
|
998
|
-
# mount ->(env) {[200, {}, ['Rack Four']]}, at: '/rack4'
|
999
|
-
# mount 'dashboard#index', at: '/dashboard'
|
431
|
+
# router = Hanami::Router.new(base_url: "https://hanamirb.org") do
|
432
|
+
# get "/login", to: ->(*) { ... }, as: :login
|
433
|
+
# get "/:name", to: ->(*) { ... }, as: :framework
|
1000
434
|
# end
|
1001
435
|
#
|
1002
|
-
#
|
1003
|
-
#
|
1004
|
-
#
|
1005
|
-
|
1006
|
-
|
1007
|
-
def mount(app, options)
|
1008
|
-
@router.mount(app, options)
|
436
|
+
# router.path(:login) # => "/login"
|
437
|
+
# router.path(:login, return_to: "/dashboard") # => "/login?return_to=%2Fdashboard"
|
438
|
+
# router.path(:framework, name: "router") # => "/router"
|
439
|
+
def path(name, variables = {})
|
440
|
+
@url_helpers.path(name, variables)
|
1009
441
|
end
|
1010
442
|
|
1011
|
-
#
|
443
|
+
# Generate an absolute URL for a specified named route.
|
444
|
+
# The additional arguments will be used to compose the relative URL - in
|
445
|
+
# case it has tokens to match - and for compose the query string.
|
1012
446
|
#
|
1013
|
-
# @param
|
447
|
+
# @param name [Symbol] the route name
|
1014
448
|
#
|
1015
|
-
# @return [
|
449
|
+
# @return [String]
|
450
|
+
#
|
451
|
+
# @raise [Hanami::Routing::InvalidRouteException] when the router fails to
|
452
|
+
# recognize a route, because of the given arguments.
|
1016
453
|
#
|
1017
454
|
# @since 0.1.0
|
1018
|
-
|
1019
|
-
|
455
|
+
#
|
456
|
+
# @see #path
|
457
|
+
#
|
458
|
+
# @example
|
459
|
+
# require "hanami/router"
|
460
|
+
#
|
461
|
+
# router = Hanami::Router.new(base_url: "https://hanamirb.org") do
|
462
|
+
# get "/login", to: ->(*) { ... }, as: :login
|
463
|
+
# get "/:name", to: ->(*) { ... }, as: :framework
|
464
|
+
# end
|
465
|
+
#
|
466
|
+
# router.url(:login) # => "https://hanamirb.org/login"
|
467
|
+
# router.url(:login, return_to: "/dashboard") # => "https://hanamirb.org/login?return_to=%2Fdashboard"
|
468
|
+
# router.url(:framework, name: "router") # => "https://hanamirb.org/router"
|
469
|
+
def url(name, variables = {})
|
470
|
+
@url_helpers.url(name, variables)
|
1020
471
|
end
|
1021
472
|
|
1022
473
|
# Recognize the given env, path, or name and return a route for testing
|
@@ -1037,34 +488,34 @@ module Hanami
|
|
1037
488
|
# @see Hanami::Routing::RecognizedRoute
|
1038
489
|
#
|
1039
490
|
# @example Successful Path Recognition
|
1040
|
-
# require
|
491
|
+
# require "hanami/router"
|
1041
492
|
#
|
1042
493
|
# router = Hanami::Router.new do
|
1043
|
-
# get
|
494
|
+
# get "/books/:id", to: ->(*) { ... }, as: :book
|
1044
495
|
# end
|
1045
496
|
#
|
1046
|
-
# route = router.recognize(
|
497
|
+
# route = router.recognize("/books/23")
|
1047
498
|
# route.verb # => "GET" (default)
|
1048
499
|
# route.routable? # => true
|
1049
500
|
# route.params # => {:id=>"23"}
|
1050
501
|
#
|
1051
502
|
# @example Successful Rack Env Recognition
|
1052
|
-
# require
|
503
|
+
# require "hanami/router"
|
1053
504
|
#
|
1054
505
|
# router = Hanami::Router.new do
|
1055
|
-
# get
|
506
|
+
# get "/books/:id", to: ->(*) { ... }, as: :book
|
1056
507
|
# end
|
1057
508
|
#
|
1058
|
-
# route = router.recognize(Rack::MockRequest.env_for(
|
509
|
+
# route = router.recognize(Rack::MockRequest.env_for("/books/23"))
|
1059
510
|
# route.verb # => "GET" (default)
|
1060
511
|
# route.routable? # => true
|
1061
512
|
# route.params # => {:id=>"23"}
|
1062
513
|
#
|
1063
514
|
# @example Successful Named Route Recognition
|
1064
|
-
# require
|
515
|
+
# require "hanami/router"
|
1065
516
|
#
|
1066
517
|
# router = Hanami::Router.new do
|
1067
|
-
# get
|
518
|
+
# get "/books/:id", to: ->(*) { ... }, as: :book
|
1068
519
|
# end
|
1069
520
|
#
|
1070
521
|
# route = router.recognize(:book, id: 23)
|
@@ -1073,43 +524,43 @@ module Hanami
|
|
1073
524
|
# route.params # => {:id=>"23"}
|
1074
525
|
#
|
1075
526
|
# @example Failing Recognition For Unknown Path
|
1076
|
-
# require
|
527
|
+
# require "hanami/router"
|
1077
528
|
#
|
1078
529
|
# router = Hanami::Router.new do
|
1079
|
-
# get
|
530
|
+
# get "/books/:id", to: ->(*) { ... }, as: :book
|
1080
531
|
# end
|
1081
532
|
#
|
1082
|
-
# route = router.recognize(
|
533
|
+
# route = router.recognize("/books")
|
1083
534
|
# route.verb # => "GET" (default)
|
1084
535
|
# route.routable? # => false
|
1085
536
|
#
|
1086
537
|
# @example Failing Recognition For Path With Wrong HTTP Verb
|
1087
|
-
# require
|
538
|
+
# require "hanami/router"
|
1088
539
|
#
|
1089
540
|
# router = Hanami::Router.new do
|
1090
|
-
# get
|
541
|
+
# get "/books/:id", to: ->(*) { ... }, as: :book
|
1091
542
|
# end
|
1092
543
|
#
|
1093
|
-
# route = router.recognize(
|
544
|
+
# route = router.recognize("/books/23", method: :post)
|
1094
545
|
# route.verb # => "POST"
|
1095
546
|
# route.routable? # => false
|
1096
547
|
#
|
1097
548
|
# @example Failing Recognition For Rack Env With Wrong HTTP Verb
|
1098
|
-
# require
|
549
|
+
# require "hanami/router"
|
1099
550
|
#
|
1100
551
|
# router = Hanami::Router.new do
|
1101
|
-
# get
|
552
|
+
# get "/books/:id", to: ->(*) { ... }, as: :book
|
1102
553
|
# end
|
1103
554
|
#
|
1104
|
-
# route = router.recognize(Rack::MockRequest.env_for(
|
555
|
+
# route = router.recognize(Rack::MockRequest.env_for("/books/23", method: :post))
|
1105
556
|
# route.verb # => "POST"
|
1106
557
|
# route.routable? # => false
|
1107
558
|
#
|
1108
559
|
# @example Failing Recognition Named Route With Wrong Params
|
1109
|
-
# require
|
560
|
+
# require "hanami/router"
|
1110
561
|
#
|
1111
562
|
# router = Hanami::Router.new do
|
1112
|
-
# get
|
563
|
+
# get "/books/:id", to: ->(*) { ... }, as: :book
|
1113
564
|
# end
|
1114
565
|
#
|
1115
566
|
# route = router.recognize(:book)
|
@@ -1117,105 +568,79 @@ module Hanami
|
|
1117
568
|
# route.routable? # => false
|
1118
569
|
#
|
1119
570
|
# @example Failing Recognition Named Route With Wrong HTTP Verb
|
1120
|
-
# require
|
571
|
+
# require "hanami/router"
|
1121
572
|
#
|
1122
573
|
# router = Hanami::Router.new do
|
1123
|
-
# get
|
574
|
+
# get "/books/:id", to: ->(*) { ... }, as: :book
|
1124
575
|
# end
|
1125
576
|
#
|
1126
577
|
# route = router.recognize(:book, {method: :post}, {id: 1})
|
1127
578
|
# route.verb # => "POST"
|
1128
579
|
# route.routable? # => false
|
1129
580
|
# route.params # => {:id=>"1"}
|
1130
|
-
def recognize(env,
|
1131
|
-
require
|
581
|
+
def recognize(env, params = {}, options = {})
|
582
|
+
require "hanami/router/recognized_route"
|
583
|
+
env = env_for(env, params, options)
|
584
|
+
endpoint, params = lookup(env)
|
585
|
+
|
586
|
+
RecognizedRoute.new(
|
587
|
+
endpoint, _params(env, params)
|
588
|
+
)
|
589
|
+
end
|
1132
590
|
|
1133
|
-
|
1134
|
-
|
591
|
+
# @since 2.0.0
|
592
|
+
# @api private
|
593
|
+
def fixed(env)
|
594
|
+
@fixed.dig(env["REQUEST_METHOD"], env["PATH_INFO"])
|
595
|
+
end
|
1135
596
|
|
1136
|
-
|
1137
|
-
|
1138
|
-
|
597
|
+
# @since 2.0.0
|
598
|
+
# @api private
|
599
|
+
def variable(env)
|
600
|
+
@variable[env["REQUEST_METHOD"]]&.find(env["PATH_INFO"])
|
1139
601
|
end
|
1140
602
|
|
1141
|
-
#
|
1142
|
-
#
|
1143
|
-
|
1144
|
-
|
1145
|
-
|
1146
|
-
|
1147
|
-
|
1148
|
-
|
1149
|
-
|
1150
|
-
|
1151
|
-
#
|
1152
|
-
# @since 0.1.0
|
1153
|
-
#
|
1154
|
-
# @example
|
1155
|
-
# require 'hanami/router'
|
1156
|
-
#
|
1157
|
-
# router = Hanami::Router.new(scheme: 'https', host: 'hanamirb.org')
|
1158
|
-
# router.get '/login', to: 'sessions#new', as: :login
|
1159
|
-
# router.get '/:name', to: 'frameworks#show', as: :framework
|
1160
|
-
#
|
1161
|
-
# router.path(:login) # => "/login"
|
1162
|
-
# router.path(:login, return_to: '/dashboard') # => "/login?return_to=%2Fdashboard"
|
1163
|
-
# router.path(:framework, name: 'router') # => "/router"
|
1164
|
-
def path(route, *args)
|
1165
|
-
@router.path(route, *args)
|
603
|
+
# @since 2.0.0
|
604
|
+
# @api private
|
605
|
+
def globbed(env)
|
606
|
+
@globbed[env["REQUEST_METHOD"]]&.each do |path, to|
|
607
|
+
if (match = path.match(env["PATH_INFO"]))
|
608
|
+
return [to, match.named_captures]
|
609
|
+
end
|
610
|
+
end
|
611
|
+
|
612
|
+
nil
|
1166
613
|
end
|
1167
614
|
|
1168
|
-
#
|
1169
|
-
#
|
1170
|
-
|
1171
|
-
|
1172
|
-
|
1173
|
-
|
1174
|
-
|
1175
|
-
|
1176
|
-
|
1177
|
-
|
1178
|
-
|
1179
|
-
|
1180
|
-
|
1181
|
-
|
1182
|
-
|
1183
|
-
|
1184
|
-
# router = Hanami::Router.new(scheme: 'https', host: 'hanamirb.org')
|
1185
|
-
# router.get '/login', to: 'sessions#new', as: :login
|
1186
|
-
# router.get '/:name', to: 'frameworks#show', as: :framework
|
1187
|
-
#
|
1188
|
-
# router.url(:login) # => "https://hanamirb.org/login"
|
1189
|
-
# router.url(:login, return_to: '/dashboard') # => "https://hanamirb.org/login?return_to=%2Fdashboard"
|
1190
|
-
# router.url(:framework, name: 'router') # => "https://hanamirb.org/router"
|
1191
|
-
def url(route, *args)
|
1192
|
-
@router.url(route, *args)
|
615
|
+
# @since 2.0.0
|
616
|
+
# @api private
|
617
|
+
def mounted(env)
|
618
|
+
@mounted.each do |prefix, app|
|
619
|
+
next unless (match = prefix.peek_match(env["PATH_INFO"]))
|
620
|
+
|
621
|
+
# TODO: ensure compatibility with existing env["SCRIPT_NAME"]
|
622
|
+
# TODO: cleanup this code
|
623
|
+
env["SCRIPT_NAME"] = env["SCRIPT_NAME"].to_s + prefix.to_s
|
624
|
+
env["PATH_INFO"] = env["PATH_INFO"].sub(prefix.to_s, "")
|
625
|
+
env["PATH_INFO"] = "/" if env["PATH_INFO"] == ""
|
626
|
+
|
627
|
+
return [app, match.named_captures]
|
628
|
+
end
|
629
|
+
|
630
|
+
nil
|
1193
631
|
end
|
1194
632
|
|
1195
|
-
#
|
1196
|
-
#
|
1197
|
-
|
1198
|
-
|
1199
|
-
|
1200
|
-
|
1201
|
-
|
1202
|
-
#
|
1203
|
-
#
|
1204
|
-
|
1205
|
-
|
1206
|
-
# get '/login', to: 'sessions#new', as: :login
|
1207
|
-
# post '/login', to: 'sessions#create'
|
1208
|
-
# delete '/logout', to: 'sessions#destroy', as: :logout
|
1209
|
-
# end
|
1210
|
-
#
|
1211
|
-
# puts router.inspector
|
1212
|
-
# # => GET, HEAD / Home::Index
|
1213
|
-
# login GET, HEAD /login Sessions::New
|
1214
|
-
# POST /login Sessions::Create
|
1215
|
-
# logout GET, HEAD /logout Sessions::Destroy
|
1216
|
-
def inspector
|
1217
|
-
require 'hanami/routing/routes_inspector'
|
1218
|
-
Routing::RoutesInspector.new(@router.routes, @router.prefix)
|
633
|
+
# @since 2.0.0
|
634
|
+
# @api private
|
635
|
+
def not_allowed(env)
|
636
|
+
(_not_allowed_fixed(env) ||
|
637
|
+
_not_allowed_variable(env)) and return [405, {"Content-Length" => "11"}, ["Not Allowed"]]
|
638
|
+
end
|
639
|
+
|
640
|
+
# @since 2.0.0
|
641
|
+
# @api private
|
642
|
+
def not_found(env)
|
643
|
+
@not_found.call(env)
|
1219
644
|
end
|
1220
645
|
|
1221
646
|
protected
|
@@ -1233,20 +658,186 @@ module Hanami
|
|
1233
658
|
#
|
1234
659
|
# @see Hanami::Router#recognize
|
1235
660
|
# @see http://www.rubydoc.info/github/rack/rack/Rack%2FMockRequest.env_for
|
1236
|
-
def env_for(env,
|
1237
|
-
|
1238
|
-
|
1239
|
-
|
1240
|
-
when
|
661
|
+
def env_for(env, params = {}, options = {})
|
662
|
+
require "rack/mock"
|
663
|
+
|
664
|
+
case env
|
665
|
+
when ::String
|
666
|
+
::Rack::MockRequest.env_for(env, options)
|
667
|
+
when ::Symbol
|
1241
668
|
begin
|
1242
|
-
url = path(env, params
|
1243
|
-
return env_for(url, options)
|
1244
|
-
rescue Hanami::
|
1245
|
-
{}
|
669
|
+
url = path(env, params)
|
670
|
+
return env_for(url, params, options) # rubocop:disable Style/RedundantReturn
|
671
|
+
rescue Hanami::Router::InvalidRouteException
|
672
|
+
{} # Empty Rack env
|
1246
673
|
end
|
1247
674
|
else
|
1248
675
|
env
|
1249
676
|
end
|
1250
677
|
end
|
678
|
+
|
679
|
+
private
|
680
|
+
|
681
|
+
# @since 2.0.0
|
682
|
+
# @api private
|
683
|
+
DEFAULT_BASE_URL = "http://localhost"
|
684
|
+
|
685
|
+
# @since 2.0.0
|
686
|
+
# @api private
|
687
|
+
DEFAULT_PREFIX = "/"
|
688
|
+
|
689
|
+
# @since 2.0.0
|
690
|
+
# @api private
|
691
|
+
DEFAULT_RESOLVER = ->(_, to) { to }
|
692
|
+
|
693
|
+
# @since 2.0.0
|
694
|
+
# @api private
|
695
|
+
DEFAULT_REDIRECT_CODE = 301
|
696
|
+
|
697
|
+
# @since 2.0.0
|
698
|
+
# @api private
|
699
|
+
PARAMS = "router.params"
|
700
|
+
|
701
|
+
# Default response when no route was matched
|
702
|
+
#
|
703
|
+
# @api private
|
704
|
+
# @since 2.0.0
|
705
|
+
NOT_FOUND = ->(*) { [404, {"Content-Length" => "9"}, ["Not Found"]] }.freeze
|
706
|
+
|
707
|
+
# @since 2.0.0
|
708
|
+
# @api private
|
709
|
+
def lookup(env)
|
710
|
+
endpoint = fixed(env)
|
711
|
+
return [endpoint, {}] if endpoint
|
712
|
+
|
713
|
+
variable(env) || globbed(env) || mounted(env)
|
714
|
+
end
|
715
|
+
|
716
|
+
# @since 2.0.0
|
717
|
+
# @api private
|
718
|
+
def add_route(http_method, path, to, as, constraints, &blk)
|
719
|
+
path = prefixed_path(path)
|
720
|
+
to = resolve_endpoint(path, to, blk)
|
721
|
+
|
722
|
+
if globbed?(path)
|
723
|
+
add_globbed_route(http_method, path, to, constraints)
|
724
|
+
elsif variable?(path)
|
725
|
+
add_variable_route(http_method, path, to, constraints)
|
726
|
+
else
|
727
|
+
add_fixed_route(http_method, path, to)
|
728
|
+
end
|
729
|
+
|
730
|
+
add_named_route(path, as, constraints) if as
|
731
|
+
end
|
732
|
+
|
733
|
+
# @since 2.0.0
|
734
|
+
# @api private
|
735
|
+
def resolve_endpoint(path, to, blk)
|
736
|
+
(to || blk) or raise MissingEndpointError.new(path)
|
737
|
+
to = Block.new(@block_context, blk) if to.nil?
|
738
|
+
|
739
|
+
@resolver.call(path, to)
|
740
|
+
end
|
741
|
+
|
742
|
+
# @since 2.0.0
|
743
|
+
# @api private
|
744
|
+
def add_globbed_route(http_method, path, to, constraints)
|
745
|
+
@globbed[http_method] ||= []
|
746
|
+
@globbed[http_method] << [Segment.fabricate(path, **constraints), to]
|
747
|
+
end
|
748
|
+
|
749
|
+
# @since 2.0.0
|
750
|
+
# @api private
|
751
|
+
def add_variable_route(http_method, path, to, constraints)
|
752
|
+
@variable[http_method] ||= Trie.new
|
753
|
+
@variable[http_method].add(path, to, constraints)
|
754
|
+
end
|
755
|
+
|
756
|
+
# @since 2.0.0
|
757
|
+
# @api private
|
758
|
+
def add_fixed_route(http_method, path, to)
|
759
|
+
@fixed[http_method] ||= {}
|
760
|
+
@fixed[http_method][path] = to
|
761
|
+
end
|
762
|
+
|
763
|
+
# @since 2.0.0
|
764
|
+
# @api private
|
765
|
+
def add_named_route(path, as, constraints)
|
766
|
+
@url_helpers.add(prefixed_name(as), Segment.fabricate(path, **constraints))
|
767
|
+
end
|
768
|
+
|
769
|
+
# @since 2.0.0
|
770
|
+
# @api private
|
771
|
+
def variable?(path)
|
772
|
+
/:/.match?(path)
|
773
|
+
end
|
774
|
+
|
775
|
+
# @since 2.0.0
|
776
|
+
# @api private
|
777
|
+
def globbed?(path)
|
778
|
+
/\*/.match?(path)
|
779
|
+
end
|
780
|
+
|
781
|
+
# @since 2.0.0
|
782
|
+
# @api private
|
783
|
+
def prefixed_path(path)
|
784
|
+
@path_prefix.join(path).to_s
|
785
|
+
end
|
786
|
+
|
787
|
+
# @since 2.0.0
|
788
|
+
# @api private
|
789
|
+
def prefixed_name(name)
|
790
|
+
@name_prefix.relative_join(name, "_").to_sym
|
791
|
+
end
|
792
|
+
|
793
|
+
# @since 2.0.0
|
794
|
+
# @api private
|
795
|
+
def _redirect(to, code)
|
796
|
+
body = Rack::Utils::HTTP_STATUS_CODES.fetch(code) do
|
797
|
+
raise UnknownHTTPStatusCodeError.new(code)
|
798
|
+
end
|
799
|
+
|
800
|
+
destination = prefixed_path(to)
|
801
|
+
Redirect.new(destination, ->(*) { [code, {"Location" => destination}, [body]] })
|
802
|
+
end
|
803
|
+
|
804
|
+
# @since 2.0.0
|
805
|
+
# @api private
|
806
|
+
def _params(env, params)
|
807
|
+
params ||= {}
|
808
|
+
env[PARAMS] ||= {}
|
809
|
+
env[PARAMS].merge!(Rack::Utils.parse_nested_query(env["QUERY_STRING"]))
|
810
|
+
env[PARAMS].merge!(params)
|
811
|
+
env[PARAMS] = Params.deep_symbolize(env[PARAMS])
|
812
|
+
env
|
813
|
+
end
|
814
|
+
|
815
|
+
# @since 2.0.0
|
816
|
+
# @api private
|
817
|
+
def _not_allowed_fixed(env)
|
818
|
+
found = false
|
819
|
+
|
820
|
+
@fixed.each_value do |routes|
|
821
|
+
break if found
|
822
|
+
|
823
|
+
found = routes.key?(env["PATH_INFO"])
|
824
|
+
end
|
825
|
+
|
826
|
+
found
|
827
|
+
end
|
828
|
+
|
829
|
+
# @since 2.0.0
|
830
|
+
# @api private
|
831
|
+
def _not_allowed_variable(env)
|
832
|
+
found = false
|
833
|
+
|
834
|
+
@variable.each_value do |routes|
|
835
|
+
break if found
|
836
|
+
|
837
|
+
found = routes.find(env["PATH_INFO"])
|
838
|
+
end
|
839
|
+
|
840
|
+
found
|
841
|
+
end
|
1251
842
|
end
|
1252
843
|
end
|