lennarb 0.1.6 → 0.2.0

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.
@@ -1,53 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Released under the MIT License.
4
- # Copyright, 2023, by Aristóteles Coutinho.
5
-
6
- # Internal dependencies
7
- #
8
- require 'lenna/middleware/default/error_handler'
9
- require 'lenna/middleware/default/logging'
10
- require 'lenna/router'
11
-
12
- # The Lenna module is used to namespace the framework.
13
- #
14
- # @public
15
- #
16
- module Lenna
17
- # The base class is used to start the server.
18
- #
19
- # @public
20
- #
21
- class Application < Router
22
- # Initialize the base class
23
- #
24
- # @yield { ... } the block to be evaluated in the context of the instance.
25
- #
26
- # @return [void | Application] Returns the instance if a block is given.
27
- #
28
- def initialize
29
- super
30
- yield self if block_given?
31
- end
32
- end
33
-
34
- # The base module is used to include the base class.
35
- #
36
- # @public
37
- #
38
- module Base
39
- def self.included(base)
40
- base.extend(ClassMethods)
41
- end
42
-
43
- module ClassMethods
44
- # Initialize the base module
45
- #
46
- # @return [Lenna::Application] Returns the instance.
47
- #
48
- # @public
49
- #
50
- def app = @app ||= Lenna::Application.new
51
- end
52
- end
53
- end
@@ -1,118 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Released under the MIT License.
4
- # Copyright, 2023, by Aristóteles Coutinho.
5
-
6
- require 'singleton'
7
-
8
- module Lenna
9
- module Middleware
10
- # The MiddlewareManager class is responsible for managing the middlewares.
11
- #
12
- # @attr global_middlewares [Array] the global middlewares
13
- # @attr middleware_chains_cache [Hash] the middleware chains cache
14
- #
15
- # Middleware chains are cached by action.
16
- # The middlewares that are added to a specific route are added to the
17
- # global middlewares.
18
- #
19
- # @private Since `v0.1.0`
20
- #
21
- class App
22
- include Singleton
23
-
24
- # @return [Array] the global middlewares.
25
- #
26
- attr_accessor :global_middlewares
27
- # @return [Hash] the middleware chains cache.
28
- #
29
- attr_accessor :middleware_chains_cache
30
-
31
- # This method will initialize the global middlewares and the
32
- # middleware chains cache.
33
- #
34
- # @return [void]
35
- #
36
- def initialize
37
- @global_middlewares = []
38
- @middleware_chains_cache = {}
39
- end
40
-
41
- # This method is used to reset the global middlewares and the middleware
42
- # chains cache.
43
- #
44
- # @return [void]
45
- #
46
- def reset!
47
- @global_middlewares = []
48
- @middleware_chains_cache = {}
49
- end
50
-
51
- # This method is used to add a middleware to the global middlewares.
52
- # @parameter middlewares [Array] the middlewares to be used
53
- # @return [void]
54
- #
55
- def use(middlewares)
56
- @global_middlewares += Array(middlewares)
57
- @middleware_chains_cache = {}
58
- end
59
-
60
- # This method is used to fetch or build the middleware chain for the given
61
- # action and route middlewares.
62
- #
63
- # @parameter action [Proc] the action to be executed
64
- # @parameter route_middlewares [Array] the middlewares to be used
65
- # @return [Proc] the middleware chain
66
- #
67
- #
68
- def fetch_or_build_middleware_chain(
69
- action,
70
- route_middlewares,
71
- http_method: nil,
72
- path: nil
73
- )
74
- signature =
75
- if http_method && path
76
- [http_method, path, route_middlewares].hash.to_s
77
- else
78
- ['global', route_middlewares].hash.to_s
79
- end
80
-
81
- @middleware_chains_cache[signature] ||=
82
- build_middleware_chain(action, route_middlewares)
83
- end
84
-
85
- # This method is used to build the middleware chain for the given action
86
- # and middlewares.
87
- #
88
- # @parameter action [Proc] the action to be executed
89
- # @parameter middlewares [Array] the middlewares to be used
90
- # @return [Proc] the middleware chain
91
- #
92
- # ex.
93
- # Given the action:
94
- # `->(req, res) { res << 'Hello' }` and the
95
- # middlewares [mw1, mw2], the middleware
96
- # chain will be:
97
- # mw1 -> mw2 -> action
98
- # The action will be the last middleware in the
99
- # chain.
100
- #
101
- def build_middleware_chain(action, middlewares)
102
- all_middlewares = (@global_middlewares + Array(middlewares))
103
-
104
- all_middlewares.reverse.reduce(action) do |next_middleware, middleware|
105
- ->(req, res) {
106
- middleware.call(
107
- req,
108
- res,
109
- -> {
110
- next_middleware.call(req, res)
111
- }
112
- )
113
- }
114
- end
115
- end
116
- end
117
- end
118
- end
@@ -1,243 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Released under the MIT License.
4
- # Copyright, 2023, by Aristóteles Coutinho.
5
-
6
- require 'cgi'
7
-
8
- module Lenna
9
- module Middleware
10
- module Default
11
- # This middleware will handle errors.
12
- #
13
- # @private Since `v0.1.0`
14
- #
15
- module ErrorHandler
16
- extend self
17
-
18
- # This method will be called by the server.
19
- #
20
- # @parameter req [Rack::Request] The request object
21
- # @parameter res [Rack::Response] The response object
22
- # @parameter next_middleware [Proc] The next middleware in the stack
23
- #
24
- # @return [void]
25
- #
26
- def call(req, res, next_middleware)
27
- next_middleware.call
28
- rescue StandardError => e
29
- env = req.env
30
- log_error(env, e)
31
-
32
- render_error_page(e, req, res)
33
- end
34
-
35
- private
36
-
37
- # This method will render the error page.
38
- #
39
- # @parameter error [StandardError] The error object
40
- # @parameter env [Hash] The environment variables
41
- # @parameter res [Rack::Response] The response object
42
- #
43
- # @return [void]
44
- #
45
- def render_error_page(error, req, res)
46
- case req.headers['Content-Type']
47
- in 'application/json' then render_json(error, res)
48
- else render_html(error, res)
49
- end
50
- end
51
-
52
- # This method will render the JSON error page.
53
- #
54
- # @parameter error [StandardError] The error object
55
- # @parameter env [Hash] The environment variables
56
- # @parameter res [Rack::Response] The response object
57
- #
58
- # @return [void]
59
- #
60
- def render_json(error, res)
61
- case ENV.fetch('RACK_ENV', 'development')
62
- in 'development' | 'test' then res.json(
63
- data: {
64
- error: error.message,
65
- status: 500
66
- }
67
- )
68
- else res.json(data: { error: 'Internal Server Error', status: 500 })
69
- end
70
- end
71
-
72
- # This method will render the HTML error page.
73
- #
74
- # @parameter error [StandardError] The error object
75
- # @parameter env [Hash] The environment variables
76
- # @parameter res [Rack::Response] The response object
77
- #
78
- # @return [void]
79
- #
80
- def render_html(error, res)
81
- res.html(error_page(error), status: 500)
82
- end
83
-
84
- # This method will log the error.
85
- #
86
- # @parameter env [Hash] The environment variables
87
- # @parameter error [StandardError] The error object
88
- #
89
- # @return [void]
90
- #
91
- def log_error(env, error)
92
- env['rack.errors'].puts error.message
93
- env['rack.errors'].puts error.backtrace.join("\n")
94
- env['rack.errors'].flush
95
- end
96
-
97
- # This method will render the error page.
98
- #
99
- def error_page(error)
100
- style = <<-STYLE
101
- <style>
102
- body { font-family: 'Helvetica Neue', sans-serif; background-color: #F7F7F7; color: #333; margin: 0; padding: 0; }
103
- .error-container { max-width: 600px; margin: 20px auto; padding: 20px; box-shadow: 0 4px 8px 0 rgba(0,0,0,0.1); background: white; border-radius: 4px; }
104
- h1 { color: #c0392b }
105
- h2 { padding: 0; margin: 0; font-size: 1.2em; }
106
- .error-details, .backtrace { text-align: left; }
107
- .error-details strong { color: #e74c3c; }
108
- pre { background: #ecf0f1; padding: 15px; overflow: auto; border-left: 5px solid #e74c3c; font-size: 0.9em; }
109
- .backtrace { margin-top: 20px; }
110
- .backtrace pre { border-color: #3498db; }
111
- svg { fill: #e74c3c; width: 50px; height: auto; }
112
- .container { display: flex; justify-content: space-between; align-items: center; align-content: center;}
113
- </style>
114
- STYLE
115
-
116
- # SVG logo
117
- svg_logo = <<~SVG
118
- <svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="100" height="100" viewBox="0 0 100 100">
119
- <path fill="#f1bc19" d="M77 12A1 1 0 1 0 77 14A1 1 0 1 0 77 12Z"></path><path fill="#e4e4f9" d="M50 13A37 37 0 1 0 50 87A37 37 0 1 0 50 13Z"></path><path fill="#f1bc19" d="M83 11A4 4 0 1 0 83 19A4 4 0 1 0 83 11Z"></path><path fill="#8889b9" d="M87 22A2 2 0 1 0 87 26A2 2 0 1 0 87 22Z"></path><path fill="#fbcd59" d="M81 74A2 2 0 1 0 81 78 2 2 0 1 0 81 74zM15 59A4 4 0 1 0 15 67 4 4 0 1 0 15 59z"></path><path fill="#8889b9" d="M25 85A2 2 0 1 0 25 89A2 2 0 1 0 25 85Z"></path><path fill="#fff" d="M18.5 49A2.5 2.5 0 1 0 18.5 54 2.5 2.5 0 1 0 18.5 49zM79.5 32A1.5 1.5 0 1 0 79.5 35 1.5 1.5 0 1 0 79.5 32z"></path><g><path fill="#fdfcee" d="M50 25.599999999999998A24.3 24.3 0 1 0 50 74.2A24.3 24.3 0 1 0 50 25.599999999999998Z"></path><path fill="#472b29" d="M50,74.8c-13.8,0-25-11.2-25-25c0-13.8,11.2-25,25-25c13.8,0,25,11.2,25,25C75,63.6,63.8,74.8,50,74.8z M50,26.3c-13,0-23.5,10.6-23.5,23.5S37,73.4,50,73.4s23.5-10.6,23.5-23.5S63,26.3,50,26.3z"></path></g><g><path fill="#ea5167" d="M49.9 29.6A20.4 20.4 0 1 0 49.9 70.4A20.4 20.4 0 1 0 49.9 29.6Z"></path></g><g><path fill="#ef7d99" d="M50.2,32.9c10.6,0,19.3,8.2,20.1,18.5c0-0.5,0.1-1,0.1-1.5c0-11-9-20-20.2-20c-11.1,0-20.2,9-20.2,20 c0,0.5,0,1,0.1,1.5C30.9,41,39.5,32.9,50.2,32.9z"></path></g><g><path fill="#472b29" d="M69.4,44.6c-0.2,0-0.4-0.1-0.5-0.4c-0.1-0.3-0.2-0.6-0.3-0.9c-0.4-1.1-0.9-2.2-1.5-3.2 c-0.1-0.2-0.1-0.5,0.2-0.7c0.2-0.1,0.5-0.1,0.7,0.2c0.6,1.1,1.1,2.2,1.5,3.3c0.1,0.3,0.2,0.6,0.3,0.9c0.1,0.3-0.1,0.5-0.3,0.6 C69.5,44.6,69.5,44.6,69.4,44.6z"></path></g><g><path fill="#472b29" d="M50,70.8c-11.5,0-20.9-9.3-20.9-20.8c0-11.5,9.4-20.8,20.9-20.8c6,0,11.7,2.6,15.6,7c0.3,0.3,0.6,0.7,0.9,1 c0.2,0.2,0.1,0.5-0.1,0.7c-0.2,0.2-0.5,0.1-0.7-0.1c-0.3-0.3-0.5-0.7-0.8-1c-3.8-4.2-9.2-6.7-14.9-6.7c-11,0-19.9,8.9-19.9,19.8 c0,10.9,8.9,19.8,19.9,19.8s19.9-8.9,19.9-19.8c0-1-0.1-2-0.2-3c0-0.3,0.1-0.5,0.4-0.6c0.3,0,0.5,0.1,0.6,0.4 c0.2,1,0.2,2.1,0.2,3.1C70.9,61.4,61.5,70.8,50,70.8z"></path></g><g><path fill="#fdfcee" d="M56,57.1c-0.3,0-0.6-0.1-0.9-0.4l-5.2-5.2l-5.2,5.2c-0.2,0.2-0.5,0.4-0.9,0.4s-0.6-0.1-0.9-0.4 c-0.5-0.5-0.5-1.2,0-1.7l5.2-5.2L43,44.6c-0.5-0.5-0.5-1.2,0-1.7c0.2-0.2,0.5-0.4,0.9-0.4s0.6,0.1,0.9,0.4l5.2,5.2l5.2-5.2 c0.2-0.2,0.5-0.4,0.9-0.4c0.3,0,0.6,0.1,0.9,0.4s0.4,0.5,0.4,0.9s-0.1,0.6-0.4,0.9l-5.2,5.2l5.2,5.2c0.2,0.2,0.4,0.5,0.4,0.9 c0,0.3-0.1,0.6-0.4,0.9S56.3,57.1,56,57.1z"></path><path fill="#472b29" d="M56,43.1c0.2,0,0.4,0.1,0.5,0.2c0.3,0.3,0.3,0.7,0,1l-5.5,5.5l5.5,5.5c0.3,0.3,0.3,0.7,0,1 c-0.1,0.1-0.3,0.2-0.5,0.2s-0.4-0.1-0.5-0.2l-5.5-5.5l-5.5,5.5c-0.1,0.1-0.3,0.2-0.5,0.2s-0.4-0.1-0.5-0.2c-0.3-0.3-0.3-0.7,0-1 l5.5-5.5l-5.5-5.5c-0.3-0.3-0.3-0.7,0-1c0.1-0.1,0.3-0.2,0.5-0.2s0.4,0.1,0.5,0.2l5.5,5.5l5.5-5.5C55.6,43.1,55.8,43.1,56,43.1 M56,42.1c-0.5,0-0.9,0.2-1.2,0.5l-4.8,4.8l-4.8-4.8c-0.3-0.3-0.8-0.5-1.2-0.5s-0.9,0.2-1.2,0.5c-0.3,0.3-0.5,0.8-0.5,1.2 s0.2,0.9,0.5,1.2l4.8,4.8l-4.8,4.8c-0.3,0.3-0.5,0.8-0.5,1.2s0.2,0.9,0.5,1.2c0.3,0.3,0.8,0.5,1.2,0.5s0.9-0.2,1.2-0.5l4.8-4.8 l4.8,4.8c0.3,0.3,0.8,0.5,1.2,0.5s0.9-0.2,1.2-0.5c0.3-0.3,0.5-0.8,0.5-1.2s-0.2-0.9-0.5-1.2l-4.8-4.8l4.8-4.8 c0.3-0.3,0.5-0.8,0.5-1.2s-0.2-0.9-0.5-1.2C56.9,42.2,56.4,42.1,56,42.1L56,42.1z"></path></g>
120
- </svg>
121
- #{' '}
122
- SVG
123
-
124
- # HTML page
125
- <<-HTML
126
- <!DOCTYPE html>
127
- <html lang="en">
128
- <head>
129
- <meta charset="UTF-8">
130
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
131
- #{' '}
132
- <title>System Error</title>
133
- #{style}
134
- </head>
135
- <body>
136
- <div class="error-container">
137
- <div class="container">
138
- <div class="item-left">
139
- <h1>Oops! An error has occurred.</h1>
140
- </div>
141
- <div class="item-right">#{svg_logo}</div>
142
- </div>
143
- #{' '}
144
- #{error_message_and_backtrace(error)}
145
- </div>
146
- </body>
147
- </html>
148
- HTML
149
- end
150
-
151
- # This method will render the error message and backtrace.
152
- #
153
- # @parameter error [StandardError] The error object
154
- # @parameter env [Hash] The environment variables
155
- #
156
- # @return [String] The HTML string
157
- #
158
- def error_message_and_backtrace(error)
159
- if ENV['RACK_ENV'] == 'development'
160
- truncated_message =
161
- error.message[0..500] + (error.message.length > 500 ? '...' : '')
162
-
163
- file, line = error.backtrace.first.split(':')
164
- line_number = Integer(line)
165
- <<-DETAILS
166
- <div class="error-details">
167
- <h2>Error Details:</h2>
168
- <p>
169
- <strong>Message:</strong> <span id="error-message">#{CGI.escapeHTML(truncated_message)}</span>
170
- </p>
171
- <div>
172
- <p><strong>Location:</strong> #{CGI.escapeHTML(file)}:#{line_number}</p>
173
- <pre>#{extract_source(file, line_number)}</pre>
174
- </div>
175
-
176
- <details>
177
- <summary>Details</summary>
178
- <p id="full-message" style="display: none;">
179
- <h3>Full Backtrace:</h3>
180
- <p><strong>Full Message:</strong> #{CGI.escapeHTML(error.message)}</p>
181
- <pre>#{error.backtrace.join("\n")}</pre>
182
- </p>
183
- </details>
184
- </div>
185
- DETAILS
186
- else
187
- "<p>We're sorry, but something went wrong. We've been notified " \
188
- 'about this issue and will take a look at it shortly.</p>'
189
- end
190
- end
191
-
192
- # This method will extract the source code.
193
- #
194
- # @parameter file [String] The file path
195
- # @parameter line_number [Integer] The line number
196
- #
197
- # @return [String] The HTML string
198
- #
199
- # ex.
200
- # extract_source('/path/to/file.rb', 10)
201
- # # => "<strong style='color: red;'> 7: </strong> =>
202
- # def foo\n<strong style='color: red;'> 8: </strong>
203
- # puts 'bar'\n<strong style='color: red;'> 9: </strong>
204
- # end\n<strong style='color: red;'> 10: </strong> foo\n<strong
205
- # style='color: red;'> 11: </strong> "
206
- #
207
- def extract_source(file, line_number)
208
- lines = ::File.readlines(file)
209
- start_line = [line_number - 3, 0].max
210
- end_line = [line_number + 3, lines.size].min
211
-
212
- line_ranger = lines[start_line...end_line]
213
-
214
- format_lines(line_ranger, line_number).join
215
- end
216
-
217
- # This method will format the lines.
218
- #
219
- # ex.
220
- # format_lines(line_ranger, line_number)
221
- # # => ["<strong style='color: red;'> 7: </strong> =>\n",
222
- # "<strong style='color: red;'> 8: </strong> puts 'bar'\n",
223
- # "<strong style='color: red;'> 9: </strong> end\n",
224
- # "<strong style='color: red;'> 10: </strong> foo\n",
225
- # "<strong style='color: red;'> 11: </strong> "]
226
- #
227
- def format_lines(lines, highlight_line)
228
- lines.map.with_index(highlight_line - 3 + 1) do |line, line_num|
229
- line_number_text = "#{line_num.to_s.rjust(6)}: "
230
- formatted_line = ::CGI.escapeHTML(line)
231
-
232
- if line_num == highlight_line
233
- "<strong style='color: red;'>#{line_number_text}</strong> " \
234
- "#{formatted_line}"
235
- else
236
- "#{line_number_text}#{formatted_line}"
237
- end
238
- end
239
- end
240
- end
241
- end
242
- end
243
- end
@@ -1,93 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Released under the MIT License.
4
- # Copyright, 2023, by Aristóteles Coutinho.
5
-
6
- require 'colorize'
7
-
8
- module Lenna
9
- module Middleware
10
- module Default
11
- # The Logging module is responsible for logging the requests.
12
- #
13
- # @private
14
- #
15
- module Logging
16
- extend self
17
-
18
- # This method is used to log the request.
19
- #
20
- # @parameter req [Rack::Request ] The request
21
- # @parameter res [Rack::Response] The response
22
- # @parameter next_middleware [Proc] The next middleware
23
- #
24
- def call(req, res, next_middleware)
25
- start_time = ::Time.now
26
- next_middleware.call
27
- end_time = ::Time.now
28
-
29
- http_method = colorize_http_method(req.request_method)
30
- status_code = colorize_status_code(res.status.to_s)
31
- duration = calculate_duration(start_time, end_time)
32
-
33
- log_message = "[#{start_time}] \"#{http_method} #{req.path_info}\" " \
34
- "#{status_code} #{format('%.2f', duration)}ms"
35
-
36
- ::Kernel.puts(log_message)
37
- end
38
-
39
- private
40
-
41
- # This method is used to colorize the request method.
42
- #
43
- # @parameter request_method [String] The request method
44
- #
45
- # @return [String] The colorized request method
46
- #
47
- # @private
48
- #
49
- def colorize_http_method(request_method)
50
- case request_method
51
- in 'GET' then 'GET'.green
52
- in 'POST' then 'POST'.magenta
53
- in 'PUT' then 'PUT'.yellow
54
- in 'DELETE' then 'DELETE'.red
55
- else request_method.blue
56
- end
57
- end
58
-
59
- # This method is used to colorize the status code.
60
- #
61
- # @param status_code [String] The status code
62
- #
63
- # @return [String] The colorized status code
64
- #
65
- # @private
66
- #
67
- def colorize_status_code(status_code)
68
- case status_code
69
- in '2' then status_code.green
70
- in '3' then status_code.blue
71
- in '4' then status_code.yellow
72
- in '5' then status_code.red
73
- else status_code
74
- end
75
- end
76
-
77
- # This method is used to calculate the duration.
78
- #
79
- # @param start_time [Time] The start time
80
- #
81
- # @param end_time [Time] The end time
82
- # @return [Float] The duration
83
- #
84
- # @api private
85
- #
86
- def calculate_duration(start_time, end_time)
87
- millis_in_second = 1000.0
88
- (end_time - start_time) * millis_in_second
89
- end
90
- end
91
- end
92
- end
93
- end
@@ -1,124 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Released under the MIT License.
4
- # Copyright, 2023, by Aristóteles Coutinho.
5
-
6
- module Lenna
7
- class Router
8
- # The Route::Builder class is responsible for building the tree of routes.
9
- #
10
- # The tree of routes is built by adding routes to the tree. Each route is
11
- # represented by a node in the tree and each node has a path and an
12
- # endpoint. The path is the path of the route and the endpoint is then
13
- # action to be executed when the route is matched.
14
- #
15
- # Those nodes are stored in a cache to avoid rebuilding the tree of routes
16
- # for each request.
17
- #
18
- # The tree use `Trie` data structures to optimize the search for a route.
19
- # The trie is a tree where each node is a character of the path.
20
- # This way, the search for a route is O(n) where n is the length of the
21
- # path.
22
- #
23
- # @private
24
- #
25
- class Builder
26
- def initialize(root_node) = @root_node = root_node
27
-
28
- # This method will add a route to the tree of routes.
29
- #
30
- # @parameter method [String] the HTTP method
31
- # @parameter path [String] the path to be matched
32
- # @parameter action [Proc] the action to be executed
33
- # @parameter cache [Cache] the cache to be used
34
- #
35
- # @return [void]
36
- #
37
- def call(method, path, action, cache)
38
- path_key = cache.cache_key(method, path)
39
-
40
- return if cache.exist?(path_key)
41
-
42
- current_node = find_or_create_route_node(path)
43
- setup_endpoint(current_node, method, action)
44
-
45
- cache.add(path_key, current_node)
46
- end
47
-
48
- private
49
-
50
- # This method will create routes that are missing.
51
- # @parameter path [String] the path to be matched
52
- #
53
- # @return [Node] the node that matches the path
54
- #
55
- def find_or_create_route_node(path)
56
- current_node = @root_node
57
- split_path(path).each do |part|
58
- current_node = find_or_create_node(current_node, part)
59
- end
60
- current_node
61
- end
62
-
63
- # This method will create the nodes that are missing.
64
- #
65
- # @parameter current_node [Node] the current node
66
- # @parameter part [String] the part of the path
67
- #
68
- # @return [Node] the node that matches the part of the path
69
- #
70
- # This way, the tree of routes is built.
71
- # @example Given the part ':id' and the tree bellow:
72
- # root
73
- # └── users
74
- # └── :id
75
- # The method will return the node :id.
76
- # If the node :id does not exist, it will be created.
77
- # The tree will be:
78
- # root
79
- # └── users
80
- # └── :id
81
- #
82
- def find_or_create_node(current_node, part)
83
- if part.start_with?(':')
84
- # If it is a placeholder, then we just create or update
85
- # the placeholder node with the placeholder name.
86
- placeholder_name = part[1..].to_sym
87
- current_node.children[:placeholder] ||= Node.new(
88
- {},
89
- nil,
90
- placeholder_name
91
- )
92
- else
93
- current_node.children[part] ||= Node.new
94
- end
95
- current_node.children[part.start_with?(':') ? :placeholder : part]
96
- end
97
-
98
- # This method will setup the endpoint of the current node.
99
- #
100
- # @parameter current_node [Node] the current node
101
- # @parameter method [String] the HTTP method
102
- # @parameter action [Proc] the action to be executed
103
- #
104
- # @return [void]
105
- #
106
- def setup_endpoint(current_node, method, action)
107
- current_node.endpoint ||= {}
108
- current_node.endpoint[method] = action
109
- end
110
-
111
- # This method will split the path into parts.
112
- #
113
- # @parameter path [String] the path to be split
114
- #
115
- # @return [Array] the splitted path
116
- #
117
- # TODO: Move this to a separate file and require it here.
118
- # Maybe utils or something like that.
119
- # Use Rack::Utils.split_path_info instead.
120
- #
121
- def split_path(path) = path.split('/').reject(&:empty?)
122
- end
123
- end
124
- end
@@ -1,52 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Released under the MIT License.
4
- # Copyright, 2023, by Aristóteles Coutinho.
5
-
6
- module Lenna
7
- class Router
8
- # This class is used to cache the routes.
9
- #
10
- # @private
11
- #
12
- class Cache
13
- def initialize = @cache = {}
14
-
15
- # This method is used to generate a key for the cache.
16
- #
17
- # @parameter [String] method
18
- # @parameter [String] path
19
- #
20
- # @return [String]
21
- #
22
- def cache_key(method, path) = "#{method} #{path}"
23
-
24
- # This method is used to add a route to the cache.
25
- #
26
- # @parameter route_key [String] The key for the route.
27
- # @parameter node [Lenna::Route::Node] The node for the route.
28
- #
29
- # @return [Lenna::Route::Node]
30
- #
31
- def add(route_key, node) = @cache[route_key] = node
32
-
33
- # This method is used to get a route from the cache.
34
- #
35
- # @parameter route_key [String] The key for the route.
36
- #
37
- # @return [Lenna::Route::Node]
38
- #
39
- def get(route_key) = @cache[route_key]
40
-
41
- # This method is used to check if a route is in the cache.
42
- #
43
- # @api public
44
- #
45
- # @parameter route_key [String] The key for the route.
46
- #
47
- # @return [Boolean]
48
- #
49
- def exist?(route_key) = @cache.key?(route_key)
50
- end
51
- end
52
- end