lennarb 0.1.4 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.4'
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.