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.
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