lennarb 0.1.5 → 0.1.7

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