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