lennarb 0.1.5 → 0.1.7

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,69 +1,68 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Released under the MIT License.
4
+ # Copyright, 2023, by Aristóteles Coutinho.
5
+
3
6
  module Lenna
4
- class Router
5
- # @note This class is responsible for matching the request path
6
- # to an endpoint and executing the endpoint action.
7
- #
8
- # @api private
9
- #
10
- # @since 0.1.0
11
- #
12
- # This will match the request path to an endpoint and execute
13
- # the endpoint action.
14
- class RouteMatcher
15
- # @param root_node [Lenna::Node] The root node
16
- def initialize(root_node) = @root_node = root_node
7
+ class Router
8
+ # This class is responsible for matching the request path
9
+ # to an endpoint and executing the endpoint action.
10
+ #
11
+ # @private Since `v0.1.0`
12
+ #
13
+ # This will match the request path to an endpoint and execute
14
+ # the endpoint action.
15
+ #
16
+ class RouteMatcher
17
+ # @parameter root_node [Lenna::Node] The root node
18
+ #
19
+ def initialize(root_node) = @root_node = root_node
17
20
 
18
- # This method will match the request path to an endpoint and execute
19
- # the endpoint action.
20
- #
21
- # @param req [Lenna::Request] The request object
22
- # @param res [Lenna::Response] The response object
23
- # @return [Lenna::Response] The response object
24
- #
25
- # @see #split_path
26
- # @see #find_endpoint
27
- # @see Lenna::Response#not_found
28
- def match_and_execute_route(req, res)
29
- params = {}
30
- path_parts = split_path(req.path_info)
31
- endpoint = find_endpoint(@root_node, path_parts, params)
21
+ # This method will match the request path to an endpoint and execute
22
+ # the endpoint action.
23
+ #
24
+ # @parameter req [Lenna::Request] The request object
25
+ # @parameter res [Lenna::Response] The response object
26
+ # @return [Lenna::Response] The response object
27
+ #
28
+ def match_and_execute_route(req, res)
29
+ params = {}
30
+ path_parts = split_path(req.path_info)
31
+ endpoint = find_endpoint(@root_node, path_parts, params)
32
32
 
33
- if endpoint && (action = endpoint[req.request_method])
34
- req.params.merge!(params)
35
- action.call(req, res)
36
- else
37
- res.not_found
38
- end
39
- end
33
+ if endpoint && (action = endpoint[req.request_method])
34
+ req.assign_params(params)
35
+ action.call(req, res)
36
+ else
37
+ res.not_found
38
+ end
39
+ end
40
40
 
41
- private
41
+ private
42
42
 
43
- # @todo: Refactor this method to a module.
44
- def split_path(path) = path.split('/').reject(&:empty?)
43
+ def split_path(path) = path.split('/').reject(&:empty?)
45
44
 
46
- # @param node [Lenna::Node] The node to search
47
- # @param parts [Array] The path parts
48
- # @param params [Hash] The params hash
49
- # @return [Lenna::Node] The node that matches the path
50
- #
51
- # @note This method is recursive.
52
- #
53
- # @since 0.1.0
54
- def find_endpoint(node, parts, params)
55
- return node.endpoint if parts.empty?
45
+ # @parameter node [Lenna::Node] The node to search
46
+ # @parameter parts [Array] The path parts
47
+ # @parameter params [Hash] The params hash
48
+ #
49
+ # @return [Lenna::Node] The node that matches the path
50
+ #
51
+ # This method is recursive.
52
+ #
53
+ def find_endpoint(node, parts, params)
54
+ return node.endpoint if parts.empty?
56
55
 
57
- part = parts.shift
58
- child_node = node.children[part]
56
+ part = parts.shift
57
+ child_node = node.children[part]
59
58
 
60
- if child_node.nil? && (placeholder_node = node.children[:placeholder])
61
- params[placeholder_node.placeholder_name] = part
62
- child_node = placeholder_node
63
- end
59
+ if child_node.nil? && (placeholder_node = node.children[:placeholder])
60
+ params[placeholder_node.placeholder_name] = part
61
+ child_node = placeholder_node
62
+ end
64
63
 
65
- find_endpoint(child_node, parts, params) if child_node
66
- end
67
- end
68
- end
64
+ find_endpoint(child_node, parts, params) if child_node
65
+ end
66
+ end
67
+ end
69
68
  end
data/lib/lenna/router.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Standard library dependencies
3
+ # Released under the MIT License.
4
+ # Copyright, 2023, by Aristóteles Coutinho.
5
+
4
6
  require 'erb'
5
7
  require 'json'
6
8
 
@@ -9,6 +11,8 @@ require 'rack'
9
11
 
10
12
  # Internal dependencies
11
13
  require 'lenna/middleware/app'
14
+ require 'lenna/middleware/default/error_handler'
15
+ require 'lenna/middleware/default/logging'
12
16
  require 'lenna/router/builder'
13
17
  require 'lenna/router/cache'
14
18
  require 'lenna/router/namespace_stack'
@@ -17,157 +21,186 @@ require 'lenna/router/response'
17
21
  require 'lenna/router/route_matcher'
18
22
 
19
23
  module Lenna
20
- # The Node struct is used to represent a node in the tree of routes.
21
- # @attr children [Hash] the children of the node
22
- # @attr endpoint [String] the endpoint of the node
23
- # @attr placeholder_name [String] the name of the placeholder
24
- # @attr placeholder [Bool] whether the node is a placeholder
25
- Node =
26
- ::Struct.new(:children, :endpoint, :placeholder_name, :placeholder) do
27
- def initialize(children = {}, endpoint = nil, placeholder_name = nil)
28
- super(children, endpoint, placeholder_name, !placeholder_name.nil?)
29
- end
30
- end
31
- public_constant :Node
32
-
33
- # The router class is responsible for adding routes and calling the
34
- # middleware chain for each request.
35
- class Router
36
- # @return [Node] the root node of the tree of routes
37
- attr_reader :root_node
38
-
39
- # @return [Route::Cache] the cache of routes
40
- attr_reader :cache
41
-
42
- # @return [Route::NamespaceStack] the stack of namespaces
43
- attr_reader :namespace_stack
44
-
45
- # @return [MiddlewareManager] the middleware manager
46
- attr_reader :middleware_manager
47
-
48
- # @return [Route::Builder] the route builder
49
- attr_reader :roter_builder
50
-
51
- # @return [void]
52
- def initialize(middleware_manager: Middleware::App.new, cache: Cache.new)
53
- @cache = cache
54
- @root_node = Node.new({}, nil)
55
- @middleware_manager = middleware_manager
56
- @namespace_stack = NamespaceStack.new
57
- @roter_builder = Builder.new(@root_node)
58
- end
59
-
60
- # This method is used to add a namespace to the routes.
61
- #
62
- # @param prefix [String] the prefix to be used
63
- # @param block [Proc] the block to be executed
64
- # @return [void]
65
- # @note This method is used to add a namespaces
66
- # to the routes.
67
- #
68
- # @since 0.1.0
69
- #
70
- # @see Route::NamespaceStack#push
71
- #
72
- # @example:
73
- #
74
- # namespace '/api' do |route|
75
- # route.get '/users' do
76
- # # ...
77
- # end
78
- # end
79
- def namespace(prefix, &block)
80
- @namespace_stack.push(prefix)
81
- block.call(self)
82
- @namespace_stack.pop
83
- end
84
-
85
- # This method is used to add a middleware to the middleware manager.
86
- #
87
- # @see MiddlewareManager#use
88
- #
89
- # @since 0.1.0
90
- #
91
- # @param middlewares [Array] the middlewares to be used
92
- # @return [void]
93
- def use(*middlewares)
94
- @middleware_manager.use(middlewares)
95
- end
96
-
97
- # Proxy methods to add routes
98
- #
99
- # @see #add_route
100
- #
101
- # @since 0.1.0
102
- #
103
- # @param path [String] the path to be matched
104
- # @param middlewares [Array] the middlewares to be used
105
- # @param action [Proc] the action to be executed
106
- # @return [Lennarb::Route] the route that was added
107
- def get(path, *, &) = add_route(::Rack::GET, path, *, &)
108
- def put(path, *, &) = add_route(::Rack::PUT, path, *, &)
109
- def post(path, *, &) = add_route(::Rack::POST, path, *, &)
110
- def delete(path, *, &) = add_route(::Rack::DELETE, path, *, &)
111
-
112
- # @see #call!
113
- def call(env) = dup.call!(env)
114
-
115
- # This method is used to call the middleware chain for each request.
116
- #
117
- # @param env [Hash] the Rack env
118
- # @return [Array] the Lennarb::Response
119
- #
120
- # @since 0.1.0
121
- def call!(env)
122
- # TODO: - Remove this after.
123
- # env.fetch('RACK_ENV', 'development')
124
- env['RACK_ENV'] ||= 'development'
125
-
126
- middleware_pipeline = @middleware_manager.fetch_or_build_middleware_chain(
127
- method(:process_request), []
128
- )
129
-
130
- req = Request.new(env)
131
- res = Response.new
132
-
133
- middleware_pipeline.call(req, res)
134
-
135
- res.finish
136
- end
137
-
138
- private
139
-
140
- # This method is used to add a route to the tree of routes.
141
- #
142
- # @see MiddlewareManager#build_middleware_chain
143
- # @see Route::Cache#add
144
- # @see Route::Builder#call
145
- # @see Route::NamespaceStack#current_prefix
146
- #
147
- # @since 0.1.0
148
- #
149
- # @return [Lenna::Route] the route that was added
150
- def add_route(http_method, path, *middlewares, &action)
151
- full_path = @namespace_stack.current_prefix + path
152
-
153
- middleware_chain = @middleware_manager.build_middleware_chain(
154
- action,
155
- middlewares
156
- )
157
-
158
- @roter_builder.call(http_method, full_path, middleware_chain, @cache)
159
- end
160
-
161
- # This method is used to process the request.
162
- #
163
- # @see Route::Matcher#match_and_execute_route
164
- #
165
- # @param req [Request] the request
166
- # @param res [Response] the response
167
- # @return [void]
168
- def process_request(req, res)
169
- @route_matcher ||= RouteMatcher.new(@root_node)
170
- @route_matcher.match_and_execute_route(req, res)
171
- end
172
- end
24
+ # The Node struct is used to represent a node in the tree of routes.
25
+ #
26
+ # @attr children [Hash] the children of the node
27
+ # @attr endpoint [String] the endpoint of the node
28
+ # @attr placeholder_name [String] the name of the placeholder
29
+ # @attr placeholder [Bool] whether the node is a placeholder
30
+ #
31
+ Node =
32
+ ::Struct.new(:children, :endpoint, :placeholder_name, :placeholder) do
33
+ def initialize(children = {}, endpoint = nil, placeholder_name = nil)
34
+ super(children, endpoint, placeholder_name, !placeholder_name.nil?)
35
+ end
36
+ end
37
+ public_constant :Node
38
+
39
+ # The router class is responsible for adding routes and calling the
40
+ # middleware chain for each request.
41
+ #
42
+ # @private
43
+ #
44
+ class Router
45
+ # @return [Node] the root node of the tree of routes
46
+ #
47
+ attr_reader :root_node
48
+
49
+ # @return [Route::Cache] the cache of routes
50
+ #
51
+ attr_reader :cache
52
+
53
+ # @return [Route::NamespaceStack] the stack of namespaces
54
+ #
55
+ attr_reader :namespace_stack
56
+
57
+ # @return [MiddlewareManager] the middleware manager
58
+ #
59
+ attr_reader :middleware_manager
60
+
61
+ # @return [Route::Builder] the route builder
62
+ #
63
+ attr_reader :roter_builder
64
+
65
+ # This method is used to initialize the router.
66
+ # By default, it uses the App middleware and the Cache.
67
+ #
68
+ # @return [void]
69
+ #
70
+ def initialize(middleware_manager: Middleware::App.instance, cache: Cache.new)
71
+ @cache = cache
72
+ @root_node = Node.new({}, nil)
73
+ @middleware_manager = middleware_manager
74
+ @namespace_stack = NamespaceStack.new
75
+ @roter_builder = Builder.new(@root_node)
76
+ end
77
+
78
+ # This method is used to add a namespace to the routes.
79
+ #
80
+ # @parameter prefix [String] the prefix to be used
81
+ # @parameter block [Proc] the block to be executed
82
+ #
83
+ # @return [void]
84
+ #
85
+ # @public `Since v0.1.0`
86
+ #
87
+ # see {Route::NamespaceStack#push}
88
+ #
89
+ # ex.
90
+ #
91
+ # namespace '/api' do |route|
92
+ # route.get '/users' do
93
+ # # ...
94
+ # end
95
+ # end
96
+ #
97
+ def namespace(prefix, &block)
98
+ @namespace_stack.push(prefix)
99
+ block.call(self)
100
+ @namespace_stack.pop
101
+ end
102
+
103
+ # This method is used to add a middleware to the middleware manager.
104
+ #
105
+ # See {MiddlewareManager#use}
106
+ #
107
+ # @public `Since v0.1.0`
108
+ #
109
+ #
110
+ # @parameter middlewares [Array] the middlewares to be used
111
+ #
112
+ # @return [void]
113
+ #
114
+ def use(*middlewares) = @middleware_manager.use(middlewares)
115
+
116
+ # Proxy methods to add routes
117
+ #
118
+ # @parameter path [String] the path to be matched
119
+ # @parameter * [Array] the middlewares to be used
120
+ # @yield { ... } the block to be executed
121
+ #
122
+ # @return [void]
123
+ #
124
+ # see {#add_route}
125
+ #
126
+ # @public `Since v0.1`
127
+ def get(path, *, &) = add_route(::Rack::GET, path, *, &)
128
+ def put(path, *, &) = add_route(::Rack::PUT, path, *, &)
129
+ def post(path, *, &) = add_route(::Rack::POST, path, *, &)
130
+ def delete(path, *, &) = add_route(::Rack::DELETE, path, *, &)
131
+
132
+ def call(env) = dup.call!(env)
133
+
134
+ # This method is used to call the middleware chain for each request.
135
+ # It also sets the RACK_ENV to development if it is not set.
136
+ #
137
+ # @parameter env [Hash] the Rack env
138
+ #
139
+ # @return [Array] the Lennarb::Response
140
+ #
141
+ def call!(env)
142
+ # TODO: Use pifano to set middlewares by environment.
143
+ #
144
+ middlewares_by_enviroment =
145
+ if ENV['RACK_ENV'] == 'development'
146
+ [
147
+ Middleware::Default::Logging,
148
+ Middleware::Default::ErrorHandler
149
+ ]
150
+ else
151
+ []
152
+ end
153
+
154
+ middleware_pipeline = @middleware_manager.fetch_or_build_middleware_chain(
155
+ method(:process_request),
156
+ middlewares_by_enviroment
157
+ )
158
+
159
+ req = Request.new(env)
160
+ res = Response.new
161
+
162
+ middleware_pipeline.call(req, res)
163
+
164
+ res.finish
165
+ end
166
+
167
+ private
168
+
169
+ # This method is used to add a route to the tree of routes.
170
+ #
171
+ # @parameter http_method [Rack::Method] the http method to be used
172
+ # @parameter path [String] the path to be matched
173
+ # @parameter middlewares [Array] the middlewares to be used
174
+ # @parameter action [Proc] the action to be executed
175
+ #
176
+ # see {MiddlewareManager#build_middleware_chain}
177
+ # see {Route::Cache#add}
178
+ # see {Route::Builder#call}
179
+ # see {Route::NamespaceStack#current_prefix}
180
+ #
181
+ # @return [void]
182
+ #
183
+ def add_route(http_method, path, *middlewares, &action)
184
+ full_path = @namespace_stack.current_prefix + path
185
+
186
+ middleware_chain = @middleware_manager.fetch_or_build_middleware_chain(action, middlewares, http_method:, path:)
187
+
188
+ @roter_builder.call(http_method, full_path, middleware_chain, @cache)
189
+ end
190
+
191
+ # This method is used to process the request.
192
+ #
193
+ # It uses the Route::Matcher to match the request to a route and
194
+ # execute the action.
195
+ #
196
+ # @parameter req [Request] the request
197
+ # @parameter res [Response] the response
198
+ #
199
+ # @return [void]
200
+ #
201
+ def process_request(req, res)
202
+ @route_matcher ||= RouteMatcher.new(@root_node)
203
+ @route_matcher.match_and_execute_route(req, res)
204
+ end
205
+ end
173
206
  end
@@ -1,17 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Released under the MIT License.
4
+ # Copyright, 2023, by Aristóteles Coutinho.
5
+
3
6
  module Lennarb
4
- module ArrayExtensions
5
- def wrap(object)
6
- if object.nil?
7
- []
8
- elsif object.respond_to?(:to_ary)
9
- object.to_ary || [object]
10
- else
11
- [object]
12
- end
13
- end
14
- end
7
+ # The ArrayExtensions module is used to add the wrap method to the Array
8
+ # class.
9
+ #
10
+ # @public Since `v0.1.0`
11
+ #
12
+ module ArrayExtensions
13
+ # Wraps the object in an array if it is not already an array.
14
+ #
15
+ # @param object [Object] the object to wrap
16
+ #
17
+ # @return [Array] the wrapped object
18
+ #
19
+ def wrap(object)
20
+ if object.nil?
21
+ []
22
+ elsif object.respond_to?(:to_ary)
23
+ object.to_ary || [object]
24
+ else
25
+ [object]
26
+ end
27
+ end
28
+ end
15
29
  end
16
30
 
17
31
  Array.extend(Lennarb::ArrayExtensions)
@@ -1,7 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Released under the MIT License.
4
+ # Copyright, 2023, by Aristóteles Coutinho.
5
+
3
6
  module Lennarb
4
- VERSION = '0.1.5'
7
+ VERSION = '0.1.7'
5
8
 
6
- public_constant :VERSION
9
+ public_constant :VERSION
7
10
  end
data/lib/lennarb.rb CHANGED
@@ -1,8 +1,46 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Released under the MIT License.
4
+ # Copyright, 2023, by Aristóteles Coutinho.
5
+
6
+ ENV['RACK_ENV'] ||= 'development'
7
+
3
8
  # Extension for Array class
9
+ #
4
10
  require 'lennarb/array_extensions'
5
11
 
6
12
  # Base class for Lennarb
7
- require 'lenna/base'
13
+ #
14
+ require 'lenna/application'
8
15
  require 'lennarb/version'
16
+
17
+ # Core extensions
18
+ #
19
+ require 'pathname'
20
+
21
+ # Zeitwerk
22
+ #
23
+ require 'zeitwerk'
24
+
25
+ # Zeitwerk loader
26
+ #
27
+ Zeitwerk::Loader.new.tap do |loader|
28
+ loader.inflector.inflect('Version' => 'VERSION')
29
+ loader.push_dir(__dir__)
30
+ loader.setup
31
+ end
32
+
33
+ # Lennarb module
34
+ #
35
+ module Lennarb
36
+ module_function
37
+
38
+ # Lennarb root path
39
+ #
40
+ # @return [Pathname] the root path
41
+ #
42
+ def root
43
+ File.expand_path('..', __dir__)
44
+ Pathname.new(File.expand_path('..', __dir__))
45
+ end
46
+ end
data/license.md ADDED
@@ -0,0 +1,21 @@
1
+ # MIT License
2
+
3
+ Copyright, 2023, by Aristóteles Coutinho.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/readme.md ADDED
@@ -0,0 +1,31 @@
1
+ # Lennarb
2
+
3
+ Lennarb is a experimental lightweight, fast, and modular web framework for Ruby based on Rack.
4
+
5
+ ## Usage
6
+
7
+ Please see the [project documentation](https://aristotelesbr.github.io/lennarb) for more details.
8
+
9
+ - [Getting Started](https://aristotelesbr.github.io/lennarbguides/getting-started/index) - This guide show you how to use the `lennarb`
10
+
11
+ - [Middlewares](https://aristotelesbr.github.io/lennarbguides/middlewares/index) - This guide shows how to use middlewares in Lennarb.
12
+
13
+ - [Namespace routes](https://aristotelesbr.github.io/lennarbguides/namespace-routes/index) - This guide show you how to use namespace routes.
14
+
15
+ ## Contributing
16
+
17
+ We welcome contributions to this project.
18
+
19
+ 1. Fork it.
20
+ 2. Create your feature branch (`git checkout -b my-new-feature`).
21
+ 3. Commit your changes (`git commit -am 'Add some feature'`).
22
+ 4. Push to the branch (`git push origin my-new-feature`).
23
+ 5. Create new Pull Request.
24
+
25
+ ### Developer Certificate of Origin
26
+
27
+ This project uses the [Developer Certificate of Origin](https://developercertificate.org/). All contributors to this project must agree to this document to have their contributions accepted.
28
+
29
+ ### Contributor Covenant
30
+
31
+ This project is governed by the [Contributor Covenant](https://www.contributor-covenant.org/). All contributors and participants agree to abide by its terms.