lennarb 0.1.5 → 0.1.6

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,185 @@ 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(
71
+ middleware_manager: Middleware::App.instance,
72
+ cache: Cache.new
73
+ )
74
+ @cache = cache
75
+ @root_node = Node.new({}, nil)
76
+ @middleware_manager = middleware_manager
77
+ @namespace_stack = NamespaceStack.new
78
+ @roter_builder = Builder.new(@root_node)
79
+ end
80
+
81
+ # This method is used to add a namespace to the routes.
82
+ #
83
+ # @parameter prefix [String] the prefix to be used
84
+ # @parameter block [Proc] the block to be executed
85
+ #
86
+ # @return [void]
87
+ #
88
+ # @public `Since v0.1.0`
89
+ #
90
+ # see {Route::NamespaceStack#push}
91
+ #
92
+ # ex.
93
+ #
94
+ # namespace '/api' do |route|
95
+ # route.get '/users' do
96
+ # # ...
97
+ # end
98
+ # end
99
+ #
100
+ def namespace(prefix, &block)
101
+ @namespace_stack.push(prefix)
102
+ block.call(self)
103
+ @namespace_stack.pop
104
+ end
105
+
106
+ # This method is used to add a middleware to the middleware manager.
107
+ #
108
+ # See {MiddlewareManager#use}
109
+ #
110
+ # @public `Since v0.1.0`
111
+ #
112
+ #
113
+ # @parameter middlewares [Array] the middlewares to be used
114
+ #
115
+ # @return [void]
116
+ #
117
+ def use(*middlewares) = @middleware_manager.use(middlewares)
118
+
119
+ # Proxy methods to add routes
120
+ #
121
+ # @parameter path [String] the path to be matched
122
+ # @parameter * [Array] the middlewares to be used
123
+ # @yield { ... } the block to be executed
124
+ #
125
+ # @return [void]
126
+ #
127
+ # see {#add_route}
128
+ #
129
+ # @public `Since v0.1`
130
+ def get(path, *, &) = add_route(::Rack::GET, path, *, &)
131
+ def put(path, *, &) = add_route(::Rack::PUT, path, *, &)
132
+ def post(path, *, &) = add_route(::Rack::POST, path, *, &)
133
+ def delete(path, *, &) = add_route(::Rack::DELETE, path, *, &)
134
+
135
+ def call(env) = dup.call!(env)
136
+
137
+ # This method is used to call the middleware chain for each request.
138
+ # It also sets the RACK_ENV to development if it is not set.
139
+ #
140
+ # @parameter env [Hash] the Rack env
141
+ #
142
+ # @return [Array] the Lennarb::Response
143
+ #
144
+ def call!(env)
145
+ middleware_pipeline = @middleware_manager.fetch_or_build_middleware_chain(
146
+ method(:process_request),
147
+ [
148
+ Middleware::Default::Logging,
149
+ Middleware::Default::ErrorHandler
150
+ ]
151
+ )
152
+
153
+ req = Request.new(env)
154
+ res = Response.new
155
+
156
+ middleware_pipeline.call(req, res)
157
+
158
+ res.finish
159
+ end
160
+
161
+ private
162
+
163
+ # This method is used to add a route to the tree of routes.
164
+ #
165
+ # @parameter http_method [Rack::Method] the http method to be used
166
+ # @parameter path [String] the path to be matched
167
+ # @parameter middlewares [Array] the middlewares to be used
168
+ # @parameter action [Proc] the action to be executed
169
+ #
170
+ # see {MiddlewareManager#build_middleware_chain}
171
+ # see {Route::Cache#add}
172
+ # see {Route::Builder#call}
173
+ # see {Route::NamespaceStack#current_prefix}
174
+ #
175
+ # @return [void]
176
+ #
177
+ def add_route(http_method, path, *middlewares, &action)
178
+ full_path = @namespace_stack.current_prefix + path
179
+
180
+ middleware_chain = @middleware_manager.fetch_or_build_middleware_chain(
181
+ action,
182
+ middlewares,
183
+ http_method:,
184
+ path:
185
+ )
186
+
187
+ @roter_builder.call(http_method, full_path, middleware_chain, @cache)
188
+ end
189
+
190
+ # This method is used to process the request.
191
+ #
192
+ # It uses the Route::Matcher to match the request to a route and
193
+ # execute the action.
194
+ #
195
+ # @parameter req [Request] the request
196
+ # @parameter res [Response] the response
197
+ #
198
+ # @return [void]
199
+ #
200
+ def process_request(req, res)
201
+ @route_matcher ||= RouteMatcher.new(@root_node)
202
+ @route_matcher.match_and_execute_route(req, res)
203
+ end
204
+ end
173
205
  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.6'
5
8
 
6
- public_constant :VERSION
9
+ public_constant :VERSION
7
10
  end
data/lib/lennarb.rb CHANGED
@@ -1,8 +1,15 @@
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'
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.