hanami-router 1.3.2 → 2.0.0.alpha1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|