hanami-router 2.0.0.alpha1 → 2.0.0.alpha2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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