hanami-router 1.3.2 → 2.0.0.alpha1

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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -3
  3. data/README.md +192 -154
  4. data/hanami-router.gemspec +23 -20
  5. data/lib/hanami/middleware/body_parser.rb +17 -13
  6. data/lib/hanami/middleware/body_parser/class_interface.rb +56 -56
  7. data/lib/hanami/middleware/body_parser/errors.rb +7 -4
  8. data/lib/hanami/middleware/body_parser/json_parser.rb +5 -3
  9. data/lib/hanami/middleware/error.rb +16 -0
  10. data/lib/hanami/router.rb +262 -149
  11. data/lib/hanami/router/version.rb +3 -1
  12. data/lib/hanami/routing.rb +193 -0
  13. data/lib/hanami/routing/endpoint.rb +122 -104
  14. data/lib/hanami/routing/endpoint_resolver.rb +20 -16
  15. data/lib/hanami/routing/prefix.rb +102 -0
  16. data/lib/hanami/routing/recognized_route.rb +40 -26
  17. data/lib/hanami/routing/resource.rb +9 -7
  18. data/lib/hanami/routing/resource/action.rb +58 -33
  19. data/lib/hanami/routing/resource/nested.rb +4 -1
  20. data/lib/hanami/routing/resource/options.rb +3 -1
  21. data/lib/hanami/routing/resources.rb +6 -4
  22. data/lib/hanami/routing/resources/action.rb +11 -6
  23. data/lib/hanami/routing/routes_inspector.rb +22 -20
  24. data/lib/hanami/routing/scope.rb +112 -0
  25. metadata +47 -25
  26. data/lib/hanami-router.rb +0 -1
  27. data/lib/hanami/routing/error.rb +0 -7
  28. data/lib/hanami/routing/force_ssl.rb +0 -212
  29. data/lib/hanami/routing/http_router.rb +0 -220
  30. data/lib/hanami/routing/http_router_monkey_patch.rb +0 -38
  31. data/lib/hanami/routing/namespace.rb +0 -98
  32. data/lib/hanami/routing/parsers.rb +0 -113
  33. data/lib/hanami/routing/parsing/json_parser.rb +0 -33
  34. data/lib/hanami/routing/parsing/parser.rb +0 -61
  35. data/lib/hanami/routing/route.rb +0 -71
@@ -1,6 +1,8 @@
1
- require 'hanami/utils/string'
2
- require 'hanami/utils/class'
3
- require 'hanami/routing/endpoint'
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/utils/string"
4
+ require "hanami/utils/class"
5
+ require "hanami/routing/endpoint"
4
6
 
5
7
  module Hanami
6
8
  module Routing
@@ -12,11 +14,11 @@ module Hanami
12
14
  class EndpointResolver
13
15
  # @since 0.2.0
14
16
  # @api private
15
- NAMING_PATTERN = '%{controller}::%{action}'.freeze
17
+ NAMING_PATTERN = "%<controller>s::%<action>s"
16
18
 
17
19
  # @since 0.7.0
18
20
  # @api private
19
- DEFAULT_RESPONSE = [404, {'X-Cascade' => 'pass'}, 'Not Found'].freeze
21
+ DEFAULT_RESPONSE = [404, { "X-Cascade" => "pass" }, "Not Found"].freeze
20
22
 
21
23
  # Default separator for controller and action.
22
24
  # A different separator can be passed to #initialize with the `:separator` option.
@@ -32,7 +34,7 @@ module Hanami
32
34
  # router = Hanami::Router.new do
33
35
  # get '/', to: 'articles#show'
34
36
  # end
35
- ACTION_SEPARATOR = '#'.freeze
37
+ ACTION_SEPARATOR = "#"
36
38
 
37
39
  attr_reader :action_separator
38
40
 
@@ -183,10 +185,11 @@ module Hanami
183
185
  end
184
186
 
185
187
  protected
188
+
186
189
  # @api private
187
190
  def default
188
191
  @endpoint_class.new(
189
- ->(env) { DEFAULT_RESPONSE }
192
+ ->(_env) { DEFAULT_RESPONSE }
190
193
  )
191
194
  end
192
195
 
@@ -208,6 +211,7 @@ module Hanami
208
211
  end
209
212
 
210
213
  private
214
+
211
215
  # @api private
212
216
  def resolve_callable(callable)
213
217
  if callable.respond_to?(:call)
@@ -219,19 +223,19 @@ module Hanami
219
223
 
220
224
  # @api private
221
225
  def resolve_matchable(matchable)
222
- if matchable.respond_to?(:match)
223
- constantize(
224
- resolve_action(matchable) || classify(matchable)
225
- )
226
- end
226
+ return unless matchable.respond_to?(:match)
227
+
228
+ constantize(
229
+ resolve_action(matchable) || classify(matchable)
230
+ )
227
231
  end
228
232
 
229
233
  # @api private
230
234
  def resolve_action(string)
231
- if string.match(action_separator)
232
- controller, action = string.split(action_separator).map {|token| classify(token) }
233
- @pattern % {controller: controller, action: action}
234
- end
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)
235
239
  end
236
240
  end
237
241
  end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "delegate"
4
+ require "hanami/utils/path_prefix"
5
+
6
+ module Hanami
7
+ module Routing
8
+ # Prefix for routes.
9
+ # Implementation of Hanami::Router#prefix
10
+ #
11
+ # @since 2.0.0
12
+ # @api private
13
+ #
14
+ # @see Hanami::Router#prefix
15
+ class Prefix < SimpleDelegator
16
+ # @api private
17
+ # @since 2.0.0
18
+ def initialize(router, path, namespace, configuration, &blk)
19
+ @router = router
20
+ @namespace = namespace
21
+ @configuration = configuration
22
+ @path = Utils::PathPrefix.new(path)
23
+ __setobj__(@router)
24
+ instance_eval(&blk)
25
+ end
26
+
27
+ # @api private
28
+ # @since 2.0.0
29
+ def get(path, options = {}, &endpoint)
30
+ super(@path.join(path), options.merge(namespace: @namespace, configuration: @configuration), &endpoint)
31
+ end
32
+
33
+ # @api private
34
+ # @since 2.0.0
35
+ def post(path, options = {}, &endpoint)
36
+ super(@path.join(path), options.merge(namespace: @namespace, configuration: @configuration), &endpoint)
37
+ end
38
+
39
+ # @api private
40
+ # @since 2.0.0
41
+ def put(path, options = {}, &endpoint)
42
+ super(@path.join(path), options.merge(namespace: @namespace, configuration: @configuration), &endpoint)
43
+ end
44
+
45
+ # @api private
46
+ # @since 2.0.0
47
+ def patch(path, options = {}, &endpoint)
48
+ super(@path.join(path), options.merge(namespace: @namespace, configuration: @configuration), &endpoint)
49
+ end
50
+
51
+ # @api private
52
+ # @since 2.0.0
53
+ def delete(path, options = {}, &endpoint)
54
+ super(@path.join(path), options.merge(namespace: @namespace, configuration: @configuration), &endpoint)
55
+ end
56
+
57
+ # @api private
58
+ # @since 2.0.0
59
+ def trace(path, options = {}, &endpoint)
60
+ super(@path.join(path), options.merge(namespace: @namespace, configuration: @configuration), &endpoint)
61
+ end
62
+
63
+ # @api private
64
+ # @since 2.0.0
65
+ def options(path, options = {}, &endpoint)
66
+ super(@path.join(path), options.merge(namespace: @namespace, configuration: @configuration), &endpoint)
67
+ end
68
+
69
+ # @api private
70
+ # @since 2.0.0
71
+ def resource(name, options = {})
72
+ super(name, options.merge(prefix: @path.relative_join(options[:prefix]), namespace: @namespace, configuration: @configuration))
73
+ end
74
+
75
+ # @api private
76
+ # @since 2.0.0
77
+ def resources(name, options = {})
78
+ super(name, options.merge(prefix: @path.relative_join(options[:prefix]), namespace: @namespace, configuration: @configuration))
79
+ end
80
+
81
+ # @api private
82
+ # @since 2.0.0
83
+ def redirect(path, options = {}, &endpoint)
84
+ super(@path.join(path), options.merge(to: @path.join(options[:to])), &endpoint)
85
+ end
86
+
87
+ # @api private
88
+ # @since 2.0.0
89
+ def mount(app, options)
90
+ super(app, options.merge(at: @path.join(options[:at])))
91
+ end
92
+
93
+ # Supports nested prefix
94
+ #
95
+ # @api private
96
+ # @since 2.0.0
97
+ def prefix(path, &blk)
98
+ self.class.new(self, path, @namespace, @configuration, &blk)
99
+ end
100
+ end
101
+ end
102
+ end
@@ -1,4 +1,6 @@
1
- require 'hanami/utils/string'
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/utils/string"
2
4
 
3
5
  module Hanami
4
6
  module Routing
@@ -10,23 +12,25 @@ module Hanami
10
12
  class RecognizedRoute
11
13
  # @since 0.5.0
12
14
  # @api private
13
- REQUEST_METHOD = 'REQUEST_METHOD'.freeze
15
+ REQUEST_METHOD = "REQUEST_METHOD"
14
16
 
15
17
  # @since 0.7.0
16
18
  # @api private
17
- PATH_INFO = 'PATH_INFO'.freeze
19
+ PATH_INFO = "PATH_INFO"
18
20
 
19
21
  # @since 0.5.0
20
22
  # @api private
21
- NAMESPACE = '%s::'.freeze
23
+ NAMESPACE = "%s::"
22
24
 
23
25
  # @since 0.5.0
24
26
  # @api private
25
- NAMESPACE_REPLACEMENT = ''.freeze
27
+ NAMESPACE_REPLACEMENT = ""
26
28
 
27
29
  # @since 0.5.0
28
30
  # @api private
29
- ACTION_PATH_SEPARATOR = '/'.freeze
31
+ ACTION_PATH_SEPARATOR = "/"
32
+
33
+ ACTION_SEPARATOR = "#"
30
34
 
31
35
  # @since 0.5.0
32
36
  # @api public
@@ -42,18 +46,20 @@ module Hanami
42
46
  #
43
47
  # @since 0.5.0
44
48
  # @api private
45
- def initialize(response, env, router)
46
- @env = env
47
- @endpoint = nil
48
- @params = {}
49
-
50
- unless response.nil?
51
- @endpoint = response.route.dest
52
- @params = response.params
53
- end
49
+ def initialize(route, env, namespace)
50
+ @env = env
51
+ @endpoint = nil
52
+ @params = {}
53
+ @namespace = namespace
54
+
55
+ return if route.nil?
56
+
57
+ @endpoint = route.instance_variable_get(:@endpoint)
58
+
59
+ return unless routable?
54
60
 
55
- @namespace = router.namespace
56
- @action_separator = router.action_separator
61
+ route.call(@env)
62
+ @params = @env["router.params"]
57
63
  end
58
64
 
59
65
  # Rack protocol compatibility
@@ -70,7 +76,7 @@ module Hanami
70
76
  # @see Hanami::Routing::RecognizedRoute#routable?
71
77
  # @see Hanami::Router::NotRoutableEndpointError
72
78
  def call(env)
73
- if routable?
79
+ if routable? # rubocop:disable Style/GuardClause
74
80
  @endpoint.call(env)
75
81
  else
76
82
  raise Hanami::Router::NotRoutableEndpointError.new(@env)
@@ -116,12 +122,14 @@ module Hanami
116
122
  # puts router.recognize('/books/23').action # => "books#show"
117
123
  def action
118
124
  return if !routable? || redirect?
125
+
119
126
  namespace = NAMESPACE % @namespace
120
127
 
121
128
  if destination.match(namespace)
122
129
  Hanami::Utils::String.transform(
123
130
  destination.sub(namespace, NAMESPACE_REPLACEMENT),
124
- :underscore, [:rsub, ACTION_PATH_SEPARATOR, @action_separator])
131
+ :underscore, [:rsub, ACTION_PATH_SEPARATOR, ACTION_SEPARATOR]
132
+ )
125
133
  else
126
134
  destination
127
135
  end
@@ -146,7 +154,9 @@ module Hanami
146
154
  # puts router.recognize('/').routable? # => true
147
155
  # puts router.recognize('/foo').routable? # => false
148
156
  def routable?
149
- @endpoint&.routable? || false
157
+ return false if @endpoint.nil?
158
+
159
+ @endpoint.respond_to?(:routable?) ? @endpoint.routable? : true
150
160
  end
151
161
 
152
162
  # Check if redirect
@@ -169,7 +179,9 @@ module Hanami
169
179
  # puts router.recognize('/home').redirect? # => true
170
180
  # puts router.recognize('/').redirect? # => false
171
181
  def redirect?
172
- @endpoint&.redirect? || false
182
+ return false if @endpoint.nil?
183
+
184
+ @endpoint.respond_to?(:redirect?) ? @endpoint.redirect? : false
173
185
  end
174
186
 
175
187
  # Returns the redirect destination path
@@ -193,7 +205,9 @@ module Hanami
193
205
  # puts router.recognize('/home').destination_path # => "/"
194
206
  # puts router.recognize('/').destination_path # => nil
195
207
  def redirection_path
196
- @endpoint&.destination_path
208
+ return unless redirect?
209
+
210
+ @endpoint.destination_path if @endpoint.respond_to?(:destination_path)
197
211
  end
198
212
 
199
213
  private
@@ -204,13 +218,13 @@ module Hanami
204
218
  # @see Hanami::Routing::Endpoint
205
219
  def destination
206
220
  @destination ||= begin
207
- case k = @endpoint.__getobj__
208
- when Class
209
- k.name
221
+ case @endpoint
210
222
  when Proc
211
223
  @endpoint.inspect
224
+ when Class
225
+ @endpoint.name
212
226
  else
213
- k.class.name
227
+ @endpoint.class.name
214
228
  end
215
229
  end
216
230
  end
@@ -1,6 +1,8 @@
1
- require 'hanami/utils/class_attribute'
2
- require 'hanami/routing/resource/options'
3
- require 'hanami/routing/resource/action'
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/utils/class_attribute"
4
+ require "hanami/routing/resource/options"
5
+ require "hanami/routing/resource/action"
4
6
 
5
7
  module Hanami
6
8
  module Routing
@@ -17,14 +19,14 @@ module Hanami
17
19
 
18
20
  # @api private
19
21
  # @since 0.4.0
20
- NESTED_ROUTES_SEPARATOR = '/'.freeze
22
+ NESTED_ROUTES_SEPARATOR = "/"
21
23
 
22
24
  # Set of default routes
23
25
  #
24
26
  # @api private
25
27
  # @since 0.1.0
26
28
  class_attribute :actions
27
- self.actions = [:new, :create, :show, :edit, :update, :destroy]
29
+ self.actions = %i[new create show edit update destroy]
28
30
 
29
31
  # Action class
30
32
  #
@@ -83,7 +85,7 @@ module Hanami
83
85
  #
84
86
  # @api private
85
87
  # @since 0.4.0
86
- def wildcard_param(route_param = nil)
88
+ def wildcard_param(_route_param = nil)
87
89
  NESTED_ROUTES_SEPARATOR
88
90
  end
89
91
 
@@ -92,7 +94,7 @@ module Hanami
92
94
  # @api private
93
95
  # @since 0.4.0
94
96
  def _resource(klass, name, options, &blk)
95
- options = options.merge(separator: @options[:separator], namespace: @options[:namespace])
97
+ options = options.merge(separator: @options[:separator], prefix: @options[:prefix])
96
98
  klass.new(@router, [@name, name].join(NESTED_ROUTES_SEPARATOR), options, self, &blk)
97
99
  end
98
100
 
@@ -1,7 +1,9 @@
1
- require 'hanami/utils/string'
2
- require 'hanami/utils/path_prefix'
3
- require 'hanami/utils/class_attribute'
4
- require 'hanami/routing/resource/nested'
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/utils/string"
4
+ require "hanami/utils/path_prefix"
5
+ require "hanami/utils/class_attribute"
6
+ require "hanami/routing/resource/nested"
5
7
 
6
8
  module Hanami
7
9
  module Routing
@@ -20,7 +22,7 @@ module Hanami
20
22
  #
21
23
  # @api private
22
24
  # @since 0.4.0
23
- NESTED_ROUTES_SEPARATOR = '/'.freeze
25
+ NESTED_ROUTES_SEPARATOR = "/"
24
26
 
25
27
  # Ruby namespace where lookup for default subclasses.
26
28
  #
@@ -41,7 +43,7 @@ module Hanami
41
43
  # @api private
42
44
  # @since 0.2.0
43
45
  class_attribute :named_route_separator
44
- self.named_route_separator = '_'.freeze
46
+ self.named_route_separator = "_"
45
47
 
46
48
  # Generate an action for the given router
47
49
  #
@@ -82,7 +84,7 @@ module Hanami
82
84
  #
83
85
  # @since 0.1.0
84
86
  def generate(&blk)
85
- @router.send verb, path, to: endpoint, as: as
87
+ @router.send verb, path, to: endpoint, as: as, namespace: namespace, configuration: configuration
86
88
  instance_eval(&blk) if block_given?
87
89
  end
88
90
 
@@ -105,15 +107,30 @@ module Hanami
105
107
  @resource_name ||= @options[:name].to_s
106
108
  end
107
109
 
110
+ # Prefix
111
+ #
112
+ # @api private
113
+ # @since 2.0.0
114
+ def prefix
115
+ @prefix ||= Utils::PathPrefix.new(@options[:prefix])
116
+ end
117
+
108
118
  # Namespace
109
119
  #
110
120
  # @api private
111
- # @since 0.2.0
121
+ # @since 2.0.0
112
122
  def namespace
113
- @namespace ||= Utils::PathPrefix.new @options[:namespace]
123
+ @namespace ||= @options[:namespace]
124
+ end
125
+
126
+ # Configuration
127
+ #
128
+ # @api private
129
+ # @since 2.0.0
130
+ def configuration
131
+ @configuration ||= @options[:configuration]
114
132
  end
115
133
 
116
- private
117
134
  # Load a subclass, according to the given action name
118
135
  #
119
136
  # @param action [String] the action name
@@ -128,6 +145,8 @@ module Hanami
128
145
  Utils::Class.load!(Utils::String.classify(action), namespace)
129
146
  end
130
147
 
148
+ private_class_method :class_for
149
+
131
150
  # Accepted HTTP verb
132
151
  #
133
152
  # @see Hanami::Routing::Resource::Action.verb
@@ -138,21 +157,21 @@ module Hanami
138
157
  self.class.verb
139
158
  end
140
159
 
141
- # The namespaced URL relative path
160
+ # The prefixed relative URL
142
161
  #
143
162
  # @example
144
- # require 'hanami/router'
163
+ # require "hanami/router"
145
164
  #
146
165
  # Hanami::Router.new do
147
- # resources 'flowers'
166
+ # resources "flowers"
148
167
  #
149
- # namespace 'animals' do
150
- # resources 'mammals'
168
+ # prefix "animals" do
169
+ # resources "mammals"
151
170
  # end
152
171
  # end
153
172
  #
154
- # # It will generate paths like '/flowers', '/flowers/:id' ..
155
- # # It will generate paths like '/animals/mammals', '/animals/mammals/:id' ..
173
+ # # It will generate paths like "/flowers", "/flowers/:id" ..
174
+ # # It will generate paths like "/animals/mammals", "/animals/mammals/:id" ..
156
175
  #
157
176
  # @api private
158
177
  # @since 0.1.0
@@ -170,19 +189,19 @@ module Hanami
170
189
  # @api private
171
190
  # @since 0.1.0
172
191
  def rest_path
173
- namespace.join(_nested_rest_path || resource_name.to_s)
192
+ prefix.join(_nested_rest_path || resource_name.to_s)
174
193
  end
175
194
 
176
- # The namespaced name of the action within the whole context of the router.
195
+ # The prefixed name of the action within the whole context of the router.
177
196
  #
178
197
  # @example
179
- # require 'hanami/router'
198
+ # require "hanami/router"
180
199
  #
181
200
  # Hanami::Router.new do
182
- # resources 'flowers'
201
+ # resources "flowers"
183
202
  #
184
- # namespace 'animals' do
185
- # resources 'mammals'
203
+ # prefix "animals" do
204
+ # resources "mammals"
186
205
  # end
187
206
  # end
188
207
  #
@@ -192,7 +211,7 @@ module Hanami
192
211
  # @api private
193
212
  # @since 0.1.0
194
213
  def as
195
- namespace.relative_join(_singularized_as, self.class.named_route_separator).to_sym
214
+ prefix.relative_join(_singularized_as, self.class.named_route_separator).to_sym
196
215
  end
197
216
 
198
217
  # The name of the RESTful action.
@@ -219,7 +238,7 @@ module Hanami
219
238
  # @api private
220
239
  # @since 0.1.0
221
240
  def endpoint
222
- [ controller_name, action_name ].join separator
241
+ [controller_name, action_name].join separator
223
242
  end
224
243
 
225
244
  # Separator between controller and action name
@@ -260,7 +279,7 @@ module Hanami
260
279
  def _singularized_as
261
280
  name = @options[:as] ? @options[:as].to_s : resource_name
262
281
  name.split(NESTED_ROUTES_SEPARATOR).map do |n|
263
- Hanami::Utils::String.singularize(n)
282
+ @router.inflector.singularize(n)
264
283
  end
265
284
  end
266
285
 
@@ -286,18 +305,23 @@ module Hanami
286
305
  instance_eval(&blk) if block_given?
287
306
  end
288
307
 
289
- protected
290
308
  # @since 0.1.0
291
309
  # @api private
292
- def method_missing(m, *args)
293
- verb = m
310
+ def method_missing(method_name, *args) # rubocop:disable Style/MethodMissingSuper
311
+ verb = method_name
294
312
  action_name = Utils::PathPrefix.new(args.first).relative_join(nil)
295
313
 
296
314
  @router.__send__ verb, path(action_name),
297
- to: endpoint(action_name), as: as(action_name)
315
+ to: endpoint(action_name), as: as(action_name),
316
+ namespace: namespace, configuration: configuration
317
+ end
318
+
319
+ def respond_to_missing?(method_name, include_all)
320
+ @router.respond_to?(method_name, include_all)
298
321
  end
299
322
 
300
323
  private
324
+
301
325
  # @since 0.1.0
302
326
  # @api private
303
327
  def path(action_name)
@@ -307,13 +331,13 @@ module Hanami
307
331
  # @since 0.1.0
308
332
  # @api private
309
333
  def endpoint(action_name)
310
- [ controller_name, action_name ].join separator
334
+ [controller_name, action_name].join separator
311
335
  end
312
336
 
313
337
  # @since 0.1.0
314
338
  # @api private
315
339
  def as(action_name)
316
- [ action_name, super() ].join(self.class.named_route_separator).to_sym
340
+ [action_name, super()].join(self.class.named_route_separator).to_sym
317
341
  end
318
342
  end
319
343
 
@@ -332,6 +356,7 @@ module Hanami
332
356
  # @since 0.1.0
333
357
  module DefaultMemberAction
334
358
  private
359
+
335
360
  # @since 0.1.0
336
361
  # @api private
337
362
  def path
@@ -341,7 +366,7 @@ module Hanami
341
366
  # @since 0.1.0
342
367
  # @api private
343
368
  def as
344
- [ action_name, super ].join(self.class.named_route_separator).to_sym
369
+ [action_name, super].join(self.class.named_route_separator).to_sym
345
370
  end
346
371
  end
347
372