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