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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -0
- data/README.md +11 -401
- data/hanami-router.gemspec +2 -4
- data/lib/hanami/middleware/body_parser.rb +2 -2
- data/lib/hanami/middleware/body_parser/class_interface.rb +10 -4
- data/lib/hanami/middleware/body_parser/json_parser.rb +4 -4
- data/lib/hanami/router.rb +525 -1040
- data/lib/hanami/router/block.rb +88 -0
- data/lib/hanami/router/error.rb +67 -0
- data/lib/hanami/router/node.rb +93 -0
- data/lib/hanami/router/params.rb +35 -0
- data/lib/hanami/router/prefix.rb +65 -0
- data/lib/hanami/router/recognized_route.rb +92 -0
- data/lib/hanami/router/redirect.rb +28 -0
- data/lib/hanami/router/segment.rb +19 -0
- data/lib/hanami/router/trie.rb +63 -0
- data/lib/hanami/router/url_helpers.rb +40 -0
- data/lib/hanami/router/version.rb +2 -1
- metadata +17 -48
- data/lib/hanami/routing.rb +0 -193
- data/lib/hanami/routing/endpoint.rb +0 -213
- data/lib/hanami/routing/endpoint_resolver.rb +0 -242
- data/lib/hanami/routing/prefix.rb +0 -102
- data/lib/hanami/routing/recognized_route.rb +0 -233
- data/lib/hanami/routing/resource.rb +0 -121
- data/lib/hanami/routing/resource/action.rb +0 -427
- data/lib/hanami/routing/resource/nested.rb +0 -44
- data/lib/hanami/routing/resource/options.rb +0 -76
- data/lib/hanami/routing/resources.rb +0 -50
- data/lib/hanami/routing/resources/action.rb +0 -161
- data/lib/hanami/routing/routes_inspector.rb +0 -223
- data/lib/hanami/routing/scope.rb +0 -112
data/hanami-router.gemspec
CHANGED
@@ -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", "~>
|
28
|
+
spec.add_development_dependency "rake", "~> 13"
|
31
29
|
spec.add_development_dependency "rack-test", "~> 1.0"
|
32
|
-
spec.add_development_dependency "rspec", "~> 3.
|
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/
|
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
|
-
|
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
|
-
|
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 "
|
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
|
-
|
30
|
-
rescue
|
31
|
-
raise BodyParsingError.new(
|
29
|
+
JSON.parse(body)
|
30
|
+
rescue StandardError => exception
|
31
|
+
raise BodyParsingError.new(exception.message)
|
32
32
|
end
|
33
33
|
end
|
34
34
|
end
|
data/lib/hanami/router.rb
CHANGED
@@ -1,136 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "rack/
|
4
|
-
|
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
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
#
|
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
|
-
|
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
|
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
|
152
|
-
#
|
153
|
-
# @
|
154
|
-
#
|
155
|
-
# @
|
156
|
-
# @
|
157
|
-
#
|
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
|
-
# @
|
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
|
-
#
|
216
|
-
#
|
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
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
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
|
-
#
|
77
|
+
# Resolve the given Rack env to a registered endpoint and invokes it.
|
270
78
|
#
|
271
|
-
#
|
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 [
|
81
|
+
# @return [Array] a finalized Rack env response
|
276
82
|
#
|
277
|
-
# @since 0.
|
278
|
-
|
279
|
-
|
280
|
-
self
|
281
|
-
end
|
83
|
+
# @since 0.1.0
|
84
|
+
def call(env)
|
85
|
+
endpoint, params = lookup(env)
|
282
86
|
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
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
|
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
|
-
# @
|
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
|
-
#
|
104
|
+
# @see #get
|
105
|
+
# @see #path
|
106
|
+
# @see #url
|
379
107
|
#
|
380
|
-
#
|
381
|
-
#
|
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
|
-
#
|
111
|
+
# router = Hanami::Router.new do
|
112
|
+
# root to: ->(env) { [200, {}, ["Hello from Hanami!"]] }
|
113
|
+
# end
|
386
114
|
#
|
387
|
-
# @example
|
388
|
-
# require
|
115
|
+
# @example Block endpoint
|
116
|
+
# require "hanami/router"
|
389
117
|
#
|
390
|
-
#
|
391
|
-
#
|
392
|
-
#
|
118
|
+
# router = Hanami::Router.new do
|
119
|
+
# root do
|
120
|
+
# "Hello from Hanami!"
|
393
121
|
# end
|
394
122
|
# end
|
395
123
|
#
|
396
|
-
#
|
397
|
-
#
|
398
|
-
#
|
399
|
-
# @example Duck typed endpoints (string: controller + action)
|
400
|
-
# require 'hanami/router'
|
124
|
+
# @example URL helpers
|
125
|
+
# require "hanami/router"
|
401
126
|
#
|
402
|
-
#
|
403
|
-
#
|
404
|
-
#
|
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
|
-
#
|
411
|
-
#
|
412
|
-
|
413
|
-
|
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
|
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
|
-
# @
|
424
|
-
# @option options [String,Proc,Class,Object#call] :to the endpoint
|
148
|
+
# @since 0.1.0
|
425
149
|
#
|
426
|
-
# @
|
150
|
+
# @see #initialize
|
151
|
+
# @see #path
|
152
|
+
# @see #url
|
427
153
|
#
|
428
|
-
# @
|
429
|
-
#
|
154
|
+
# @example Proc endpoint
|
155
|
+
# require "hanami/router"
|
430
156
|
#
|
431
|
-
#
|
157
|
+
# Hanami::Router.new do
|
158
|
+
# get "/", to: ->(*) { [200, {}, ["OK"]] }
|
159
|
+
# end
|
432
160
|
#
|
433
|
-
# @
|
434
|
-
|
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
|
-
#
|
164
|
+
# Hanami::Router.new do
|
165
|
+
# get "/" do
|
166
|
+
# "OK"
|
167
|
+
# end
|
168
|
+
# end
|
441
169
|
#
|
442
|
-
# @
|
443
|
-
#
|
170
|
+
# @example Named route
|
171
|
+
# require "hanami/router"
|
444
172
|
#
|
445
|
-
#
|
173
|
+
# router = Hanami::Router.new do
|
174
|
+
# get "/", to: ->(*) { [200, {}, ["OK"]] }, as: :welcome
|
175
|
+
# end
|
446
176
|
#
|
447
|
-
#
|
448
|
-
#
|
177
|
+
# router.path(:welcome) # => "/"
|
178
|
+
# router.url(:welcome) # => "http://localhost/"
|
449
179
|
#
|
450
|
-
# @
|
180
|
+
# @example Constraints
|
181
|
+
# require "hanami/router"
|
451
182
|
#
|
452
|
-
#
|
453
|
-
|
454
|
-
|
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
|
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
|
462
|
-
# @
|
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
|
-
|
473
|
-
|
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
|
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
|
481
|
-
# @
|
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
|
-
|
492
|
-
|
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
|
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
|
500
|
-
# @
|
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
|
-
|
511
|
-
|
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
|
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
|
519
|
-
# @
|
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
|
-
# @
|
524
|
-
# option passed to the constructor
|
525
|
-
#
|
526
|
-
# @see Hanami::Router#get
|
253
|
+
# @since 0.1.0
|
527
254
|
#
|
528
|
-
# @
|
529
|
-
|
530
|
-
|
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
|
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
|
538
|
-
# @
|
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
|
-
# @
|
543
|
-
# option passed to the constructor
|
544
|
-
#
|
545
|
-
# @see Hanami::Router#get
|
271
|
+
# @since 0.1.0
|
546
272
|
#
|
547
|
-
# @
|
548
|
-
|
549
|
-
|
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
|
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
|
557
|
-
# @
|
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
|
-
|
568
|
-
|
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
|
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
|
-
# @
|
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
|
-
#
|
596
|
-
#
|
597
|
-
|
598
|
-
|
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
|
317
|
+
# Defines a route that accepts UNLINK requests for the given path.
|
602
318
|
#
|
603
|
-
# @param path [String] the
|
604
|
-
# @param
|
605
|
-
# @
|
606
|
-
#
|
607
|
-
# @
|
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
|
613
|
-
#
|
614
|
-
# @
|
615
|
-
#
|
616
|
-
|
617
|
-
|
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
|
633
|
-
# with the given relative path.
|
335
|
+
# Defines a route that redirects the incoming request to another path.
|
634
336
|
#
|
635
|
-
#
|
636
|
-
#
|
637
|
-
# @param
|
638
|
-
#
|
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
|
-
# @
|
657
|
-
# require "hanami/router"
|
342
|
+
# @since 0.1.0
|
658
343
|
#
|
659
|
-
#
|
660
|
-
#
|
661
|
-
|
662
|
-
|
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
|
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
|
675
|
-
# @param
|
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
|
-
#
|
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
|
-
#
|
799
|
-
#
|
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
|
-
# #
|
810
|
-
|
811
|
-
|
812
|
-
|
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
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
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
|
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
|
954
|
-
# @
|
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
|
959
|
-
# require
|
399
|
+
# @example
|
400
|
+
# require "hanami/router"
|
960
401
|
#
|
961
402
|
# Hanami::Router.new do
|
962
|
-
# mount
|
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
|
-
#
|
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
|
-
#
|
976
|
-
# get '/rack1', to: RackOne.new
|
977
|
-
# mount RackTwo.new, at: '/rack2'
|
978
|
-
# end
|
417
|
+
# @return [String]
|
979
418
|
#
|
980
|
-
#
|
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
|
-
#
|
999
|
-
# def call(env)
|
1000
|
-
# end
|
1001
|
-
# end
|
422
|
+
# @since 0.1.0
|
1002
423
|
#
|
1003
|
-
#
|
1004
|
-
# def call(env)
|
1005
|
-
# end
|
1006
|
-
# end
|
424
|
+
# @see #url
|
1007
425
|
#
|
1008
|
-
#
|
1009
|
-
#
|
1010
|
-
# def call(env)
|
1011
|
-
# end
|
1012
|
-
# end
|
1013
|
-
# end
|
426
|
+
# @example
|
427
|
+
# require "hanami/router"
|
1014
428
|
#
|
1015
|
-
# Hanami::Router.new do
|
1016
|
-
#
|
1017
|
-
#
|
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
|
-
#
|
1024
|
-
#
|
1025
|
-
#
|
1026
|
-
|
1027
|
-
|
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
|
-
#
|
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
|
445
|
+
# @param name [Symbol] the route name
|
1036
446
|
#
|
1037
|
-
# @return [
|
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
|
-
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
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
|
489
|
+
# require "hanami/router"
|
1071
490
|
#
|
1072
491
|
# router = Hanami::Router.new do
|
1073
|
-
# get
|
492
|
+
# get "/books/:id", to: ->(*) { ... }, as: :book
|
1074
493
|
# end
|
1075
494
|
#
|
1076
|
-
# route = router.recognize(
|
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
|
501
|
+
# require "hanami/router"
|
1083
502
|
#
|
1084
503
|
# router = Hanami::Router.new do
|
1085
|
-
# get
|
504
|
+
# get "/books/:id", to: ->(*) { ... }, as: :book
|
1086
505
|
# end
|
1087
506
|
#
|
1088
|
-
# route = router.recognize(Rack::MockRequest.env_for(
|
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
|
513
|
+
# require "hanami/router"
|
1095
514
|
#
|
1096
515
|
# router = Hanami::Router.new do
|
1097
|
-
# get
|
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
|
525
|
+
# require "hanami/router"
|
1107
526
|
#
|
1108
527
|
# router = Hanami::Router.new do
|
1109
|
-
# get
|
528
|
+
# get "/books/:id", to: ->(*) { ... }, as: :book
|
1110
529
|
# end
|
1111
530
|
#
|
1112
|
-
# route = router.recognize(
|
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
|
536
|
+
# require "hanami/router"
|
1118
537
|
#
|
1119
538
|
# router = Hanami::Router.new do
|
1120
|
-
# get
|
539
|
+
# get "/books/:id", to: ->(*) { ... }, as: :book
|
1121
540
|
# end
|
1122
541
|
#
|
1123
|
-
# route = router.recognize(
|
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
|
547
|
+
# require "hanami/router"
|
1129
548
|
#
|
1130
549
|
# router = Hanami::Router.new do
|
1131
|
-
# get
|
550
|
+
# get "/books/:id", to: ->(*) { ... }, as: :book
|
1132
551
|
# end
|
1133
552
|
#
|
1134
|
-
# route = router.recognize(Rack::MockRequest.env_for(
|
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
|
558
|
+
# require "hanami/router"
|
1140
559
|
#
|
1141
560
|
# router = Hanami::Router.new do
|
1142
|
-
# get
|
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
|
569
|
+
# require "hanami/router"
|
1151
570
|
#
|
1152
571
|
# router = Hanami::Router.new do
|
1153
|
-
# get
|
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,
|
1161
|
-
|
1162
|
-
|
1163
|
-
|
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
|
-
|
584
|
+
RecognizedRoute.new(
|
585
|
+
endpoint, _params(env, params)
|
586
|
+
)
|
1166
587
|
end
|
1167
588
|
|
1168
|
-
#
|
1169
|
-
#
|
1170
|
-
|
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
|
-
#
|
1198
|
-
#
|
1199
|
-
|
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
|
-
#
|
1225
|
-
#
|
1226
|
-
|
1227
|
-
|
1228
|
-
|
1229
|
-
|
1230
|
-
|
1231
|
-
|
1232
|
-
|
1233
|
-
|
1234
|
-
|
1235
|
-
|
1236
|
-
#
|
1237
|
-
#
|
1238
|
-
|
1239
|
-
|
1240
|
-
|
1241
|
-
|
1242
|
-
|
1243
|
-
|
1244
|
-
|
1245
|
-
|
1246
|
-
|
1247
|
-
|
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,
|
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
|
1272
|
-
return env_for(url, options)
|
1273
|
-
rescue Hanami::
|
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
|
-
|
1284
|
-
|
1285
|
-
|
1286
|
-
|
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
|
-
|
1291
|
-
|
1292
|
-
|
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
|
-
|
1302
|
-
|
1303
|
-
|
710
|
+
# @since 2.0.0
|
711
|
+
# @api private
|
712
|
+
EMPTY_RACK_ENV = {}.freeze
|
1304
713
|
|
1305
|
-
|
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
|
-
|
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
|
-
|
1314
|
-
|
1315
|
-
|
1316
|
-
|
1317
|
-
|
1318
|
-
|
1319
|
-
|
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
|
-
|
1323
|
-
|
1324
|
-
end
|
737
|
+
add_named_route(path, as, constraints) if as
|
738
|
+
end
|
1325
739
|
|
1326
|
-
|
1327
|
-
|
1328
|
-
|
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
|
-
|
1331
|
-
|
746
|
+
@resolver.call(path, to)
|
747
|
+
end
|
1332
748
|
|
1333
|
-
|
1334
|
-
|
1335
|
-
|
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
|
-
|
1338
|
-
|
1339
|
-
|
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
|
-
|
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
|
-
|
1346
|
-
|
1347
|
-
|
822
|
+
# @since 2.0.0
|
823
|
+
# @api private
|
824
|
+
def _not_allowed_fixed(env)
|
825
|
+
found = false
|
1348
826
|
|
1349
|
-
|
1350
|
-
|
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
|
-
|
1354
|
-
|
830
|
+
found = routes.key?(env["PATH_INFO"])
|
831
|
+
end
|
832
|
+
|
833
|
+
found
|
1355
834
|
end
|
1356
835
|
|
1357
|
-
|
1358
|
-
|
1359
|
-
|
1360
|
-
|
1361
|
-
|
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
|