hanami-router 0.0.0 → 0.6.0
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 +101 -0
- data/LICENSE.md +22 -0
- data/README.md +667 -9
- data/hanami-router.gemspec +18 -12
- data/lib/hanami-router.rb +1 -0
- data/lib/hanami/router.rb +1160 -3
- data/lib/hanami/router/version.rb +3 -2
- data/lib/hanami/routing/endpoint.rb +151 -0
- data/lib/hanami/routing/endpoint_resolver.rb +225 -0
- data/lib/hanami/routing/error.rb +7 -0
- data/lib/hanami/routing/force_ssl.rb +209 -0
- data/lib/hanami/routing/http_router.rb +187 -0
- data/lib/hanami/routing/namespace.rb +92 -0
- data/lib/hanami/routing/parsers.rb +71 -0
- data/lib/hanami/routing/parsing/json_parser.rb +28 -0
- data/lib/hanami/routing/parsing/parser.rb +58 -0
- data/lib/hanami/routing/recognized_route.rb +153 -0
- data/lib/hanami/routing/resource.rb +116 -0
- data/lib/hanami/routing/resource/action.rb +387 -0
- data/lib/hanami/routing/resource/nested.rb +39 -0
- data/lib/hanami/routing/resource/options.rb +74 -0
- data/lib/hanami/routing/resources.rb +48 -0
- data/lib/hanami/routing/resources/action.rb +150 -0
- data/lib/hanami/routing/route.rb +62 -0
- data/lib/hanami/routing/routes_inspector.rb +215 -0
- metadata +94 -16
- data/.gitignore +0 -9
- data/Gemfile +0 -4
- data/Rakefile +0 -2
- data/bin/console +0 -14
- data/bin/setup +0 -8
@@ -0,0 +1,187 @@
|
|
1
|
+
require 'http_router'
|
2
|
+
require 'hanami/utils/io'
|
3
|
+
require 'hanami/routing/endpoint_resolver'
|
4
|
+
require 'hanami/routing/route'
|
5
|
+
require 'hanami/routing/parsers'
|
6
|
+
require 'hanami/routing/force_ssl'
|
7
|
+
require 'hanami/routing/error'
|
8
|
+
require 'hanami/utils/path_prefix'
|
9
|
+
|
10
|
+
Hanami::Utils::IO.silence_warnings do
|
11
|
+
HttpRouter::Route::VALID_HTTP_VERBS = %w{GET POST PUT PATCH DELETE HEAD OPTIONS TRACE}
|
12
|
+
end
|
13
|
+
|
14
|
+
module Hanami
|
15
|
+
module Routing
|
16
|
+
# Invalid route
|
17
|
+
# This is raised when the router fails to recognize a route, because of the
|
18
|
+
# given arguments.
|
19
|
+
#
|
20
|
+
# @since 0.1.0
|
21
|
+
class InvalidRouteException < Hanami::Routing::Error
|
22
|
+
end
|
23
|
+
|
24
|
+
# HTTP router
|
25
|
+
#
|
26
|
+
# This implementation is based on ::HttpRouter (http_router gem).
|
27
|
+
#
|
28
|
+
# Hanami::Router wraps an instance of this class, in order to protect its
|
29
|
+
# public API from any future change of ::HttpRouter.
|
30
|
+
#
|
31
|
+
# @since 0.1.0
|
32
|
+
# @api private
|
33
|
+
class HttpRouter < ::HttpRouter
|
34
|
+
# Script name - rack enviroment variable
|
35
|
+
#
|
36
|
+
# @since 0.5.0
|
37
|
+
# @api private
|
38
|
+
SCRIPT_NAME = 'SCRIPT_NAME'.freeze
|
39
|
+
|
40
|
+
# @since 0.5.0
|
41
|
+
# @api private
|
42
|
+
attr_reader :namespace
|
43
|
+
|
44
|
+
# Initialize the router.
|
45
|
+
#
|
46
|
+
# @see Hanami::Router#initialize
|
47
|
+
#
|
48
|
+
# @since 0.1.0
|
49
|
+
# @api private
|
50
|
+
def initialize(options = {}, &blk)
|
51
|
+
super(options, &nil)
|
52
|
+
|
53
|
+
@namespace = options[:namespace] if options[:namespace]
|
54
|
+
@default_scheme = options[:scheme] if options[:scheme]
|
55
|
+
@default_host = options[:host] if options[:host]
|
56
|
+
@default_port = options[:port] if options[:port]
|
57
|
+
@route_class = options[:route] || Routing::Route
|
58
|
+
@resolver = options[:resolver] || Routing::EndpointResolver.new(options)
|
59
|
+
@parsers = Routing::Parsers.new(options[:parsers])
|
60
|
+
@prefix = Utils::PathPrefix.new(options[:prefix] || '')
|
61
|
+
@force_ssl = Hanami::Routing::ForceSsl.new(!!options[:force_ssl], host: @default_host, port: @default_port)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Separator between controller and action name.
|
65
|
+
#
|
66
|
+
# @see Hanami::Routing::EndpointResolver::ACTION_SEPARATOR
|
67
|
+
#
|
68
|
+
# @since 0.1.0
|
69
|
+
# @api private
|
70
|
+
def action_separator
|
71
|
+
@resolver.action_separator
|
72
|
+
end
|
73
|
+
|
74
|
+
# Finds a path from the given options.
|
75
|
+
#
|
76
|
+
# @see Hanami::Routing::EndpointResolver#find
|
77
|
+
#
|
78
|
+
# @since 0.1.0
|
79
|
+
# @api private
|
80
|
+
def find(options)
|
81
|
+
@resolver.find(options)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Generate a relative URL for a specified named route.
|
85
|
+
#
|
86
|
+
# @see Hanami::Router#path
|
87
|
+
#
|
88
|
+
# @since 0.1.0
|
89
|
+
# @api private
|
90
|
+
def raw_path(route, *args)
|
91
|
+
_rescue_url_recognition do
|
92
|
+
_custom_path(super(route, *args))
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Generate an absolute URL for a specified named route.
|
97
|
+
#
|
98
|
+
# @see Hanami::Router#path
|
99
|
+
#
|
100
|
+
# @since 0.1.0
|
101
|
+
# @api private
|
102
|
+
def raw_url(route, *args)
|
103
|
+
_rescue_url_recognition do
|
104
|
+
_custom_path(super(route, *args))
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Support for OPTIONS HTTP verb
|
109
|
+
#
|
110
|
+
# @see Hanami::Router#options
|
111
|
+
#
|
112
|
+
# @since 0.1.0
|
113
|
+
# @api private
|
114
|
+
def options(path, options = {}, &blk)
|
115
|
+
add_with_request_method(path, :options, options, &blk)
|
116
|
+
end
|
117
|
+
|
118
|
+
# Allow to mount a Rack app
|
119
|
+
#
|
120
|
+
# @see Hanami::Router#mount
|
121
|
+
#
|
122
|
+
# @since 0.1.1
|
123
|
+
# @api private
|
124
|
+
def mount(app, options)
|
125
|
+
add("#{ options.fetch(:at) }*").to(
|
126
|
+
@resolver.resolve(to: app)
|
127
|
+
)
|
128
|
+
end
|
129
|
+
|
130
|
+
# @api private
|
131
|
+
def raw_call(env, &blk)
|
132
|
+
if response = @force_ssl.call(env)
|
133
|
+
response
|
134
|
+
else
|
135
|
+
super(@parsers.call(env))
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# @api private
|
140
|
+
def reset!
|
141
|
+
uncompile
|
142
|
+
@routes, @named_routes, @root = [], Hash.new{|h,k| h[k] = []}, Node::Root.new(self)
|
143
|
+
@default_host, @default_port, @default_scheme = 'localhost', 80, 'http'
|
144
|
+
end
|
145
|
+
|
146
|
+
# @api private
|
147
|
+
def pass_on_response(response)
|
148
|
+
super response.to_a
|
149
|
+
end
|
150
|
+
|
151
|
+
# @api private
|
152
|
+
def no_response(request, env)
|
153
|
+
if request.acceptable_methods.any? && !request.acceptable_methods.include?(env['REQUEST_METHOD'])
|
154
|
+
[405, {'Allow' => request.acceptable_methods.sort.join(", ")}, []]
|
155
|
+
else
|
156
|
+
@default_app.call(env)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# @api private
|
161
|
+
# @since 0.5.0
|
162
|
+
def rewrite_path_info(env, request)
|
163
|
+
super
|
164
|
+
env[SCRIPT_NAME] = @prefix + env[SCRIPT_NAME]
|
165
|
+
end
|
166
|
+
|
167
|
+
private
|
168
|
+
|
169
|
+
def _rescue_url_recognition
|
170
|
+
yield
|
171
|
+
rescue ::HttpRouter::InvalidRouteException,
|
172
|
+
::HttpRouter::TooManyParametersException => e
|
173
|
+
raise Routing::InvalidRouteException.new("#{ e.message } - please check given arguments")
|
174
|
+
end
|
175
|
+
|
176
|
+
def add_with_request_method(path, method, opts = {}, &app)
|
177
|
+
super.generate(@resolver, opts, &app)
|
178
|
+
end
|
179
|
+
|
180
|
+
def _custom_path(uri_string)
|
181
|
+
uri = URI.parse(uri_string)
|
182
|
+
uri.path = @prefix.join(uri.path)
|
183
|
+
uri.to_s
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
require 'hanami/utils/path_prefix'
|
3
|
+
|
4
|
+
module Hanami
|
5
|
+
module Routing
|
6
|
+
# Namespace for routes.
|
7
|
+
# Implementation of Hanami::Router#namespace
|
8
|
+
#
|
9
|
+
# @since 0.1.0
|
10
|
+
#
|
11
|
+
# @api private
|
12
|
+
#
|
13
|
+
# @see Hanami::Router#namespace
|
14
|
+
class Namespace < SimpleDelegator
|
15
|
+
# @api private
|
16
|
+
# @since 0.1.0
|
17
|
+
def initialize(router, name, &blk)
|
18
|
+
@router = router
|
19
|
+
@name = Utils::PathPrefix.new(name)
|
20
|
+
__setobj__(@router)
|
21
|
+
instance_eval(&blk)
|
22
|
+
end
|
23
|
+
|
24
|
+
# @api private
|
25
|
+
# @since 0.1.0
|
26
|
+
def get(path, options = {}, &endpoint)
|
27
|
+
super(@name.join(path), options, &endpoint)
|
28
|
+
end
|
29
|
+
|
30
|
+
# @api private
|
31
|
+
# @since 0.1.0
|
32
|
+
def post(path, options = {}, &endpoint)
|
33
|
+
super(@name.join(path), options, &endpoint)
|
34
|
+
end
|
35
|
+
|
36
|
+
# @api private
|
37
|
+
# @since 0.1.0
|
38
|
+
def put(path, options = {}, &endpoint)
|
39
|
+
super(@name.join(path), options, &endpoint)
|
40
|
+
end
|
41
|
+
|
42
|
+
# @api private
|
43
|
+
# @since 0.1.0
|
44
|
+
def patch(path, options = {}, &endpoint)
|
45
|
+
super(@name.join(path), options, &endpoint)
|
46
|
+
end
|
47
|
+
|
48
|
+
# @api private
|
49
|
+
# @since 0.1.0
|
50
|
+
def delete(path, options = {}, &endpoint)
|
51
|
+
super(@name.join(path), options, &endpoint)
|
52
|
+
end
|
53
|
+
|
54
|
+
# @api private
|
55
|
+
# @since 0.1.0
|
56
|
+
def trace(path, options = {}, &endpoint)
|
57
|
+
super(@name.join(path), options, &endpoint)
|
58
|
+
end
|
59
|
+
|
60
|
+
# @api private
|
61
|
+
# @since 0.1.0
|
62
|
+
def options(path, options = {}, &endpoint)
|
63
|
+
super(@name.join(path), options, &endpoint)
|
64
|
+
end
|
65
|
+
|
66
|
+
# @api private
|
67
|
+
# @since 0.1.0
|
68
|
+
def resource(name, options = {})
|
69
|
+
super name, options.merge(namespace: @name.relative_join(options[:namespace]))
|
70
|
+
end
|
71
|
+
|
72
|
+
# @api private
|
73
|
+
# @since 0.1.0
|
74
|
+
def resources(name, options = {})
|
75
|
+
super name, options.merge(namespace: @name.relative_join(options[:namespace]))
|
76
|
+
end
|
77
|
+
|
78
|
+
# @api private
|
79
|
+
# @since 0.1.0
|
80
|
+
def redirect(path, options = {}, &endpoint)
|
81
|
+
super(@name.join(path), options.merge(to: @name.join(options[:to])), &endpoint)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Supports nested namespaces
|
85
|
+
# @api private
|
86
|
+
# @since 0.1.0
|
87
|
+
def namespace(name, &blk)
|
88
|
+
Routing::Namespace.new(self, name, &blk)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'hanami/routing/parsing/parser'
|
2
|
+
|
3
|
+
module Hanami
|
4
|
+
module Routing
|
5
|
+
class Parsers
|
6
|
+
CONTENT_TYPE = 'CONTENT_TYPE'.freeze
|
7
|
+
MEDIA_TYPE_MATCHER = /\s*[;,]\s*/.freeze
|
8
|
+
|
9
|
+
RACK_INPUT = 'rack.input'.freeze
|
10
|
+
ROUTER_PARAMS = 'router.params'.freeze
|
11
|
+
|
12
|
+
def initialize(parsers)
|
13
|
+
@parsers = prepare(parsers)
|
14
|
+
_redefine_call
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(env)
|
18
|
+
env
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def prepare(args)
|
23
|
+
result = Hash.new
|
24
|
+
args = Array(args)
|
25
|
+
return result if args.empty?
|
26
|
+
|
27
|
+
args.each do |arg|
|
28
|
+
parser = Parsing::Parser.for(arg)
|
29
|
+
|
30
|
+
parser.mime_types.each do |mime|
|
31
|
+
result[mime] = parser
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
result.default = Parsing::Parser.new
|
36
|
+
result
|
37
|
+
end
|
38
|
+
|
39
|
+
def _redefine_call
|
40
|
+
return if @parsers.empty?
|
41
|
+
|
42
|
+
define_singleton_method :call do |env|
|
43
|
+
body = env[RACK_INPUT].read
|
44
|
+
return env if body.empty?
|
45
|
+
|
46
|
+
env[RACK_INPUT].rewind # somebody might try to read this stream
|
47
|
+
env[ROUTER_PARAMS] ||= {} # prepare params
|
48
|
+
|
49
|
+
env[ROUTER_PARAMS].merge!(
|
50
|
+
@parsers[
|
51
|
+
media_type(env)
|
52
|
+
].parse(body)
|
53
|
+
)
|
54
|
+
|
55
|
+
env
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def media_type(env)
|
60
|
+
if ct = content_type(env)
|
61
|
+
ct.split(MEDIA_TYPE_MATCHER, 2).first.downcase
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def content_type(env)
|
66
|
+
content_type = env[CONTENT_TYPE]
|
67
|
+
content_type.nil? || content_type.empty? ? nil : content_type
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Hanami
|
4
|
+
module Routing
|
5
|
+
module Parsing
|
6
|
+
class JsonParser < Parser
|
7
|
+
def mime_types
|
8
|
+
['application/json', 'application/vnd.api+json']
|
9
|
+
end
|
10
|
+
|
11
|
+
# Parse a json string
|
12
|
+
#
|
13
|
+
# @param body [String] a json string
|
14
|
+
#
|
15
|
+
# @return [Hash] the parsed json
|
16
|
+
#
|
17
|
+
# @raise [Hanami::Routing::Parsing::BodyParsingError] when the body can't be parsed.
|
18
|
+
#
|
19
|
+
# @since 0.2.0
|
20
|
+
def parse(body)
|
21
|
+
JSON.parse(body)
|
22
|
+
rescue JSON::ParserError => e
|
23
|
+
raise BodyParsingError.new(e.message)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'hanami/utils/class'
|
2
|
+
require 'hanami/utils/string'
|
3
|
+
require 'hanami/routing/error'
|
4
|
+
|
5
|
+
module Hanami
|
6
|
+
module Routing
|
7
|
+
module Parsing
|
8
|
+
# Body parsing error
|
9
|
+
# This is raised when parser fails to parse the body
|
10
|
+
#
|
11
|
+
# @since 0.5.0
|
12
|
+
class BodyParsingError < Hanami::Routing::Error
|
13
|
+
end
|
14
|
+
|
15
|
+
# @since 0.2.0
|
16
|
+
class UnknownParserError < Hanami::Routing::Error
|
17
|
+
def initialize(parser)
|
18
|
+
super("Unknown Parser: `#{ parser }'")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# @since 0.2.0
|
23
|
+
class Parser
|
24
|
+
# @since 0.2.0
|
25
|
+
def self.for(parser)
|
26
|
+
case parser
|
27
|
+
when String, Symbol
|
28
|
+
require_parser(parser)
|
29
|
+
else
|
30
|
+
parser
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# @since 0.2.0
|
35
|
+
def mime_types
|
36
|
+
raise NotImplementedError
|
37
|
+
end
|
38
|
+
|
39
|
+
# @since 0.2.0
|
40
|
+
def parse(body)
|
41
|
+
Hash.new
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
# @since 0.2.0
|
46
|
+
# @api private
|
47
|
+
def self.require_parser(parser)
|
48
|
+
require "hanami/routing/parsing/#{ parser }_parser"
|
49
|
+
|
50
|
+
parser = Utils::String.new(parser).classify
|
51
|
+
Utils::Class.load!("Hanami::Routing::Parsing::#{ parser }Parser").new
|
52
|
+
rescue LoadError, NameError
|
53
|
+
raise UnknownParserError.new(parser)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'hanami/utils/string'
|
2
|
+
|
3
|
+
module Hanami
|
4
|
+
module Routing
|
5
|
+
# Represents a result of router path recognition.
|
6
|
+
#
|
7
|
+
# @since 0.5.0
|
8
|
+
#
|
9
|
+
# @see Hanami::Router#recognize
|
10
|
+
class RecognizedRoute
|
11
|
+
# @since 0.5.0
|
12
|
+
# @api private
|
13
|
+
REQUEST_METHOD = 'REQUEST_METHOD'.freeze
|
14
|
+
|
15
|
+
# @since 0.5.0
|
16
|
+
# @api private
|
17
|
+
NAMESPACE = '%s::'.freeze
|
18
|
+
|
19
|
+
# @since 0.5.0
|
20
|
+
# @api private
|
21
|
+
NAMESPACE_REPLACEMENT = ''.freeze
|
22
|
+
|
23
|
+
# @since 0.5.0
|
24
|
+
# @api private
|
25
|
+
ACTION_PATH_SEPARATOR = '/'.freeze
|
26
|
+
|
27
|
+
# @since 0.5.0
|
28
|
+
# @api public
|
29
|
+
attr_reader :params
|
30
|
+
|
31
|
+
# Creates a new instance
|
32
|
+
#
|
33
|
+
# @param response [HttpRouter::Response] raw response of recognition
|
34
|
+
# @param env [Hash] Rack env
|
35
|
+
# @param router [Hanami::Routing::HttpRouter] low level router
|
36
|
+
#
|
37
|
+
# @return [Hanami::Routing::RecognizedRoute]
|
38
|
+
#
|
39
|
+
# @since 0.5.0
|
40
|
+
# @api private
|
41
|
+
def initialize(response, env, router)
|
42
|
+
@env = env
|
43
|
+
|
44
|
+
unless response.nil?
|
45
|
+
@endpoint = response.route.dest
|
46
|
+
@params = response.params
|
47
|
+
end
|
48
|
+
|
49
|
+
@namespace = router.namespace
|
50
|
+
@action_separator = router.action_separator
|
51
|
+
end
|
52
|
+
|
53
|
+
# Rack protocol compatibility
|
54
|
+
#
|
55
|
+
# @param env [Hash] Rack env
|
56
|
+
#
|
57
|
+
# @return [Array] serialized Rack response
|
58
|
+
#
|
59
|
+
# @raise [Hanami::Router::NotRoutableEndpointError] if not routable
|
60
|
+
#
|
61
|
+
# @since 0.5.0
|
62
|
+
# @api public
|
63
|
+
#
|
64
|
+
# @see Hanami::Routing::RecognizedRoute#routable?
|
65
|
+
# @see Hanami::Router::NotRoutableEndpointError
|
66
|
+
def call(env)
|
67
|
+
if routable?
|
68
|
+
@endpoint.call(env)
|
69
|
+
else
|
70
|
+
raise Hanami::Router::NotRoutableEndpointError.new(@env)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# HTTP verb (aka method)
|
75
|
+
#
|
76
|
+
# @return [String]
|
77
|
+
#
|
78
|
+
# @since 0.5.0
|
79
|
+
# @api public
|
80
|
+
def verb
|
81
|
+
@env[REQUEST_METHOD]
|
82
|
+
end
|
83
|
+
|
84
|
+
# Action name
|
85
|
+
#
|
86
|
+
# @return [String]
|
87
|
+
#
|
88
|
+
# @since 0.5.0
|
89
|
+
# @api public
|
90
|
+
#
|
91
|
+
# @see Hanami::Router#recognize
|
92
|
+
#
|
93
|
+
# @example
|
94
|
+
# require 'hanami/router'
|
95
|
+
#
|
96
|
+
# router = Hanami::Router.new do
|
97
|
+
# get '/books/:id', to: 'books#show'
|
98
|
+
# end
|
99
|
+
#
|
100
|
+
# puts router.recognize('/books/23').action # => "books#show"
|
101
|
+
def action
|
102
|
+
namespace = NAMESPACE % @namespace
|
103
|
+
|
104
|
+
if destination.match(namespace)
|
105
|
+
Hanami::Utils::String.new(
|
106
|
+
destination.sub(namespace, NAMESPACE_REPLACEMENT)
|
107
|
+
).underscore.rsub(ACTION_PATH_SEPARATOR, @action_separator)
|
108
|
+
else
|
109
|
+
destination
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Check if routable
|
114
|
+
#
|
115
|
+
# @return [TrueClass,FalseClass]
|
116
|
+
#
|
117
|
+
# @since 0.5.0
|
118
|
+
# @api public
|
119
|
+
#
|
120
|
+
# @see Hanami::Router#recognize
|
121
|
+
#
|
122
|
+
# @example
|
123
|
+
# require 'hanami/router'
|
124
|
+
#
|
125
|
+
# router = Hanami::Router.new do
|
126
|
+
# get '/', to: 'home#index'
|
127
|
+
# end
|
128
|
+
#
|
129
|
+
# puts router.recognize('/').routable? # => true
|
130
|
+
# puts router.recognize('/foo').routable? # => false
|
131
|
+
def routable?
|
132
|
+
!!@endpoint
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
# @since 0.5.0
|
138
|
+
# @api private
|
139
|
+
#
|
140
|
+
# @see Hanami::Routing::Endpoint
|
141
|
+
def destination
|
142
|
+
@destination ||= begin
|
143
|
+
case k = @endpoint.__getobj__
|
144
|
+
when Class
|
145
|
+
k
|
146
|
+
else
|
147
|
+
k.class
|
148
|
+
end.name
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|