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,238 +0,0 @@
|
|
1
|
-
require 'hanami/utils/string'
|
2
|
-
require 'hanami/utils/class'
|
3
|
-
require 'hanami/routing/endpoint'
|
4
|
-
|
5
|
-
module Hanami
|
6
|
-
module Routing
|
7
|
-
# Resolve duck-typed endpoints
|
8
|
-
#
|
9
|
-
# @since 0.1.0
|
10
|
-
#
|
11
|
-
# @api private
|
12
|
-
class EndpointResolver
|
13
|
-
# @since 0.2.0
|
14
|
-
# @api private
|
15
|
-
NAMING_PATTERN = '%{controller}::%{action}'.freeze
|
16
|
-
|
17
|
-
# @since 0.7.0
|
18
|
-
# @api private
|
19
|
-
DEFAULT_RESPONSE = [404, {'X-Cascade' => 'pass'}, 'Not Found'].freeze
|
20
|
-
|
21
|
-
# Default separator for controller and action.
|
22
|
-
# A different separator can be passed to #initialize with the `:separator` option.
|
23
|
-
#
|
24
|
-
# @see #initialize
|
25
|
-
# @see #resolve
|
26
|
-
#
|
27
|
-
# @since 0.1.0
|
28
|
-
#
|
29
|
-
# @example
|
30
|
-
# require 'hanami/router'
|
31
|
-
#
|
32
|
-
# router = Hanami::Router.new do
|
33
|
-
# get '/', to: 'articles#show'
|
34
|
-
# end
|
35
|
-
ACTION_SEPARATOR = '#'.freeze
|
36
|
-
|
37
|
-
attr_reader :action_separator
|
38
|
-
|
39
|
-
# Initialize an endpoint resolver
|
40
|
-
#
|
41
|
-
# @param options [Hash] the options used to customize lookup behavior
|
42
|
-
#
|
43
|
-
# @option options [Class] :endpoint the endpoint class that is returned
|
44
|
-
# by `#resolve`. (defaults to `Hanami::Routing::Endpoint`)
|
45
|
-
#
|
46
|
-
# @option options [Class,Module] :namespace the Ruby namespace where to
|
47
|
-
# lookup for controllers and actions. (defaults to `Object`)
|
48
|
-
#
|
49
|
-
# @option options [String] :pattern the string to interpolate in order
|
50
|
-
# to return an action name. This string SHOULD contain
|
51
|
-
# <tt>'%{controller}'</tt> and <tt>'%{action}'</tt>, all the other keys
|
52
|
-
# will be ignored.
|
53
|
-
# See the examples below.
|
54
|
-
#
|
55
|
-
# @option options [String] :action_separator the separator between controller and
|
56
|
-
# action name. (defaults to `ACTION_SEPARATOR`)
|
57
|
-
#
|
58
|
-
# @return [Hanami::Routing::EndpointResolver] self
|
59
|
-
#
|
60
|
-
# @since 0.1.0
|
61
|
-
# @api private
|
62
|
-
#
|
63
|
-
# @example Specify custom endpoint class
|
64
|
-
# require 'hanami/router'
|
65
|
-
#
|
66
|
-
# resolver = Hanami::Routing::EndpointResolver.new(endpoint: CustomEndpoint)
|
67
|
-
# router = Hanami::Router.new(resolver: resolver)
|
68
|
-
#
|
69
|
-
# router.get('/', to: endpoint).dest # => #<CustomEndpoint:0x007f97f3359570 ...>
|
70
|
-
#
|
71
|
-
# @example Specify custom Ruby namespace
|
72
|
-
# require 'hanami/router'
|
73
|
-
#
|
74
|
-
# resolver = Hanami::Routing::EndpointResolver.new(namespace: MyApp)
|
75
|
-
# router = Hanami::Router.new(resolver: resolver)
|
76
|
-
#
|
77
|
-
# router.get('/', to: 'articles#show')
|
78
|
-
# # => Will look for: MyApp::Articles::Show
|
79
|
-
#
|
80
|
-
#
|
81
|
-
#
|
82
|
-
# @example Specify custom pattern
|
83
|
-
# require 'hanami/router'
|
84
|
-
#
|
85
|
-
# resolver = Hanami::Routing::EndpointResolver.new(pattern: '%{controller}Controller::%{action}')
|
86
|
-
# router = Hanami::Router.new(resolver: resolver)
|
87
|
-
#
|
88
|
-
# router.get('/', to: 'articles#show')
|
89
|
-
# # => Will look for: ArticlesController::Show
|
90
|
-
#
|
91
|
-
#
|
92
|
-
#
|
93
|
-
# @example Specify custom controller-action separator
|
94
|
-
# require 'hanami/router'
|
95
|
-
#
|
96
|
-
# resolver = Hanami::Routing::EndpointResolver.new(separator: '@')
|
97
|
-
# router = Hanami::Router.new(resolver: resolver)
|
98
|
-
#
|
99
|
-
# router.get('/', to: 'articles@show')
|
100
|
-
# # => Will look for: Articles::Show
|
101
|
-
def initialize(options = {})
|
102
|
-
@endpoint_class = options[:endpoint] || Endpoint
|
103
|
-
@namespace = options[:namespace] || Object
|
104
|
-
@action_separator = options[:action_separator] || ACTION_SEPARATOR
|
105
|
-
@pattern = options[:pattern] || NAMING_PATTERN
|
106
|
-
end
|
107
|
-
|
108
|
-
# Resolve the given set of HTTP verb, path, endpoint and options.
|
109
|
-
# If it fails to resolve, it will mount the default endpoint to the given
|
110
|
-
# path, which returns an 404 (Not Found).
|
111
|
-
#
|
112
|
-
# @param options [Hash] the options required to resolve the endpoint
|
113
|
-
#
|
114
|
-
# @option options [String,Proc,Class,Object#call] :to the endpoint
|
115
|
-
# @option options [String] :namespace an optional routing namespace
|
116
|
-
#
|
117
|
-
# @return [Endpoint] this may vary according to the :endpoint option
|
118
|
-
# passed to #initialize
|
119
|
-
#
|
120
|
-
# @since 0.1.0
|
121
|
-
# @api private
|
122
|
-
#
|
123
|
-
# @see #initialize
|
124
|
-
# @see #find
|
125
|
-
#
|
126
|
-
# @example Resolve to a Proc
|
127
|
-
# require 'hanami/router'
|
128
|
-
#
|
129
|
-
# router = Hanami::Router.new
|
130
|
-
# router.get '/', to: ->(env) { [200, {}, ['Hi!']] }
|
131
|
-
#
|
132
|
-
# @example Resolve to a class
|
133
|
-
# require 'hanami/router'
|
134
|
-
#
|
135
|
-
# router = Hanami::Router.new
|
136
|
-
# router.get '/', to: RackMiddleware
|
137
|
-
#
|
138
|
-
# @example Resolve to a Rack compatible object (respond to #call)
|
139
|
-
# require 'hanami/router'
|
140
|
-
#
|
141
|
-
# router = Hanami::Router.new
|
142
|
-
# router.get '/', to: AnotherMiddleware.new
|
143
|
-
#
|
144
|
-
# @example Resolve to a Hanami::Action from a string (see Hanami::Controller framework)
|
145
|
-
# require 'hanami/router'
|
146
|
-
#
|
147
|
-
# router = Hanami::Router.new
|
148
|
-
# router.get '/', to: 'articles#show'
|
149
|
-
#
|
150
|
-
# @example Resolve to a Hanami::Action (see Hanami::Controller framework)
|
151
|
-
# require 'hanami/router'
|
152
|
-
#
|
153
|
-
# router = Hanami::Router.new
|
154
|
-
# router.get '/', to: Articles::Show
|
155
|
-
#
|
156
|
-
# @example Resolve a redirect with a namespace
|
157
|
-
# require 'hanami/router'
|
158
|
-
#
|
159
|
-
# router = Hanami::Router.new
|
160
|
-
# router.namespace 'users' do
|
161
|
-
# get '/home', to: ->(env) { ... }
|
162
|
-
# redirect '/dashboard', to: '/home'
|
163
|
-
# end
|
164
|
-
#
|
165
|
-
# # GET /users/dashboard => 301 Location: "/users/home"
|
166
|
-
def resolve(options, &endpoint)
|
167
|
-
result = endpoint || find(options)
|
168
|
-
resolve_callable(result) || resolve_matchable(result) || default
|
169
|
-
end
|
170
|
-
|
171
|
-
# Finds a path from the given options.
|
172
|
-
#
|
173
|
-
# @param options [Hash] the path description
|
174
|
-
# @option options [String,Proc,Class,Object#call] :to the endpoint
|
175
|
-
# @option options [String] :namespace an optional namespace
|
176
|
-
#
|
177
|
-
# @since 0.1.0
|
178
|
-
# @api private
|
179
|
-
#
|
180
|
-
# @return [Object]
|
181
|
-
def find(options)
|
182
|
-
options[:to]
|
183
|
-
end
|
184
|
-
|
185
|
-
protected
|
186
|
-
# @api private
|
187
|
-
def default
|
188
|
-
@endpoint_class.new(
|
189
|
-
->(env) { DEFAULT_RESPONSE }
|
190
|
-
)
|
191
|
-
end
|
192
|
-
|
193
|
-
# @api private
|
194
|
-
def constantize(string)
|
195
|
-
klass = Utils::Class.load!(string, @namespace)
|
196
|
-
if klass.respond_to?(:call)
|
197
|
-
Endpoint.new(klass)
|
198
|
-
else
|
199
|
-
ClassEndpoint.new(klass)
|
200
|
-
end
|
201
|
-
rescue NameError
|
202
|
-
LazyEndpoint.new(string, @namespace)
|
203
|
-
end
|
204
|
-
|
205
|
-
# @api private
|
206
|
-
def classify(string)
|
207
|
-
Utils::String.transform(string, :underscore, :classify)
|
208
|
-
end
|
209
|
-
|
210
|
-
private
|
211
|
-
# @api private
|
212
|
-
def resolve_callable(callable)
|
213
|
-
if callable.respond_to?(:call)
|
214
|
-
@endpoint_class.new(callable)
|
215
|
-
elsif callable.is_a?(Class) && callable.instance_methods.include?(:call)
|
216
|
-
@endpoint_class.new(callable.new)
|
217
|
-
end
|
218
|
-
end
|
219
|
-
|
220
|
-
# @api private
|
221
|
-
def resolve_matchable(matchable)
|
222
|
-
if matchable.respond_to?(:match)
|
223
|
-
constantize(
|
224
|
-
resolve_action(matchable) || classify(matchable)
|
225
|
-
)
|
226
|
-
end
|
227
|
-
end
|
228
|
-
|
229
|
-
# @api private
|
230
|
-
def resolve_action(string)
|
231
|
-
if string.match(action_separator)
|
232
|
-
controller, action = string.split(action_separator).map {|token| classify(token) }
|
233
|
-
@pattern % {controller: controller, action: action}
|
234
|
-
end
|
235
|
-
end
|
236
|
-
end
|
237
|
-
end
|
238
|
-
end
|
data/lib/hanami/routing/error.rb
DELETED
@@ -1,212 +0,0 @@
|
|
1
|
-
require 'rack/request'
|
2
|
-
require 'hanami/utils/deprecation'
|
3
|
-
|
4
|
-
module Hanami
|
5
|
-
module Routing
|
6
|
-
# Force ssl
|
7
|
-
#
|
8
|
-
# Redirect response to the secure equivalent resource (https)
|
9
|
-
#
|
10
|
-
# @since 0.4.1
|
11
|
-
# @api private
|
12
|
-
class ForceSsl
|
13
|
-
# Https scheme
|
14
|
-
#
|
15
|
-
# @since 0.4.1
|
16
|
-
# @api private
|
17
|
-
SSL_SCHEME = 'https'.freeze
|
18
|
-
|
19
|
-
# @since 0.4.1
|
20
|
-
# @api private
|
21
|
-
HTTPS = 'HTTPS'.freeze
|
22
|
-
|
23
|
-
# @since 0.4.1
|
24
|
-
# @api private
|
25
|
-
ON = 'on'.freeze
|
26
|
-
|
27
|
-
# Location header
|
28
|
-
#
|
29
|
-
# @since 0.4.1
|
30
|
-
# @api private
|
31
|
-
LOCATION_HEADER = 'Location'.freeze
|
32
|
-
|
33
|
-
# Default http port
|
34
|
-
#
|
35
|
-
# @since 0.4.1
|
36
|
-
# @api private
|
37
|
-
DEFAULT_HTTP_PORT = 80
|
38
|
-
|
39
|
-
# Default ssl port
|
40
|
-
#
|
41
|
-
# @since 0.4.1
|
42
|
-
# @api private
|
43
|
-
DEFAULT_SSL_PORT = 443
|
44
|
-
|
45
|
-
# Moved Permanently http code
|
46
|
-
#
|
47
|
-
# @since 0.4.1
|
48
|
-
# @api private
|
49
|
-
MOVED_PERMANENTLY_HTTP_CODE = 301
|
50
|
-
|
51
|
-
# Temporary Redirect http code
|
52
|
-
#
|
53
|
-
# @since 0.4.1
|
54
|
-
# @api private
|
55
|
-
TEMPORARY_REDIRECT_HTTP_CODE = 307
|
56
|
-
|
57
|
-
# @since 0.4.1
|
58
|
-
# @api private
|
59
|
-
HTTP_X_FORWARDED_SSL = 'HTTP_X_FORWARDED_SSL'.freeze
|
60
|
-
|
61
|
-
# @since 0.4.1
|
62
|
-
# @api private
|
63
|
-
HTTP_X_FORWARDED_SCHEME = 'HTTP_X_FORWARDED_SCHEME'.freeze
|
64
|
-
|
65
|
-
# @since 0.4.1
|
66
|
-
# @api private
|
67
|
-
HTTP_X_FORWARDED_PROTO = 'HTTP_X_FORWARDED_PROTO'.freeze
|
68
|
-
|
69
|
-
# @since 0.4.1
|
70
|
-
# @api private
|
71
|
-
HTTP_X_FORWARDED_PROTO_SEPARATOR = ','.freeze
|
72
|
-
|
73
|
-
# @since 0.4.1
|
74
|
-
# @api private
|
75
|
-
RACK_URL_SCHEME = 'rack.url_scheme'.freeze
|
76
|
-
|
77
|
-
# @since 0.4.1
|
78
|
-
# @api private
|
79
|
-
REQUEST_METHOD = 'REQUEST_METHOD'.freeze
|
80
|
-
|
81
|
-
# @since 0.4.1
|
82
|
-
# @api private
|
83
|
-
IDEMPOTENT_METHODS = ['GET', 'HEAD'].freeze
|
84
|
-
|
85
|
-
EMPTY_BODY = [].freeze
|
86
|
-
|
87
|
-
# Initialize ForceSsl.
|
88
|
-
#
|
89
|
-
# @param active [Boolean] activate redirection to SSL
|
90
|
-
# @param options [Hash] set of options
|
91
|
-
# @option options [String] :host
|
92
|
-
# @option options [Integer] :port
|
93
|
-
#
|
94
|
-
# @since 0.4.1
|
95
|
-
# @api private
|
96
|
-
def initialize(active, options = {})
|
97
|
-
@active = active
|
98
|
-
@host = options[:host]
|
99
|
-
@port = options[:port]
|
100
|
-
|
101
|
-
_redefine_call
|
102
|
-
end
|
103
|
-
|
104
|
-
# Set 301 status and Location header if this feature is activated.
|
105
|
-
#
|
106
|
-
# @param env [Hash] a Rack env instance
|
107
|
-
#
|
108
|
-
# @return [Array]
|
109
|
-
#
|
110
|
-
# @see Hanami::Routing::HttpRouter#call
|
111
|
-
#
|
112
|
-
# @since 0.4.1
|
113
|
-
# @api private
|
114
|
-
def call(env)
|
115
|
-
end
|
116
|
-
|
117
|
-
# Check if router has to force the response with ssl
|
118
|
-
#
|
119
|
-
# @return [Boolean]
|
120
|
-
#
|
121
|
-
# @since 0.4.1
|
122
|
-
# @api private
|
123
|
-
def force?(env)
|
124
|
-
!ssl?(env)
|
125
|
-
end
|
126
|
-
|
127
|
-
private
|
128
|
-
|
129
|
-
# @since 0.4.1
|
130
|
-
# @api private
|
131
|
-
attr_reader :host
|
132
|
-
|
133
|
-
# Return full url to redirect
|
134
|
-
#
|
135
|
-
# @param env [Hash] Rack env
|
136
|
-
#
|
137
|
-
# @return [String]
|
138
|
-
#
|
139
|
-
# @since 0.4.1
|
140
|
-
# @api private
|
141
|
-
def full_url(env)
|
142
|
-
"#{ SSL_SCHEME }://#{ host }:#{ port }#{ ::Rack::Request.new(env).fullpath }"
|
143
|
-
end
|
144
|
-
|
145
|
-
# Return redirect code
|
146
|
-
#
|
147
|
-
# @param env [Hash] Rack env
|
148
|
-
#
|
149
|
-
# @return [Integer]
|
150
|
-
#
|
151
|
-
# @since 0.4.1
|
152
|
-
# @api private
|
153
|
-
def redirect_code(env)
|
154
|
-
if IDEMPOTENT_METHODS.include?(env[REQUEST_METHOD])
|
155
|
-
MOVED_PERMANENTLY_HTTP_CODE
|
156
|
-
else
|
157
|
-
TEMPORARY_REDIRECT_HTTP_CODE
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
# Return correct default port for full url
|
162
|
-
#
|
163
|
-
# @return [Integer]
|
164
|
-
#
|
165
|
-
# @since 0.4.1
|
166
|
-
# @api private
|
167
|
-
def port
|
168
|
-
if @port == DEFAULT_HTTP_PORT
|
169
|
-
DEFAULT_SSL_PORT
|
170
|
-
else
|
171
|
-
@port
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
|
-
# @since 0.4.1
|
176
|
-
# @api private
|
177
|
-
def _redefine_call
|
178
|
-
return unless @active
|
179
|
-
|
180
|
-
Hanami::Utils::Deprecation.new('force_ssl option is deprecated, please delegate this behaviour to Nginx/Apache or use a Rack middleware like `rack-ssl`')
|
181
|
-
|
182
|
-
define_singleton_method :call do |env|
|
183
|
-
[redirect_code(env), { LOCATION_HEADER => full_url(env) }, EMPTY_BODY] if force?(env)
|
184
|
-
end
|
185
|
-
end
|
186
|
-
|
187
|
-
# Adapted from Rack::Request#scheme
|
188
|
-
#
|
189
|
-
# @since 0.4.1
|
190
|
-
# @api private
|
191
|
-
def scheme(env)
|
192
|
-
if env[HTTPS] == ON
|
193
|
-
SSL_SCHEME
|
194
|
-
elsif env[HTTP_X_FORWARDED_SSL] == ON
|
195
|
-
SSL_SCHEME
|
196
|
-
elsif env[HTTP_X_FORWARDED_SCHEME]
|
197
|
-
env[HTTP_X_FORWARDED_SCHEME]
|
198
|
-
elsif env[HTTP_X_FORWARDED_PROTO]
|
199
|
-
env[HTTP_X_FORWARDED_PROTO].split(HTTP_X_FORWARDED_PROTO_SEPARATOR)[0]
|
200
|
-
else
|
201
|
-
env[RACK_URL_SCHEME]
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
|
-
# @since 0.4.1
|
206
|
-
# @api private
|
207
|
-
def ssl?(env)
|
208
|
-
scheme(env) == SSL_SCHEME
|
209
|
-
end
|
210
|
-
end
|
211
|
-
end
|
212
|
-
end
|