hanami-router 1.3.1 → 2.0.0.alpha4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +44 -0
  3. data/LICENSE.md +1 -1
  4. data/README.md +97 -443
  5. data/hanami-router.gemspec +23 -19
  6. data/lib/hanami/middleware/body_parser.rb +21 -15
  7. data/lib/hanami/middleware/body_parser/class_interface.rb +62 -56
  8. data/lib/hanami/middleware/body_parser/errors.rb +7 -4
  9. data/lib/hanami/middleware/body_parser/json_parser.rb +9 -7
  10. data/lib/hanami/middleware/body_parser/parser.rb +58 -0
  11. data/lib/hanami/middleware/error.rb +16 -0
  12. data/lib/hanami/router.rb +548 -957
  13. data/lib/hanami/router/block.rb +88 -0
  14. data/lib/hanami/router/error.rb +67 -0
  15. data/lib/hanami/router/node.rb +91 -0
  16. data/lib/hanami/router/params.rb +35 -0
  17. data/lib/hanami/router/prefix.rb +67 -0
  18. data/lib/hanami/router/recognized_route.rb +92 -0
  19. data/lib/hanami/router/redirect.rb +28 -0
  20. data/lib/hanami/router/segment.rb +19 -0
  21. data/lib/hanami/router/trie.rb +63 -0
  22. data/lib/hanami/router/url_helpers.rb +40 -0
  23. data/lib/hanami/router/version.rb +4 -1
  24. metadata +61 -41
  25. data/lib/hanami-router.rb +0 -1
  26. data/lib/hanami/routing/endpoint.rb +0 -195
  27. data/lib/hanami/routing/endpoint_resolver.rb +0 -238
  28. data/lib/hanami/routing/error.rb +0 -7
  29. data/lib/hanami/routing/force_ssl.rb +0 -212
  30. data/lib/hanami/routing/http_router.rb +0 -220
  31. data/lib/hanami/routing/http_router_monkey_patch.rb +0 -38
  32. data/lib/hanami/routing/namespace.rb +0 -98
  33. data/lib/hanami/routing/parsers.rb +0 -113
  34. data/lib/hanami/routing/parsing/json_parser.rb +0 -33
  35. data/lib/hanami/routing/parsing/parser.rb +0 -61
  36. data/lib/hanami/routing/recognized_route.rb +0 -219
  37. data/lib/hanami/routing/resource.rb +0 -119
  38. data/lib/hanami/routing/resource/action.rb +0 -402
  39. data/lib/hanami/routing/resource/nested.rb +0 -41
  40. data/lib/hanami/routing/resource/options.rb +0 -74
  41. data/lib/hanami/routing/resources.rb +0 -48
  42. data/lib/hanami/routing/resources/action.rb +0 -156
  43. data/lib/hanami/routing/route.rb +0 -71
  44. 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
@@ -1,7 +0,0 @@
1
- module Hanami
2
- module Routing
3
- # @since 0.5.0
4
- class Error < ::StandardError
5
- end
6
- end
7
- end
@@ -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