hanami-router 1.3.2 → 2.0.0.alpha1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -3
- data/README.md +192 -154
- data/hanami-router.gemspec +23 -20
- data/lib/hanami/middleware/body_parser.rb +17 -13
- data/lib/hanami/middleware/body_parser/class_interface.rb +56 -56
- data/lib/hanami/middleware/body_parser/errors.rb +7 -4
- data/lib/hanami/middleware/body_parser/json_parser.rb +5 -3
- data/lib/hanami/middleware/error.rb +16 -0
- data/lib/hanami/router.rb +262 -149
- data/lib/hanami/router/version.rb +3 -1
- data/lib/hanami/routing.rb +193 -0
- data/lib/hanami/routing/endpoint.rb +122 -104
- data/lib/hanami/routing/endpoint_resolver.rb +20 -16
- data/lib/hanami/routing/prefix.rb +102 -0
- data/lib/hanami/routing/recognized_route.rb +40 -26
- data/lib/hanami/routing/resource.rb +9 -7
- data/lib/hanami/routing/resource/action.rb +58 -33
- data/lib/hanami/routing/resource/nested.rb +4 -1
- data/lib/hanami/routing/resource/options.rb +3 -1
- data/lib/hanami/routing/resources.rb +6 -4
- data/lib/hanami/routing/resources/action.rb +11 -6
- data/lib/hanami/routing/routes_inspector.rb +22 -20
- data/lib/hanami/routing/scope.rb +112 -0
- metadata +47 -25
- data/lib/hanami-router.rb +0 -1
- data/lib/hanami/routing/error.rb +0 -7
- data/lib/hanami/routing/force_ssl.rb +0 -212
- data/lib/hanami/routing/http_router.rb +0 -220
- data/lib/hanami/routing/http_router_monkey_patch.rb +0 -38
- data/lib/hanami/routing/namespace.rb +0 -98
- data/lib/hanami/routing/parsers.rb +0 -113
- data/lib/hanami/routing/parsing/json_parser.rb +0 -33
- data/lib/hanami/routing/parsing/parser.rb +0 -61
- data/lib/hanami/routing/route.rb +0 -71
data/hanami-router.gemspec
CHANGED
@@ -1,30 +1,33 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require
|
5
|
+
require "hanami/router/version"
|
5
6
|
|
6
7
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name =
|
8
|
+
spec.name = "hanami-router"
|
8
9
|
spec.version = Hanami::Router::VERSION
|
9
|
-
spec.authors = [
|
10
|
-
spec.email = [
|
11
|
-
spec.description =
|
12
|
-
spec.summary =
|
13
|
-
spec.homepage =
|
14
|
-
spec.license =
|
10
|
+
spec.authors = ["Luca Guidi"]
|
11
|
+
spec.email = ["me@lucaguidi.com"]
|
12
|
+
spec.description = "Rack compatible HTTP router for Ruby"
|
13
|
+
spec.summary = "Rack compatible HTTP router for Ruby and Hanami"
|
14
|
+
spec.homepage = "http://hanamirb.org"
|
15
|
+
spec.license = "MIT"
|
15
16
|
|
16
|
-
spec.files = `git ls-files -- lib/* CHANGELOG.md LICENSE.md README.md hanami-router.gemspec`.split(
|
17
|
+
spec.files = `git ls-files -- lib/* CHANGELOG.md LICENSE.md README.md hanami-router.gemspec`.split($INPUT_RECORD_SEPARATOR)
|
17
18
|
spec.executables = []
|
18
19
|
spec.test_files = spec.files.grep(%r{^(test)/})
|
19
|
-
spec.require_paths = [
|
20
|
-
spec.required_ruby_version =
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
spec.required_ruby_version = ">= 2.5.0"
|
21
22
|
|
22
|
-
spec.add_dependency
|
23
|
-
spec.add_dependency
|
24
|
-
spec.add_dependency
|
23
|
+
spec.add_dependency "rack", "~> 2.0"
|
24
|
+
spec.add_dependency "mustermann", "~> 1.0"
|
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"
|
25
28
|
|
26
|
-
spec.add_development_dependency
|
27
|
-
spec.add_development_dependency
|
28
|
-
spec.add_development_dependency
|
29
|
-
spec.add_development_dependency
|
29
|
+
spec.add_development_dependency "bundler", ">= 1.6", "< 3"
|
30
|
+
spec.add_development_dependency "rake", "~> 12"
|
31
|
+
spec.add_development_dependency "rack-test", "~> 1.0"
|
32
|
+
spec.add_development_dependency "rspec", "~> 3.7"
|
30
33
|
end
|
@@ -1,5 +1,8 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "hanami/utils/hash"
|
4
|
+
require "hanami/middleware/error"
|
5
|
+
require_relative "body_parser/class_interface"
|
3
6
|
|
4
7
|
module Hanami
|
5
8
|
module Middleware
|
@@ -8,7 +11,7 @@ module Hanami
|
|
8
11
|
class BodyParser
|
9
12
|
# @since 1.3.0
|
10
13
|
# @api private
|
11
|
-
CONTENT_TYPE =
|
14
|
+
CONTENT_TYPE = "CONTENT_TYPE"
|
12
15
|
|
13
16
|
# @since 1.3.0
|
14
17
|
# @api private
|
@@ -16,17 +19,17 @@ module Hanami
|
|
16
19
|
|
17
20
|
# @since 1.3.0
|
18
21
|
# @api private
|
19
|
-
RACK_INPUT =
|
22
|
+
RACK_INPUT = "rack.input"
|
20
23
|
|
21
24
|
# @since 1.3.0
|
22
25
|
# @api private
|
23
|
-
ROUTER_PARAMS =
|
26
|
+
ROUTER_PARAMS = "router.params"
|
24
27
|
|
25
28
|
# @api private
|
26
|
-
ROUTER_PARSED_BODY =
|
29
|
+
ROUTER_PARSED_BODY = "router.parsed_body"
|
27
30
|
|
28
31
|
# @api private
|
29
|
-
FALLBACK_KEY =
|
32
|
+
FALLBACK_KEY = "_"
|
30
33
|
|
31
34
|
extend ClassInterface
|
32
35
|
|
@@ -55,18 +58,18 @@ module Hanami
|
|
55
58
|
parser_names = Array(parser_names)
|
56
59
|
return {} if parser_names.empty?
|
57
60
|
|
58
|
-
parser_names.each_with_object({})
|
61
|
+
parser_names.each_with_object({}) do |name, parsers|
|
59
62
|
parser = self.class.for(name)
|
60
63
|
|
61
64
|
parser.mime_types.each do |mime|
|
62
65
|
parsers[mime] = parser
|
63
66
|
end
|
64
|
-
|
67
|
+
end
|
65
68
|
end
|
66
69
|
|
67
70
|
# @api private
|
68
71
|
def _symbolize(body)
|
69
|
-
if body.is_a?(Hash)
|
72
|
+
if body.is_a?(::Hash)
|
70
73
|
Utils::Hash.deep_symbolize(body)
|
71
74
|
else
|
72
75
|
{ FALLBACK_KEY => body }
|
@@ -82,9 +85,10 @@ module Hanami
|
|
82
85
|
|
83
86
|
# @api private
|
84
87
|
def media_type(env)
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
+
ct = content_type(env)
|
89
|
+
return unless ct
|
90
|
+
|
91
|
+
ct.split(MEDIA_TYPE_MATCHER, 2).first.downcase
|
88
92
|
end
|
89
93
|
|
90
94
|
# @api private
|
@@ -1,56 +1,56 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "hanami/utils/class"
|
4
|
+
require "hanami/utils/string"
|
5
|
+
require_relative "errors"
|
6
|
+
|
7
|
+
module Hanami
|
8
|
+
module Middleware
|
9
|
+
class BodyParser
|
10
|
+
# @api private
|
11
|
+
# @since 1.3.0
|
12
|
+
module ClassInterface
|
13
|
+
# @api private
|
14
|
+
# @since 1.3.0
|
15
|
+
def for(parser) # rubocop:disable Metrics/MethodLength
|
16
|
+
parser =
|
17
|
+
case parser
|
18
|
+
when String, Symbol
|
19
|
+
require_parser(parser)
|
20
|
+
when Class
|
21
|
+
parser.new
|
22
|
+
else
|
23
|
+
parser
|
24
|
+
end
|
25
|
+
|
26
|
+
ensure_parser parser
|
27
|
+
|
28
|
+
parser
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# @api private
|
34
|
+
# @since 1.3.0
|
35
|
+
PARSER_METHODS = %i[mime_types parse].freeze
|
36
|
+
|
37
|
+
# @api private
|
38
|
+
# @since 1.3.0
|
39
|
+
def ensure_parser(parser)
|
40
|
+
raise InvalidParserError.new(parser) unless PARSER_METHODS.all? { |method| parser.respond_to?(method) }
|
41
|
+
end
|
42
|
+
|
43
|
+
# @api private
|
44
|
+
# @since 1.3.0
|
45
|
+
def require_parser(parser)
|
46
|
+
require "hanami/middleware/body_parser/#{parser}_parser"
|
47
|
+
|
48
|
+
parser = Utils::String.classify(parser)
|
49
|
+
Utils::Class.load!("Hanami::Middleware::BodyParser::#{parser}Parser").new
|
50
|
+
rescue LoadError, NameError
|
51
|
+
raise UnknownParserError.new(parser)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Hanami
|
4
4
|
module Middleware
|
@@ -9,14 +9,17 @@ module Hanami
|
|
9
9
|
# This is raised when parser fails to parse the body
|
10
10
|
#
|
11
11
|
# @since 1.3.0
|
12
|
-
class BodyParsingError < Hanami::
|
12
|
+
class BodyParsingError < Hanami::Middleware::Error
|
13
13
|
end
|
14
14
|
|
15
15
|
# @since 1.3.0
|
16
|
-
class UnknownParserError < Hanami::
|
16
|
+
class UnknownParserError < Hanami::Middleware::Error
|
17
|
+
def initialize(name)
|
18
|
+
super("Unknown body parser: `#{name.inspect}'")
|
19
|
+
end
|
17
20
|
end
|
18
21
|
|
19
|
-
class InvalidParserError < Hanami::
|
22
|
+
class InvalidParserError < Hanami::Middleware::Error
|
20
23
|
end
|
21
24
|
end
|
22
25
|
end
|
@@ -1,5 +1,7 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "hanami/utils/json"
|
4
|
+
require_relative "errors"
|
3
5
|
|
4
6
|
module Hanami
|
5
7
|
module Middleware
|
@@ -10,7 +12,7 @@ module Hanami
|
|
10
12
|
# @since 1.3.0
|
11
13
|
# @api private
|
12
14
|
def mime_types
|
13
|
-
[
|
15
|
+
["application/json", "application/vnd.api+json"]
|
14
16
|
end
|
15
17
|
|
16
18
|
# Parse a json string
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hanami
|
4
|
+
# Hanami Rack middleware
|
5
|
+
#
|
6
|
+
# @since 1.3.0
|
7
|
+
module Middleware
|
8
|
+
unless defined?(Error)
|
9
|
+
# Base error for Rack middleware
|
10
|
+
#
|
11
|
+
# @since 2.0.0
|
12
|
+
class Error < ::StandardError
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/hanami/router.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rack/request"
|
4
|
+
require "dry/inflector"
|
5
|
+
require "hanami/routing"
|
6
|
+
require "hanami/utils/hash"
|
7
7
|
|
8
8
|
# Hanami
|
9
9
|
#
|
@@ -75,7 +75,12 @@ module Hanami
|
|
75
75
|
# end
|
76
76
|
#
|
77
77
|
# # All the requests starting with "/api" will be forwarded to Api::App
|
78
|
-
|
78
|
+
#
|
79
|
+
class Router # rubocop:disable Metrics/ClassLength
|
80
|
+
# @since 2.0.0
|
81
|
+
# @api private
|
82
|
+
attr_reader :inflector
|
83
|
+
|
79
84
|
# This error is raised when <tt>#call</tt> is invoked on a non-routable
|
80
85
|
# recognized route.
|
81
86
|
#
|
@@ -88,15 +93,15 @@ module Hanami
|
|
88
93
|
class NotRoutableEndpointError < Hanami::Routing::Error
|
89
94
|
# @since 0.5.0
|
90
95
|
# @api private
|
91
|
-
REQUEST_METHOD =
|
96
|
+
REQUEST_METHOD = "REQUEST_METHOD"
|
92
97
|
|
93
98
|
# @since 0.5.0
|
94
99
|
# @api private
|
95
|
-
PATH_INFO =
|
100
|
+
PATH_INFO = "PATH_INFO"
|
96
101
|
|
97
102
|
# @since 0.5.0
|
98
103
|
def initialize(env)
|
99
|
-
super %(Cannot find routable endpoint for #{
|
104
|
+
super %(Cannot find routable endpoint for #{env[REQUEST_METHOD]} "#{env[PATH_INFO]}")
|
100
105
|
end
|
101
106
|
end
|
102
107
|
|
@@ -106,7 +111,7 @@ module Hanami
|
|
106
111
|
# @api private
|
107
112
|
#
|
108
113
|
# @see Hanami::Router#root
|
109
|
-
ROOT_PATH =
|
114
|
+
ROOT_PATH = "/"
|
110
115
|
|
111
116
|
# Returns the given block as it is.
|
112
117
|
#
|
@@ -154,8 +159,8 @@ module Hanami
|
|
154
159
|
# (defaults to `Hanami::Routing::Route`)
|
155
160
|
# @option options [String] :action_separator the separator between controller
|
156
161
|
# and action name (eg. 'dashboard#show', where '#' is the :action_separator)
|
157
|
-
# @option options [
|
158
|
-
# the
|
162
|
+
# @option options [Object, #pluralize, #singularize] :inflector
|
163
|
+
# the inflector class (defaults to `Dry::Inflector.new`)
|
159
164
|
#
|
160
165
|
# @param blk [Proc] the optional block to define the routes
|
161
166
|
#
|
@@ -178,51 +183,53 @@ module Hanami
|
|
178
183
|
# end
|
179
184
|
#
|
180
185
|
# @example Body parsers
|
181
|
-
# require 'json'
|
182
|
-
# require 'hanami/router'
|
183
186
|
#
|
184
|
-
#
|
187
|
+
# require 'hanami/router'
|
188
|
+
# require 'hanami/middleware/body_parser'
|
185
189
|
#
|
186
|
-
#
|
190
|
+
# app = Hanami::Router.new do
|
191
|
+
# patch '/books/:id', to: ->(env) { [200, {},[env['router.params'].inspect]] }
|
192
|
+
# end
|
187
193
|
#
|
188
|
-
#
|
189
|
-
#
|
190
|
-
# end
|
194
|
+
# use Hanami::Middleware::BodyParser, :json
|
195
|
+
# run app
|
191
196
|
#
|
192
|
-
#
|
197
|
+
# # From the shell
|
193
198
|
#
|
194
|
-
#
|
195
|
-
#
|
196
|
-
#
|
197
|
-
#
|
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
|
199
204
|
#
|
200
205
|
# # It returns
|
201
206
|
#
|
202
207
|
# [200, {}, ["{:published=>\"true\",:id=>\"1\"}"]]
|
203
208
|
#
|
204
209
|
# @example Custom body parser
|
205
|
-
# require 'hanami/router'
|
206
210
|
#
|
207
|
-
#
|
208
|
-
#
|
209
|
-
# ['application/xml', 'text/xml']
|
210
|
-
# end
|
211
|
+
# require 'hanami/router'
|
212
|
+
# require 'hanami/middleware/body_parser'
|
211
213
|
#
|
212
|
-
# # Parse body and return a Hash
|
213
|
-
# def parse(body)
|
214
|
-
# # ...
|
215
|
-
# end
|
216
|
-
# end
|
217
214
|
#
|
218
|
-
#
|
215
|
+
# class XmlParser < Hanami::Middleware::BodyParser::Parser
|
216
|
+
# def mime_types
|
217
|
+
# ['application/xml', 'text/xml']
|
218
|
+
# end
|
219
219
|
#
|
220
|
-
#
|
220
|
+
# # Parse body and return a Hash
|
221
|
+
# def parse(body)
|
222
|
+
# # parse xml
|
223
|
+
# end
|
224
|
+
# end
|
221
225
|
#
|
222
|
-
#
|
223
|
-
# patch '/authors/:id', to:
|
226
|
+
# app = Hanami::Router.new do
|
227
|
+
# patch '/authors/:id', to: ->(env) { [200, {},[env['router.params'].inspect]] }
|
224
228
|
# end
|
225
229
|
#
|
230
|
+
# use Hanami::Middleware::BodyParser, XmlParser
|
231
|
+
# run app
|
232
|
+
#
|
226
233
|
# # From the shell
|
227
234
|
#
|
228
235
|
# curl http://localhost:2300/authors/1 \
|
@@ -234,9 +241,29 @@ module Hanami
|
|
234
241
|
# # It returns
|
235
242
|
#
|
236
243
|
# [200, {}, ["{:name=>\"LG\",:id=>\"1\"}"]]
|
237
|
-
|
238
|
-
|
239
|
-
|
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
|
240
267
|
end
|
241
268
|
|
242
269
|
# Returns self
|
@@ -253,30 +280,6 @@ module Hanami
|
|
253
280
|
self
|
254
281
|
end
|
255
282
|
|
256
|
-
# To support defining routes in the `define` wrapper.
|
257
|
-
#
|
258
|
-
# @param blk [Proc] the block to define the routes
|
259
|
-
#
|
260
|
-
# @return [Hanami::Routing::Route]
|
261
|
-
#
|
262
|
-
# @since 0.2.0
|
263
|
-
#
|
264
|
-
# @example In Hanami framework
|
265
|
-
# class Application < Hanami::Application
|
266
|
-
# configure do
|
267
|
-
# routes 'config/routes'
|
268
|
-
# end
|
269
|
-
# end
|
270
|
-
#
|
271
|
-
# # In `config/routes`
|
272
|
-
#
|
273
|
-
# define do
|
274
|
-
# get # ...
|
275
|
-
# end
|
276
|
-
def define(&blk)
|
277
|
-
instance_eval(&blk) if block_given?
|
278
|
-
end
|
279
|
-
|
280
283
|
# Check if there are defined routes
|
281
284
|
#
|
282
285
|
# @return [TrueClass,FalseClass] the result of the check
|
@@ -292,7 +295,7 @@ module Hanami
|
|
292
295
|
# router = Hanami::Router.new { get '/', to: ->(env) { } }
|
293
296
|
# router.defined? # => true
|
294
297
|
def defined?
|
295
|
-
@
|
298
|
+
@routes.any?
|
296
299
|
end
|
297
300
|
|
298
301
|
# Defines a route that accepts a GET request for the given path.
|
@@ -409,8 +412,8 @@ module Hanami
|
|
409
412
|
#
|
410
413
|
# # It will map to Flowers::Index.new, which is the
|
411
414
|
# # Hanami::Controller convention.
|
412
|
-
def get(path,
|
413
|
-
|
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)
|
414
417
|
end
|
415
418
|
|
416
419
|
# Defines a route that accepts a POST request for the given path.
|
@@ -428,8 +431,8 @@ module Hanami
|
|
428
431
|
# @see Hanami::Router#get
|
429
432
|
#
|
430
433
|
# @since 0.1.0
|
431
|
-
def post(path,
|
432
|
-
|
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)
|
433
436
|
end
|
434
437
|
|
435
438
|
# Defines a route that accepts a PUT request for the given path.
|
@@ -447,8 +450,8 @@ module Hanami
|
|
447
450
|
# @see Hanami::Router#get
|
448
451
|
#
|
449
452
|
# @since 0.1.0
|
450
|
-
def put(path,
|
451
|
-
|
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)
|
452
455
|
end
|
453
456
|
|
454
457
|
# Defines a route that accepts a PATCH request for the given path.
|
@@ -466,8 +469,8 @@ module Hanami
|
|
466
469
|
# @see Hanami::Router#get
|
467
470
|
#
|
468
471
|
# @since 0.1.0
|
469
|
-
def patch(path,
|
470
|
-
|
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)
|
471
474
|
end
|
472
475
|
|
473
476
|
# Defines a route that accepts a DELETE request for the given path.
|
@@ -485,8 +488,8 @@ module Hanami
|
|
485
488
|
# @see Hanami::Router#get
|
486
489
|
#
|
487
490
|
# @since 0.1.0
|
488
|
-
def delete(path,
|
489
|
-
|
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)
|
490
493
|
end
|
491
494
|
|
492
495
|
# Defines a route that accepts a TRACE request for the given path.
|
@@ -504,8 +507,8 @@ module Hanami
|
|
504
507
|
# @see Hanami::Router#get
|
505
508
|
#
|
506
509
|
# @since 0.1.0
|
507
|
-
def trace(path,
|
508
|
-
|
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)
|
509
512
|
end
|
510
513
|
|
511
514
|
# Defines a route that accepts a LINK request for the given path.
|
@@ -523,8 +526,8 @@ module Hanami
|
|
523
526
|
# @see Hanami::Router#get
|
524
527
|
#
|
525
528
|
# @since 0.8.0
|
526
|
-
def link(path,
|
527
|
-
|
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)
|
528
531
|
end
|
529
532
|
|
530
533
|
# Defines a route that accepts an UNLINK request for the given path.
|
@@ -542,8 +545,27 @@ module Hanami
|
|
542
545
|
# @see Hanami::Router#get
|
543
546
|
#
|
544
547
|
# @since 0.8.0
|
545
|
-
def unlink(path,
|
546
|
-
|
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)
|
550
|
+
end
|
551
|
+
|
552
|
+
# Defines a route that accepts a OPTIONS request for the given path.
|
553
|
+
#
|
554
|
+
# @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
|
+
#
|
559
|
+
# @param blk [Proc] the anonymous proc to be used as endpoint for the route
|
560
|
+
#
|
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
|
+
# @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)
|
547
569
|
end
|
548
570
|
|
549
571
|
# Defines a root route (a GET route for '/')
|
@@ -572,27 +594,8 @@ module Hanami
|
|
572
594
|
#
|
573
595
|
# router.path(:root) # => "/"
|
574
596
|
# router.url(:root) # => "https://hanamirb.org/"
|
575
|
-
def root(
|
576
|
-
|
577
|
-
end
|
578
|
-
|
579
|
-
# Defines a route that accepts a OPTIONS request for the given path.
|
580
|
-
#
|
581
|
-
# @param path [String] the relative URL to be matched
|
582
|
-
#
|
583
|
-
# @param options [Hash] the options to customize the route
|
584
|
-
# @option options [String,Proc,Class,Object#call] :to the endpoint
|
585
|
-
#
|
586
|
-
# @param blk [Proc] the anonymous proc to be used as endpoint for the route
|
587
|
-
#
|
588
|
-
# @return [Hanami::Routing::Route] this may vary according to the :route
|
589
|
-
# option passed to the constructor
|
590
|
-
#
|
591
|
-
# @see Hanami::Router#get
|
592
|
-
#
|
593
|
-
# @since 0.1.0
|
594
|
-
def options(path, options = {}, &blk)
|
595
|
-
@router.options(path, options, &blk)
|
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)
|
596
599
|
end
|
597
600
|
|
598
601
|
# Defines an HTTP redirect
|
@@ -621,57 +624,75 @@ module Hanami
|
|
621
624
|
#
|
622
625
|
# router = Hanami::Router.new
|
623
626
|
# router.redirect '/legacy', to: '/new_endpoint'
|
624
|
-
def redirect(path,
|
625
|
-
|
626
|
-
|
627
|
-
route.dest = Hanami::Routing::RedirectEndpoint.new(destination_path, route.dest)
|
628
|
-
end
|
627
|
+
def redirect(path, to:, code: 301)
|
628
|
+
to = Routing::Redirect.new(@prefix.join(to).to_s, code)
|
629
|
+
add_route(GET, path, to)
|
629
630
|
end
|
630
631
|
|
631
|
-
# Defines a Ruby block: all the routes defined within it will be
|
632
|
+
# Defines a Ruby block: all the routes defined within it will be prefixed
|
632
633
|
# with the given relative path.
|
633
634
|
#
|
634
|
-
#
|
635
|
+
# Prefix blocks can be nested multiple times.
|
635
636
|
#
|
636
|
-
# @param
|
637
|
+
# @param path [String] the relative path where the nested routes will
|
637
638
|
# be mounted
|
638
639
|
# @param blk [Proc] the block that defines the resources
|
639
640
|
#
|
640
|
-
# @return [
|
641
|
+
# @return [void]
|
641
642
|
#
|
642
|
-
# @since 0.
|
643
|
+
# @since 2.0.0
|
643
644
|
#
|
644
645
|
# @see Hanami::Router
|
645
646
|
#
|
646
647
|
# @example Basic example
|
647
|
-
# require
|
648
|
+
# require "hanami/router"
|
648
649
|
#
|
649
650
|
# Hanami::Router.new do
|
650
|
-
#
|
651
|
-
# get
|
651
|
+
# prefix "trees" do
|
652
|
+
# get "/sequoia", to: endpoint # => "/trees/sequoia"
|
652
653
|
# end
|
653
654
|
# end
|
654
655
|
#
|
655
|
-
# @example Nested
|
656
|
-
# require
|
656
|
+
# @example Nested prefix
|
657
|
+
# require "hanami/router"
|
657
658
|
#
|
658
659
|
# Hanami::Router.new do
|
659
|
-
#
|
660
|
-
#
|
661
|
-
# get
|
660
|
+
# prefix "animals" do
|
661
|
+
# prefix "mammals" do
|
662
|
+
# get "/cats", to: endpoint # => "/animals/mammals/cats"
|
662
663
|
# end
|
663
664
|
# end
|
664
665
|
# end
|
666
|
+
def prefix(path, namespace: nil, configuration: nil, &blk)
|
667
|
+
Routing::Prefix.new(self, path, namespace, configuration, &blk)
|
668
|
+
end
|
669
|
+
|
670
|
+
# Defines a scope for routes.
|
671
|
+
#
|
672
|
+
# A scope is a combination of a path prefix and a Ruby namespace.
|
673
|
+
#
|
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
|
679
|
+
#
|
680
|
+
# @since 2.0.0
|
681
|
+
# @api private
|
665
682
|
#
|
666
683
|
# @example
|
667
|
-
# require
|
684
|
+
# require "hanami/router"
|
685
|
+
# require "hanami/controller"
|
668
686
|
#
|
669
|
-
#
|
670
|
-
#
|
671
|
-
#
|
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
|
672
693
|
# end
|
673
|
-
def
|
674
|
-
Routing::
|
694
|
+
def scope(prefix, namespace:, configuration:, &blk)
|
695
|
+
Routing::Scope.new(self, prefix, namespace, configuration, &blk)
|
675
696
|
end
|
676
697
|
|
677
698
|
# Defines a set of named routes for a single RESTful resource.
|
@@ -793,7 +814,7 @@ module Hanami
|
|
793
814
|
# # | GET | /identity/keys | Identity::Keys | | :keys_identity |
|
794
815
|
# # +------+----------------+----------------+------+----------------+
|
795
816
|
def resource(name, options = {}, &blk)
|
796
|
-
Routing::Resource.new(self, name, options.merge(separator:
|
817
|
+
Routing::Resource.new(self, name, options.merge(separator: Routing::Endpoint::ACTION_SEPARATOR), &blk)
|
797
818
|
end
|
798
819
|
|
799
820
|
# Defines a set of named routes for a plural RESTful resource.
|
@@ -916,7 +937,7 @@ module Hanami
|
|
916
937
|
# # | GET | /articles/search | Articles::Search | | :search_articles |
|
917
938
|
# # +------+------------------+------------------+------+------------------+
|
918
939
|
def resources(name, options = {}, &blk)
|
919
|
-
Routing::Resources.new(self, name, options.merge(separator:
|
940
|
+
Routing::Resources.new(self, name, options.merge(separator: Routing::Endpoint::ACTION_SEPARATOR), &blk)
|
920
941
|
end
|
921
942
|
|
922
943
|
# Mount a Rack application at the specified path.
|
@@ -1004,8 +1025,9 @@ module Hanami
|
|
1004
1025
|
# # 3. RackThree is used as it is (object), because it respond to #call
|
1005
1026
|
# # 4. That Proc is used as it is, because it respond to #call
|
1006
1027
|
# # 5. That string is resolved as Dashboard::Index (Hanami::Controller)
|
1007
|
-
def mount(app,
|
1008
|
-
@
|
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)
|
1009
1031
|
end
|
1010
1032
|
|
1011
1033
|
# Resolve the given Rack env to a registered endpoint and invoke it.
|
@@ -1016,7 +1038,15 @@ module Hanami
|
|
1016
1038
|
#
|
1017
1039
|
# @since 0.1.0
|
1018
1040
|
def call(env)
|
1019
|
-
@
|
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
|
1020
1050
|
end
|
1021
1051
|
|
1022
1052
|
# Recognize the given env, path, or name and return a route for testing
|
@@ -1128,14 +1158,11 @@ module Hanami
|
|
1128
1158
|
# route.routable? # => false
|
1129
1159
|
# route.params # => {:id=>"1"}
|
1130
1160
|
def recognize(env, options = {}, params = nil)
|
1131
|
-
|
1132
|
-
|
1133
|
-
|
1134
|
-
responses, _ = *@router.recognize(env)
|
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) }
|
1135
1164
|
|
1136
|
-
Routing::RecognizedRoute.new(
|
1137
|
-
responses.nil? ? responses : responses.first,
|
1138
|
-
env, @router)
|
1165
|
+
Routing::RecognizedRoute.new(route, env, @namespace)
|
1139
1166
|
end
|
1140
1167
|
|
1141
1168
|
# Generate an relative URL for a specified named route.
|
@@ -1161,8 +1188,10 @@ module Hanami
|
|
1161
1188
|
# router.path(:login) # => "/login"
|
1162
1189
|
# router.path(:login, return_to: '/dashboard') # => "/login?return_to=%2Fdashboard"
|
1163
1190
|
# router.path(:framework, name: 'router') # => "/router"
|
1164
|
-
def path(route,
|
1165
|
-
@
|
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")
|
1166
1195
|
end
|
1167
1196
|
|
1168
1197
|
# Generate a URL for a specified named route.
|
@@ -1188,8 +1217,8 @@ module Hanami
|
|
1188
1217
|
# router.url(:login) # => "https://hanamirb.org/login"
|
1189
1218
|
# router.url(:login, return_to: '/dashboard') # => "https://hanamirb.org/login?return_to=%2Fdashboard"
|
1190
1219
|
# router.url(:framework, name: 'router') # => "https://hanamirb.org/router"
|
1191
|
-
def url(route,
|
1192
|
-
@
|
1220
|
+
def url(route, args = {})
|
1221
|
+
@base + path(route, args)
|
1193
1222
|
end
|
1194
1223
|
|
1195
1224
|
# Returns an routes inspector
|
@@ -1214,8 +1243,8 @@ module Hanami
|
|
1214
1243
|
# POST /login Sessions::Create
|
1215
1244
|
# logout GET, HEAD /logout Sessions::Destroy
|
1216
1245
|
def inspector
|
1217
|
-
require
|
1218
|
-
Routing::RoutesInspector.new(@
|
1246
|
+
require "hanami/routing/routes_inspector"
|
1247
|
+
Routing::RoutesInspector.new(@routes, @prefix)
|
1219
1248
|
end
|
1220
1249
|
|
1221
1250
|
protected
|
@@ -1233,8 +1262,8 @@ module Hanami
|
|
1233
1262
|
#
|
1234
1263
|
# @see Hanami::Router#recognize
|
1235
1264
|
# @see http://www.rubydoc.info/github/rack/rack/Rack%2FMockRequest.env_for
|
1236
|
-
def env_for(env, options = {}, params = nil)
|
1237
|
-
|
1265
|
+
def env_for(env, options = {}, params = nil) # rubocop:disable Metrics/MethodLength
|
1266
|
+
case env
|
1238
1267
|
when String
|
1239
1268
|
Rack::MockRequest.env_for(env, options)
|
1240
1269
|
when Symbol
|
@@ -1248,5 +1277,89 @@ module Hanami
|
|
1248
1277
|
env
|
1249
1278
|
end
|
1250
1279
|
end
|
1280
|
+
|
1281
|
+
private
|
1282
|
+
|
1283
|
+
PATH_INFO = "PATH_INFO"
|
1284
|
+
SCRIPT_NAME = "SCRIPT_NAME"
|
1285
|
+
SERVER_NAME = "SERVER_NAME"
|
1286
|
+
REQUEST_METHOD = "REQUEST_METHOD"
|
1287
|
+
|
1288
|
+
PARAMS = "router.params"
|
1289
|
+
|
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"
|
1300
|
+
|
1301
|
+
NOT_FOUND = ->(_) { [404, { "Content-Length" => "9" }, ["Not Found"]] }.freeze
|
1302
|
+
NOT_ALLOWED = ->(_) { [405, { "Content-Length" => "18" }, ["Method Not Allowed"]] }.freeze
|
1303
|
+
ROOT = "/"
|
1304
|
+
|
1305
|
+
BODY = 2
|
1306
|
+
|
1307
|
+
attr_reader :configuration
|
1308
|
+
|
1309
|
+
# Application
|
1310
|
+
#
|
1311
|
+
# @since 2.0.0
|
1312
|
+
# @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
|
1320
|
+
end
|
1321
|
+
|
1322
|
+
def match?(env)
|
1323
|
+
match_path?(env)
|
1324
|
+
end
|
1325
|
+
|
1326
|
+
def match_path?(env)
|
1327
|
+
result = env[PATH_INFO].start_with?(@prefix)
|
1328
|
+
result &&= @host == env[SERVER_NAME] unless @host.nil?
|
1329
|
+
|
1330
|
+
result
|
1331
|
+
end
|
1332
|
+
|
1333
|
+
def call(env)
|
1334
|
+
env[PARAMS] ||= {}
|
1335
|
+
env[PARAMS].merge!(Utils::Hash.deep_symbolize(@path.params(env[PATH_INFO]) || {}))
|
1336
|
+
|
1337
|
+
env[SCRIPT_NAME] = @prefix
|
1338
|
+
env[PATH_INFO] = env[PATH_INFO].sub(@prefix, "")
|
1339
|
+
env[PATH_INFO] = "/" if env[PATH_INFO] == ""
|
1340
|
+
|
1341
|
+
@endpoint.call(env)
|
1342
|
+
end
|
1343
|
+
end
|
1344
|
+
|
1345
|
+
def add_route(verb, path, to, as = nil, namespace = nil, config = nil, constraints = {}, &blk)
|
1346
|
+
to ||= blk
|
1347
|
+
config ||= configuration
|
1348
|
+
|
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)
|
1352
|
+
|
1353
|
+
@routes.push(route)
|
1354
|
+
@named[as] = route unless as.nil?
|
1355
|
+
end
|
1356
|
+
|
1357
|
+
def verb_for(value)
|
1358
|
+
if value == GET
|
1359
|
+
[GET, HEAD]
|
1360
|
+
else
|
1361
|
+
[value]
|
1362
|
+
end
|
1363
|
+
end
|
1251
1364
|
end
|
1252
1365
|
end
|