hanami-router 2.0.0.alpha1 → 2.0.0.alpha2
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 +16 -0
- data/README.md +11 -401
- data/hanami-router.gemspec +2 -4
- data/lib/hanami/middleware/body_parser.rb +2 -2
- data/lib/hanami/middleware/body_parser/class_interface.rb +10 -4
- data/lib/hanami/middleware/body_parser/json_parser.rb +4 -4
- data/lib/hanami/router.rb +525 -1040
- data/lib/hanami/router/block.rb +88 -0
- data/lib/hanami/router/error.rb +67 -0
- data/lib/hanami/router/node.rb +93 -0
- data/lib/hanami/router/params.rb +35 -0
- data/lib/hanami/router/prefix.rb +65 -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 +2 -1
- metadata +17 -48
- data/lib/hanami/routing.rb +0 -193
- data/lib/hanami/routing/endpoint.rb +0 -213
- data/lib/hanami/routing/endpoint_resolver.rb +0 -242
- data/lib/hanami/routing/prefix.rb +0 -102
- data/lib/hanami/routing/recognized_route.rb +0 -233
- data/lib/hanami/routing/resource.rb +0 -121
- data/lib/hanami/routing/resource/action.rb +0 -427
- data/lib/hanami/routing/resource/nested.rb +0 -44
- data/lib/hanami/routing/resource/options.rb +0 -76
- data/lib/hanami/routing/resources.rb +0 -50
- data/lib/hanami/routing/resources/action.rb +0 -161
- data/lib/hanami/routing/routes_inspector.rb +0 -223
- data/lib/hanami/routing/scope.rb +0 -112
@@ -1,213 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "delegate"
|
4
|
-
require "hanami/utils/class"
|
5
|
-
require "hanami/utils/string"
|
6
|
-
|
7
|
-
module Hanami
|
8
|
-
module Routing
|
9
|
-
# Routes endpoint
|
10
|
-
#
|
11
|
-
# @since 2.0.0
|
12
|
-
# @api private
|
13
|
-
module Endpoint
|
14
|
-
# @since 2.0.0
|
15
|
-
# @api private
|
16
|
-
#
|
17
|
-
# FIXME: Shall this be the default of Utils::Class.load! ?
|
18
|
-
DEFAULT_NAMESPACE = Object
|
19
|
-
|
20
|
-
# Controller / action separator for Hanami
|
21
|
-
#
|
22
|
-
# @since 2.0.0
|
23
|
-
# @api private
|
24
|
-
#
|
25
|
-
# @example
|
26
|
-
# require "hanami/router"
|
27
|
-
#
|
28
|
-
# Hanami::Router.new do
|
29
|
-
# get "/home", to: "home#index"
|
30
|
-
# end
|
31
|
-
ACTION_SEPARATOR = "#"
|
32
|
-
|
33
|
-
# Replacement to load an action from the string name.
|
34
|
-
#
|
35
|
-
# Please note that the `"/"` value is required by `Hanami::Utils::String#classify`.
|
36
|
-
#
|
37
|
-
# Given the `"home#index"` string, with the `Web::Controllers` namespace,
|
38
|
-
# it will try to load `Web::Controllers::Home::Index` action.
|
39
|
-
#
|
40
|
-
# @since 2.0.0
|
41
|
-
# @api private
|
42
|
-
ACTION_SEPARATOR_REPLACEMENT = "/"
|
43
|
-
|
44
|
-
# Find an endpoint for the given name
|
45
|
-
#
|
46
|
-
# @param name [String,Class,Proc,Object] the endpoint expressed as name
|
47
|
-
# (`String`), as a Rack class application (`Class`), as a Rack
|
48
|
-
# compatible proc (`Proc`), or as any other Rack compatible object
|
49
|
-
# (`Object`)
|
50
|
-
# @param namespace [Module] the Ruby module where to lookup the endpoint
|
51
|
-
# @param configuration [Hanami::Controller::Configuration] the action
|
52
|
-
# configuration
|
53
|
-
#
|
54
|
-
# @raise [Hanami::Routing::NotCallableEndpointError] if the found object
|
55
|
-
# doesn't implement Rack protocol (`#call`)
|
56
|
-
#
|
57
|
-
# @return [Object, Hanami::Routing::LazyEndpoint] a Rack compatible
|
58
|
-
# endpoint
|
59
|
-
#
|
60
|
-
# @since 2.0.0
|
61
|
-
# @api private
|
62
|
-
def self.find(name, namespace, configuration = nil)
|
63
|
-
endpoint = case name
|
64
|
-
when String
|
65
|
-
find_string(name, namespace || DEFAULT_NAMESPACE, configuration)
|
66
|
-
when Class
|
67
|
-
name.respond_to?(:call) ? name : name.new
|
68
|
-
else
|
69
|
-
name
|
70
|
-
end
|
71
|
-
|
72
|
-
raise NotCallableEndpointError.new(endpoint) unless endpoint.respond_to?(:call)
|
73
|
-
|
74
|
-
endpoint
|
75
|
-
end
|
76
|
-
|
77
|
-
# @since 1.0.1
|
78
|
-
# @api private
|
79
|
-
def redirect?
|
80
|
-
false
|
81
|
-
end
|
82
|
-
|
83
|
-
# @since 1.0.1
|
84
|
-
# @api private
|
85
|
-
def destination_path
|
86
|
-
end
|
87
|
-
|
88
|
-
# Find an endpoint from its name
|
89
|
-
#
|
90
|
-
# @param name [String] the endpoint name
|
91
|
-
# @param namespace [Module] the Ruby module where to lookup the endpoint
|
92
|
-
# @param configuration [Hanami::Controller::Configuration] the action
|
93
|
-
# configuration
|
94
|
-
#
|
95
|
-
# @return [Object, Hanami::Routing::LazyEndpoint] a Rack compatible
|
96
|
-
# endpoint
|
97
|
-
#
|
98
|
-
# @since 2.0.0
|
99
|
-
# @api private
|
100
|
-
#
|
101
|
-
# @example Basic Usage
|
102
|
-
# Hanami::Routing::Endpoint.find("MyMiddleware")
|
103
|
-
# # => #<MyMiddleware:0x007ff6df06f468>
|
104
|
-
#
|
105
|
-
# @example Hanami Action
|
106
|
-
# Hanami::Routing::Endpoint.find("home#index", Web::Controllers)
|
107
|
-
# # => #<Web::Controllers::Home::Index:0x007ff6df06f468>
|
108
|
-
def self.find_string(name, namespace, configuration)
|
109
|
-
n = Utils::String.new(name.sub(ACTION_SEPARATOR, ACTION_SEPARATOR_REPLACEMENT)).classify.to_s
|
110
|
-
klass = Utils::Class.load!(n, namespace)
|
111
|
-
|
112
|
-
if hanami_action?(name, n)
|
113
|
-
klass.new(configuration: configuration)
|
114
|
-
else
|
115
|
-
klass.new
|
116
|
-
end
|
117
|
-
rescue NameError
|
118
|
-
Hanami::Routing::LazyEndpoint.new(n, namespace)
|
119
|
-
end
|
120
|
-
|
121
|
-
private_class_method :find_string
|
122
|
-
|
123
|
-
def self.hanami_action?(name, endpoint)
|
124
|
-
name != endpoint
|
125
|
-
end
|
126
|
-
|
127
|
-
private_class_method :hanami_action?
|
128
|
-
end
|
129
|
-
|
130
|
-
# Routing endpoint
|
131
|
-
# This is the object that responds to an HTTP request made against a certain
|
132
|
-
# path.
|
133
|
-
#
|
134
|
-
# The router will use this class for the same use cases of `ClassEndpoint`,
|
135
|
-
# but when the target class can't be found, instead of raise a `LoadError`
|
136
|
-
# we reference in a lazy endpoint.
|
137
|
-
#
|
138
|
-
# For each incoming HTTP request, it will look for the referenced class,
|
139
|
-
# then it will instantiate and invoke #call on the object.
|
140
|
-
#
|
141
|
-
# This behavior is required to solve a chicken-egg situation when we try
|
142
|
-
# to load the router first and then the application with all its endpoints.
|
143
|
-
#
|
144
|
-
# @since 0.1.0
|
145
|
-
#
|
146
|
-
# @api private
|
147
|
-
#
|
148
|
-
# @see Hanami::Routing::ClassEndpoint
|
149
|
-
class LazyEndpoint < SimpleDelegator
|
150
|
-
# Initialize the lazy endpoint
|
151
|
-
#
|
152
|
-
# @since 0.1.0
|
153
|
-
# @api private
|
154
|
-
def initialize(name, namespace)
|
155
|
-
@name = name
|
156
|
-
@namespace = namespace
|
157
|
-
end
|
158
|
-
|
159
|
-
# Rack interface
|
160
|
-
#
|
161
|
-
# @raise [EndpointNotFound] when the endpoint can't be found.
|
162
|
-
#
|
163
|
-
# @since 0.1.0
|
164
|
-
# @api private
|
165
|
-
def call(env)
|
166
|
-
obj.call(env)
|
167
|
-
end
|
168
|
-
|
169
|
-
# @since 0.2.0
|
170
|
-
# @api private
|
171
|
-
def inspect
|
172
|
-
# TODO: review this implementation once the namespace feature will be
|
173
|
-
# cleaned up.
|
174
|
-
result = begin
|
175
|
-
klass
|
176
|
-
rescue
|
177
|
-
nil
|
178
|
-
end
|
179
|
-
|
180
|
-
if result.nil?
|
181
|
-
result = @name
|
182
|
-
result = "#{@namespace}::#{result}" if @namespace != Object
|
183
|
-
end
|
184
|
-
|
185
|
-
result
|
186
|
-
end
|
187
|
-
|
188
|
-
# @since 1.0.0
|
189
|
-
# @api private
|
190
|
-
def routable?
|
191
|
-
!__getobj__.nil?
|
192
|
-
rescue ArgumentError
|
193
|
-
false
|
194
|
-
end
|
195
|
-
|
196
|
-
private
|
197
|
-
|
198
|
-
# @since 0.1.0
|
199
|
-
# @api private
|
200
|
-
def obj
|
201
|
-
klass.new
|
202
|
-
end
|
203
|
-
|
204
|
-
# @since 0.2.0
|
205
|
-
# @api private
|
206
|
-
def klass
|
207
|
-
Utils::Class.load!(@name, @namespace)
|
208
|
-
rescue NameError => e
|
209
|
-
raise EndpointNotFound.new(e.message)
|
210
|
-
end
|
211
|
-
end
|
212
|
-
end
|
213
|
-
end
|
@@ -1,242 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "hanami/utils/string"
|
4
|
-
require "hanami/utils/class"
|
5
|
-
require "hanami/routing/endpoint"
|
6
|
-
|
7
|
-
module Hanami
|
8
|
-
module Routing
|
9
|
-
# Resolve duck-typed endpoints
|
10
|
-
#
|
11
|
-
# @since 0.1.0
|
12
|
-
#
|
13
|
-
# @api private
|
14
|
-
class EndpointResolver
|
15
|
-
# @since 0.2.0
|
16
|
-
# @api private
|
17
|
-
NAMING_PATTERN = "%<controller>s::%<action>s"
|
18
|
-
|
19
|
-
# @since 0.7.0
|
20
|
-
# @api private
|
21
|
-
DEFAULT_RESPONSE = [404, { "X-Cascade" => "pass" }, "Not Found"].freeze
|
22
|
-
|
23
|
-
# Default separator for controller and action.
|
24
|
-
# A different separator can be passed to #initialize with the `:separator` option.
|
25
|
-
#
|
26
|
-
# @see #initialize
|
27
|
-
# @see #resolve
|
28
|
-
#
|
29
|
-
# @since 0.1.0
|
30
|
-
#
|
31
|
-
# @example
|
32
|
-
# require 'hanami/router'
|
33
|
-
#
|
34
|
-
# router = Hanami::Router.new do
|
35
|
-
# get '/', to: 'articles#show'
|
36
|
-
# end
|
37
|
-
ACTION_SEPARATOR = "#"
|
38
|
-
|
39
|
-
attr_reader :action_separator
|
40
|
-
|
41
|
-
# Initialize an endpoint resolver
|
42
|
-
#
|
43
|
-
# @param options [Hash] the options used to customize lookup behavior
|
44
|
-
#
|
45
|
-
# @option options [Class] :endpoint the endpoint class that is returned
|
46
|
-
# by `#resolve`. (defaults to `Hanami::Routing::Endpoint`)
|
47
|
-
#
|
48
|
-
# @option options [Class,Module] :namespace the Ruby namespace where to
|
49
|
-
# lookup for controllers and actions. (defaults to `Object`)
|
50
|
-
#
|
51
|
-
# @option options [String] :pattern the string to interpolate in order
|
52
|
-
# to return an action name. This string SHOULD contain
|
53
|
-
# <tt>'%{controller}'</tt> and <tt>'%{action}'</tt>, all the other keys
|
54
|
-
# will be ignored.
|
55
|
-
# See the examples below.
|
56
|
-
#
|
57
|
-
# @option options [String] :action_separator the separator between controller and
|
58
|
-
# action name. (defaults to `ACTION_SEPARATOR`)
|
59
|
-
#
|
60
|
-
# @return [Hanami::Routing::EndpointResolver] self
|
61
|
-
#
|
62
|
-
# @since 0.1.0
|
63
|
-
# @api private
|
64
|
-
#
|
65
|
-
# @example Specify custom endpoint class
|
66
|
-
# require 'hanami/router'
|
67
|
-
#
|
68
|
-
# resolver = Hanami::Routing::EndpointResolver.new(endpoint: CustomEndpoint)
|
69
|
-
# router = Hanami::Router.new(resolver: resolver)
|
70
|
-
#
|
71
|
-
# router.get('/', to: endpoint).dest # => #<CustomEndpoint:0x007f97f3359570 ...>
|
72
|
-
#
|
73
|
-
# @example Specify custom Ruby namespace
|
74
|
-
# require 'hanami/router'
|
75
|
-
#
|
76
|
-
# resolver = Hanami::Routing::EndpointResolver.new(namespace: MyApp)
|
77
|
-
# router = Hanami::Router.new(resolver: resolver)
|
78
|
-
#
|
79
|
-
# router.get('/', to: 'articles#show')
|
80
|
-
# # => Will look for: MyApp::Articles::Show
|
81
|
-
#
|
82
|
-
#
|
83
|
-
#
|
84
|
-
# @example Specify custom pattern
|
85
|
-
# require 'hanami/router'
|
86
|
-
#
|
87
|
-
# resolver = Hanami::Routing::EndpointResolver.new(pattern: '%{controller}Controller::%{action}')
|
88
|
-
# router = Hanami::Router.new(resolver: resolver)
|
89
|
-
#
|
90
|
-
# router.get('/', to: 'articles#show')
|
91
|
-
# # => Will look for: ArticlesController::Show
|
92
|
-
#
|
93
|
-
#
|
94
|
-
#
|
95
|
-
# @example Specify custom controller-action separator
|
96
|
-
# require 'hanami/router'
|
97
|
-
#
|
98
|
-
# resolver = Hanami::Routing::EndpointResolver.new(separator: '@')
|
99
|
-
# router = Hanami::Router.new(resolver: resolver)
|
100
|
-
#
|
101
|
-
# router.get('/', to: 'articles@show')
|
102
|
-
# # => Will look for: Articles::Show
|
103
|
-
def initialize(options = {})
|
104
|
-
@endpoint_class = options[:endpoint] || Endpoint
|
105
|
-
@namespace = options[:namespace] || Object
|
106
|
-
@action_separator = options[:action_separator] || ACTION_SEPARATOR
|
107
|
-
@pattern = options[:pattern] || NAMING_PATTERN
|
108
|
-
end
|
109
|
-
|
110
|
-
# Resolve the given set of HTTP verb, path, endpoint and options.
|
111
|
-
# If it fails to resolve, it will mount the default endpoint to the given
|
112
|
-
# path, which returns an 404 (Not Found).
|
113
|
-
#
|
114
|
-
# @param options [Hash] the options required to resolve the endpoint
|
115
|
-
#
|
116
|
-
# @option options [String,Proc,Class,Object#call] :to the endpoint
|
117
|
-
# @option options [String] :namespace an optional routing namespace
|
118
|
-
#
|
119
|
-
# @return [Endpoint] this may vary according to the :endpoint option
|
120
|
-
# passed to #initialize
|
121
|
-
#
|
122
|
-
# @since 0.1.0
|
123
|
-
# @api private
|
124
|
-
#
|
125
|
-
# @see #initialize
|
126
|
-
# @see #find
|
127
|
-
#
|
128
|
-
# @example Resolve to a Proc
|
129
|
-
# require 'hanami/router'
|
130
|
-
#
|
131
|
-
# router = Hanami::Router.new
|
132
|
-
# router.get '/', to: ->(env) { [200, {}, ['Hi!']] }
|
133
|
-
#
|
134
|
-
# @example Resolve to a class
|
135
|
-
# require 'hanami/router'
|
136
|
-
#
|
137
|
-
# router = Hanami::Router.new
|
138
|
-
# router.get '/', to: RackMiddleware
|
139
|
-
#
|
140
|
-
# @example Resolve to a Rack compatible object (respond to #call)
|
141
|
-
# require 'hanami/router'
|
142
|
-
#
|
143
|
-
# router = Hanami::Router.new
|
144
|
-
# router.get '/', to: AnotherMiddleware.new
|
145
|
-
#
|
146
|
-
# @example Resolve to a Hanami::Action from a string (see Hanami::Controller framework)
|
147
|
-
# require 'hanami/router'
|
148
|
-
#
|
149
|
-
# router = Hanami::Router.new
|
150
|
-
# router.get '/', to: 'articles#show'
|
151
|
-
#
|
152
|
-
# @example Resolve to a Hanami::Action (see Hanami::Controller framework)
|
153
|
-
# require 'hanami/router'
|
154
|
-
#
|
155
|
-
# router = Hanami::Router.new
|
156
|
-
# router.get '/', to: Articles::Show
|
157
|
-
#
|
158
|
-
# @example Resolve a redirect with a namespace
|
159
|
-
# require 'hanami/router'
|
160
|
-
#
|
161
|
-
# router = Hanami::Router.new
|
162
|
-
# router.namespace 'users' do
|
163
|
-
# get '/home', to: ->(env) { ... }
|
164
|
-
# redirect '/dashboard', to: '/home'
|
165
|
-
# end
|
166
|
-
#
|
167
|
-
# # GET /users/dashboard => 301 Location: "/users/home"
|
168
|
-
def resolve(options, &endpoint)
|
169
|
-
result = endpoint || find(options)
|
170
|
-
resolve_callable(result) || resolve_matchable(result) || default
|
171
|
-
end
|
172
|
-
|
173
|
-
# Finds a path from the given options.
|
174
|
-
#
|
175
|
-
# @param options [Hash] the path description
|
176
|
-
# @option options [String,Proc,Class,Object#call] :to the endpoint
|
177
|
-
# @option options [String] :namespace an optional namespace
|
178
|
-
#
|
179
|
-
# @since 0.1.0
|
180
|
-
# @api private
|
181
|
-
#
|
182
|
-
# @return [Object]
|
183
|
-
def find(options)
|
184
|
-
options[:to]
|
185
|
-
end
|
186
|
-
|
187
|
-
protected
|
188
|
-
|
189
|
-
# @api private
|
190
|
-
def default
|
191
|
-
@endpoint_class.new(
|
192
|
-
->(_env) { DEFAULT_RESPONSE }
|
193
|
-
)
|
194
|
-
end
|
195
|
-
|
196
|
-
# @api private
|
197
|
-
def constantize(string)
|
198
|
-
klass = Utils::Class.load!(string, @namespace)
|
199
|
-
if klass.respond_to?(:call)
|
200
|
-
Endpoint.new(klass)
|
201
|
-
else
|
202
|
-
ClassEndpoint.new(klass)
|
203
|
-
end
|
204
|
-
rescue NameError
|
205
|
-
LazyEndpoint.new(string, @namespace)
|
206
|
-
end
|
207
|
-
|
208
|
-
# @api private
|
209
|
-
def classify(string)
|
210
|
-
Utils::String.transform(string, :underscore, :classify)
|
211
|
-
end
|
212
|
-
|
213
|
-
private
|
214
|
-
|
215
|
-
# @api private
|
216
|
-
def resolve_callable(callable)
|
217
|
-
if callable.respond_to?(:call)
|
218
|
-
@endpoint_class.new(callable)
|
219
|
-
elsif callable.is_a?(Class) && callable.instance_methods.include?(:call)
|
220
|
-
@endpoint_class.new(callable.new)
|
221
|
-
end
|
222
|
-
end
|
223
|
-
|
224
|
-
# @api private
|
225
|
-
def resolve_matchable(matchable)
|
226
|
-
return unless matchable.respond_to?(:match)
|
227
|
-
|
228
|
-
constantize(
|
229
|
-
resolve_action(matchable) || classify(matchable)
|
230
|
-
)
|
231
|
-
end
|
232
|
-
|
233
|
-
# @api private
|
234
|
-
def resolve_action(string)
|
235
|
-
return unless string.match?(action_separator)
|
236
|
-
|
237
|
-
controller, action = string.split(action_separator).map { |token| classify(token) }
|
238
|
-
format(@pattern, controller: controller, action: action)
|
239
|
-
end
|
240
|
-
end
|
241
|
-
end
|
242
|
-
end
|