hanami-router 1.3.1 → 2.0.0.alpha4
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 +44 -0
- data/LICENSE.md +1 -1
- data/README.md +97 -443
- 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 +548 -957
- data/lib/hanami/router/block.rb +88 -0
- data/lib/hanami/router/error.rb +67 -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 +28 -0
- data/lib/hanami/router/segment.rb +19 -0
- data/lib/hanami/router/trie.rb +63 -0
- data/lib/hanami/router/url_helpers.rb +40 -0
- data/lib/hanami/router/version.rb +4 -1
- metadata +61 -41
- 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
@@ -1,220 +0,0 @@
|
|
1
|
-
require 'uri'
|
2
|
-
require 'http_router'
|
3
|
-
require 'hanami/routing/endpoint_resolver'
|
4
|
-
require 'hanami/routing/route'
|
5
|
-
require 'hanami/routing/parsers'
|
6
|
-
require 'hanami/routing/force_ssl'
|
7
|
-
require 'hanami/routing/error'
|
8
|
-
require 'hanami/utils/path_prefix'
|
9
|
-
require 'hanami/routing/http_router_monkey_patch'
|
10
|
-
|
11
|
-
module Hanami
|
12
|
-
module Routing
|
13
|
-
# Invalid route
|
14
|
-
# This is raised when the router fails to recognize a route, because of the
|
15
|
-
# given arguments.
|
16
|
-
#
|
17
|
-
# @since 0.1.0
|
18
|
-
class InvalidRouteException < Hanami::Routing::Error
|
19
|
-
end
|
20
|
-
|
21
|
-
# HTTP router
|
22
|
-
#
|
23
|
-
# This implementation is based on ::HttpRouter (http_router gem).
|
24
|
-
#
|
25
|
-
# Hanami::Router wraps an instance of this class, in order to protect its
|
26
|
-
# public API from any future change of ::HttpRouter.
|
27
|
-
#
|
28
|
-
# @since 0.1.0
|
29
|
-
# @api private
|
30
|
-
class HttpRouter < ::HttpRouter
|
31
|
-
# Script name - rack environment variable
|
32
|
-
#
|
33
|
-
# @since 0.5.0
|
34
|
-
# @api private
|
35
|
-
SCRIPT_NAME = 'SCRIPT_NAME'.freeze
|
36
|
-
|
37
|
-
# Path info - rack environment variable
|
38
|
-
#
|
39
|
-
# @since 0.7.0
|
40
|
-
# @api private
|
41
|
-
PATH_INFO = 'PATH_INFO'.freeze
|
42
|
-
|
43
|
-
# Default PATH_INFO for Rack requests
|
44
|
-
#
|
45
|
-
# @since 0.7.0
|
46
|
-
# @api private
|
47
|
-
DEFAULT_PATH_INFO = '/'.freeze
|
48
|
-
|
49
|
-
# URL separator
|
50
|
-
#
|
51
|
-
# @since 0.7.0
|
52
|
-
# @api private
|
53
|
-
URL_SEPARATOR = '/'.freeze
|
54
|
-
|
55
|
-
# @since 0.5.0
|
56
|
-
# @api private
|
57
|
-
attr_reader :namespace
|
58
|
-
|
59
|
-
# @since 0.8.0
|
60
|
-
# @api private
|
61
|
-
attr_reader :prefix
|
62
|
-
|
63
|
-
# Initialize the router.
|
64
|
-
#
|
65
|
-
# @see Hanami::Router#initialize
|
66
|
-
#
|
67
|
-
# @since 0.1.0
|
68
|
-
# @api private
|
69
|
-
def initialize(options = {}, &blk)
|
70
|
-
if options[:parsers]
|
71
|
-
depecration_message = 'Hanami::Router options[:parsers] is deprecated and it will be removed in future versions'
|
72
|
-
Hanami::Utils::Deprecation.new(depecration_message)
|
73
|
-
end
|
74
|
-
@compiled = false
|
75
|
-
@uri_parser = URI::Parser.new
|
76
|
-
super(options, &nil)
|
77
|
-
|
78
|
-
@namespace = options[:namespace] if options[:namespace]
|
79
|
-
@default_scheme = options[:scheme] if options[:scheme]
|
80
|
-
@default_host = options[:host] if options[:host]
|
81
|
-
@default_port = options[:port] if options[:port]
|
82
|
-
@route_class = options[:route] || Routing::Route
|
83
|
-
@resolver = options[:resolver] || Routing::EndpointResolver.new(options)
|
84
|
-
@parsers = Routing::Parsers.new(options[:parsers])
|
85
|
-
@prefix = Utils::PathPrefix.new(options[:prefix] || '')
|
86
|
-
@force_ssl = Hanami::Routing::ForceSsl.new(!!options[:force_ssl], host: @default_host, port: @default_port)
|
87
|
-
end
|
88
|
-
|
89
|
-
# Separator between controller and action name.
|
90
|
-
#
|
91
|
-
# @see Hanami::Routing::EndpointResolver::ACTION_SEPARATOR
|
92
|
-
#
|
93
|
-
# @since 0.1.0
|
94
|
-
# @api private
|
95
|
-
def action_separator
|
96
|
-
@resolver.action_separator
|
97
|
-
end
|
98
|
-
|
99
|
-
# Finds a path from the given options.
|
100
|
-
#
|
101
|
-
# @see Hanami::Routing::EndpointResolver#find
|
102
|
-
#
|
103
|
-
# @since 0.1.0
|
104
|
-
# @api private
|
105
|
-
def find(options)
|
106
|
-
@resolver.find(options)
|
107
|
-
end
|
108
|
-
|
109
|
-
# Generate a relative URL for a specified named route.
|
110
|
-
#
|
111
|
-
# @see Hanami::Router#path
|
112
|
-
#
|
113
|
-
# @since 0.1.0
|
114
|
-
# @api private
|
115
|
-
def raw_path(route, *args)
|
116
|
-
_rescue_url_recognition do
|
117
|
-
_custom_path(super(route, *args))
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
# Generate an absolute URL for a specified named route.
|
122
|
-
#
|
123
|
-
# @see Hanami::Router#path
|
124
|
-
#
|
125
|
-
# @since 0.1.0
|
126
|
-
# @api private
|
127
|
-
def raw_url(route, *args)
|
128
|
-
_rescue_url_recognition do
|
129
|
-
_custom_path(super(route, *args))
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
# Support for OPTIONS HTTP verb
|
134
|
-
#
|
135
|
-
# @see Hanami::Router#options
|
136
|
-
#
|
137
|
-
# @since 0.1.0
|
138
|
-
# @api private
|
139
|
-
def options(path, options = {}, &blk)
|
140
|
-
add_with_request_method(path, :options, options, &blk)
|
141
|
-
end
|
142
|
-
|
143
|
-
# Allow to mount a Rack app
|
144
|
-
#
|
145
|
-
# @see Hanami::Router#mount
|
146
|
-
#
|
147
|
-
# @since 0.1.1
|
148
|
-
# @api private
|
149
|
-
def mount(app, options)
|
150
|
-
add("#{ options.fetch(:at) }*", host: options[:host]).to(
|
151
|
-
@resolver.resolve(to: app)
|
152
|
-
)
|
153
|
-
end
|
154
|
-
|
155
|
-
# @api private
|
156
|
-
def raw_call(env, &blk)
|
157
|
-
if response = @force_ssl.call(env)
|
158
|
-
response
|
159
|
-
else
|
160
|
-
super(@parsers.call(env))
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
# @api private
|
165
|
-
def reset!
|
166
|
-
uncompile
|
167
|
-
@routes, @named_routes, @root = [], Hash.new{|h,k| h[k] = []}, Node::Root.new(self)
|
168
|
-
@default_host, @default_port, @default_scheme = 'localhost', 80, 'http'
|
169
|
-
end
|
170
|
-
|
171
|
-
# @api private
|
172
|
-
def pass_on_response(response)
|
173
|
-
super response.to_a
|
174
|
-
end
|
175
|
-
|
176
|
-
# @api private
|
177
|
-
def no_response(request, env)
|
178
|
-
if request.acceptable_methods.any? && !request.acceptable_methods.include?(env['REQUEST_METHOD'])
|
179
|
-
[405, {'Allow' => request.acceptable_methods.sort.join(", ")}, []]
|
180
|
-
else
|
181
|
-
@default_app.call(env)
|
182
|
-
end
|
183
|
-
end
|
184
|
-
|
185
|
-
# @api private
|
186
|
-
def rewrite_partial_path_info(env, request)
|
187
|
-
if request.path.empty?
|
188
|
-
env[SCRIPT_NAME] += env[PATH_INFO]
|
189
|
-
env[PATH_INFO] = DEFAULT_PATH_INFO
|
190
|
-
else
|
191
|
-
path_info_before = env[PATH_INFO].dup
|
192
|
-
env[PATH_INFO] = "/#{@uri_parser.escape(request.path.join(URL_SEPARATOR))}"
|
193
|
-
env[SCRIPT_NAME] += path_info_before[0, path_info_before.bytesize - env[PATH_INFO].bytesize]
|
194
|
-
end
|
195
|
-
end
|
196
|
-
|
197
|
-
private
|
198
|
-
|
199
|
-
# @api private
|
200
|
-
def _rescue_url_recognition
|
201
|
-
yield
|
202
|
-
rescue ::HttpRouter::InvalidRouteException,
|
203
|
-
::HttpRouter::TooManyParametersException => e
|
204
|
-
raise Routing::InvalidRouteException.new("#{ e.message } - please check given arguments")
|
205
|
-
end
|
206
|
-
|
207
|
-
# @api private
|
208
|
-
def add_with_request_method(path, method, opts = {}, &app)
|
209
|
-
super.generate(@resolver, opts, &app)
|
210
|
-
end
|
211
|
-
|
212
|
-
# @api private
|
213
|
-
def _custom_path(uri_string)
|
214
|
-
uri = URI.parse(uri_string)
|
215
|
-
uri.path = @prefix.join(uri.path)
|
216
|
-
uri.to_s
|
217
|
-
end
|
218
|
-
end
|
219
|
-
end
|
220
|
-
end
|
@@ -1,38 +0,0 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
#
|
3
|
-
# This monkey patches http_router to make it Rack 2.0 compatible.
|
4
|
-
# Details see: https://github.com/hanami/router/issues/136
|
5
|
-
#
|
6
|
-
# @api private
|
7
|
-
class HttpRouter
|
8
|
-
# @api private
|
9
|
-
class Node
|
10
|
-
# @api private
|
11
|
-
class Path < Node
|
12
|
-
def to_code
|
13
|
-
path_ivar = inject_root_ivar(self)
|
14
|
-
"#{"if !callback && request.path.size == 1 && request.path.first == '' && (request.rack_request.head? || request.rack_request.get?) && request.rack_request.path_info[-1] == ?/
|
15
|
-
response = ::Rack::Response.new
|
16
|
-
response.redirect(request.rack_request.path_info[0, request.rack_request.path_info.size - 1], 302)
|
17
|
-
return response.finish
|
18
|
-
end" if router.redirect_trailing_slash?}
|
19
|
-
|
20
|
-
#{"if request.#{router.ignore_trailing_slash? ? 'path_finished?' : 'path.empty?'}" unless route.match_partially}
|
21
|
-
if callback
|
22
|
-
request.called = true
|
23
|
-
callback.call(Response.new(request, #{path_ivar}))
|
24
|
-
else
|
25
|
-
env = request.rack_request.env
|
26
|
-
env['router.request'] = request
|
27
|
-
env['router.params'] ||= {}
|
28
|
-
#{"env['router.params'].merge!(Hash[#{param_names.inspect}.zip(request.params)])" if dynamic?}
|
29
|
-
@router.rewrite#{"_partial" if route.match_partially}_path_info(env, request)
|
30
|
-
response = @router.process_destination_path(#{path_ivar}, env)
|
31
|
-
return response unless router.pass_on_response(response)
|
32
|
-
end
|
33
|
-
#{"end" unless route.match_partially}"
|
34
|
-
end
|
35
|
-
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
@@ -1,98 +0,0 @@
|
|
1
|
-
require 'delegate'
|
2
|
-
require 'hanami/utils/path_prefix'
|
3
|
-
|
4
|
-
module Hanami
|
5
|
-
module Routing
|
6
|
-
# Namespace for routes.
|
7
|
-
# Implementation of Hanami::Router#namespace
|
8
|
-
#
|
9
|
-
# @since 0.1.0
|
10
|
-
#
|
11
|
-
# @api private
|
12
|
-
#
|
13
|
-
# @see Hanami::Router#namespace
|
14
|
-
class Namespace < SimpleDelegator
|
15
|
-
# @api private
|
16
|
-
# @since 0.1.0
|
17
|
-
def initialize(router, name, &blk)
|
18
|
-
@router = router
|
19
|
-
@name = Utils::PathPrefix.new(name)
|
20
|
-
__setobj__(@router)
|
21
|
-
instance_eval(&blk)
|
22
|
-
end
|
23
|
-
|
24
|
-
# @api private
|
25
|
-
# @since 0.1.0
|
26
|
-
def get(path, options = {}, &endpoint)
|
27
|
-
super(@name.join(path), options, &endpoint)
|
28
|
-
end
|
29
|
-
|
30
|
-
# @api private
|
31
|
-
# @since 0.1.0
|
32
|
-
def post(path, options = {}, &endpoint)
|
33
|
-
super(@name.join(path), options, &endpoint)
|
34
|
-
end
|
35
|
-
|
36
|
-
# @api private
|
37
|
-
# @since 0.1.0
|
38
|
-
def put(path, options = {}, &endpoint)
|
39
|
-
super(@name.join(path), options, &endpoint)
|
40
|
-
end
|
41
|
-
|
42
|
-
# @api private
|
43
|
-
# @since 0.1.0
|
44
|
-
def patch(path, options = {}, &endpoint)
|
45
|
-
super(@name.join(path), options, &endpoint)
|
46
|
-
end
|
47
|
-
|
48
|
-
# @api private
|
49
|
-
# @since 0.1.0
|
50
|
-
def delete(path, options = {}, &endpoint)
|
51
|
-
super(@name.join(path), options, &endpoint)
|
52
|
-
end
|
53
|
-
|
54
|
-
# @api private
|
55
|
-
# @since 0.1.0
|
56
|
-
def trace(path, options = {}, &endpoint)
|
57
|
-
super(@name.join(path), options, &endpoint)
|
58
|
-
end
|
59
|
-
|
60
|
-
# @api private
|
61
|
-
# @since 0.1.0
|
62
|
-
def options(path, options = {}, &endpoint)
|
63
|
-
super(@name.join(path), options, &endpoint)
|
64
|
-
end
|
65
|
-
|
66
|
-
# @api private
|
67
|
-
# @since 0.1.0
|
68
|
-
def resource(name, options = {})
|
69
|
-
super name, options.merge(namespace: @name.relative_join(options[:namespace]))
|
70
|
-
end
|
71
|
-
|
72
|
-
# @api private
|
73
|
-
# @since 0.1.0
|
74
|
-
def resources(name, options = {})
|
75
|
-
super name, options.merge(namespace: @name.relative_join(options[:namespace]))
|
76
|
-
end
|
77
|
-
|
78
|
-
# @api private
|
79
|
-
# @since 0.1.0
|
80
|
-
def redirect(path, options = {}, &endpoint)
|
81
|
-
super(@name.join(path), options.merge(to: @name.join(options[:to])), &endpoint)
|
82
|
-
end
|
83
|
-
|
84
|
-
# @api private
|
85
|
-
# @since 1.1.0
|
86
|
-
def mount(app, options)
|
87
|
-
super(app, options.merge(at: @name.join(options[:at])))
|
88
|
-
end
|
89
|
-
|
90
|
-
# Supports nested namespaces
|
91
|
-
# @api private
|
92
|
-
# @since 0.1.0
|
93
|
-
def namespace(name, &blk)
|
94
|
-
Routing::Namespace.new(self, name, &blk)
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
@@ -1,113 +0,0 @@
|
|
1
|
-
require 'hanami/routing/parsing/parser'
|
2
|
-
require 'hanami/utils/hash'
|
3
|
-
|
4
|
-
module Hanami
|
5
|
-
module Routing
|
6
|
-
# @since 0.2.0
|
7
|
-
# @api private
|
8
|
-
class Parsers
|
9
|
-
# @since 0.2.0
|
10
|
-
# @api private
|
11
|
-
CONTENT_TYPE = 'CONTENT_TYPE'.freeze
|
12
|
-
|
13
|
-
# @since 0.2.0
|
14
|
-
# @api private
|
15
|
-
MEDIA_TYPE_MATCHER = /\s*[;,]\s*/.freeze
|
16
|
-
|
17
|
-
# @since 0.2.0
|
18
|
-
# @api private
|
19
|
-
RACK_INPUT = 'rack.input'.freeze
|
20
|
-
|
21
|
-
# @since 0.2.0
|
22
|
-
# @api private
|
23
|
-
ROUTER_PARAMS = 'router.params'.freeze
|
24
|
-
|
25
|
-
# @api private
|
26
|
-
ROUTER_PARSED_BODY = 'router.parsed_body'.freeze
|
27
|
-
|
28
|
-
# @api private
|
29
|
-
FALLBACK_KEY = '_'.freeze
|
30
|
-
|
31
|
-
# @since 0.2.0
|
32
|
-
# @api private
|
33
|
-
def initialize(parsers)
|
34
|
-
@parsers = prepare(parsers)
|
35
|
-
_redefine_call
|
36
|
-
end
|
37
|
-
|
38
|
-
# @since 0.2.0
|
39
|
-
# @api private
|
40
|
-
def call(env)
|
41
|
-
env
|
42
|
-
end
|
43
|
-
|
44
|
-
private
|
45
|
-
# @since 0.2.0
|
46
|
-
# @api private
|
47
|
-
def prepare(args)
|
48
|
-
result = Hash.new
|
49
|
-
args = Array(args)
|
50
|
-
return result if args.empty?
|
51
|
-
|
52
|
-
args.each do |arg|
|
53
|
-
parser = Parsing::Parser.for(arg)
|
54
|
-
|
55
|
-
parser.mime_types.each do |mime|
|
56
|
-
result[mime] = parser
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
result.default = Parsing::Parser.new
|
61
|
-
result
|
62
|
-
end
|
63
|
-
|
64
|
-
# @since 0.2.0
|
65
|
-
# @api private
|
66
|
-
def _redefine_call
|
67
|
-
return if @parsers.empty?
|
68
|
-
|
69
|
-
define_singleton_method :call do |env|
|
70
|
-
body = env[RACK_INPUT].read
|
71
|
-
return env if body.empty?
|
72
|
-
|
73
|
-
env[RACK_INPUT].rewind # somebody might try to read this stream
|
74
|
-
|
75
|
-
env[ROUTER_PARAMS] ||= {} # prepare params
|
76
|
-
env[ROUTER_PARSED_BODY] = _parse(env, body)
|
77
|
-
env[ROUTER_PARAMS] = _symbolize(env[ROUTER_PARSED_BODY]).merge(env[ROUTER_PARAMS])
|
78
|
-
|
79
|
-
env
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
# @api private
|
84
|
-
def _symbolize(body)
|
85
|
-
if body.is_a?(Hash)
|
86
|
-
Utils::Hash.deep_symbolize(body)
|
87
|
-
else
|
88
|
-
{ FALLBACK_KEY => body }
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
# @api private
|
93
|
-
def _parse(env, body)
|
94
|
-
@parsers[
|
95
|
-
media_type(env)
|
96
|
-
].parse(body)
|
97
|
-
end
|
98
|
-
|
99
|
-
# @api private
|
100
|
-
def media_type(env)
|
101
|
-
if ct = content_type(env)
|
102
|
-
ct.split(MEDIA_TYPE_MATCHER, 2).first.downcase
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
# @api private
|
107
|
-
def content_type(env)
|
108
|
-
content_type = env[CONTENT_TYPE]
|
109
|
-
content_type.nil? || content_type.empty? ? nil : content_type
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|