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.
- checksums.yaml +4 -4
- data/changelog.md +165 -0
- data/lib/lenna/application.rb +53 -0
- data/lib/lenna/middleware/app.rb +107 -92
- data/lib/lenna/middleware/default/error_handler.rb +184 -179
- data/lib/lenna/middleware/default/logging.rb +79 -81
- data/lib/lenna/router/builder.rb +111 -86
- data/lib/lenna/router/cache.rb +44 -30
- data/lib/lenna/router/namespace_stack.rb +66 -62
- data/lib/lenna/router/request.rb +125 -101
- data/lib/lenna/router/response.rb +505 -375
- data/lib/lenna/router/route_matcher.rb +56 -57
- data/lib/lenna/router.rb +186 -154
- data/lib/lennarb/array_extensions.rb +25 -11
- data/lib/lennarb/version.rb +5 -2
- data/lib/lennarb.rb +8 -1
- data/license.md +21 -0
- data/readme.md +31 -0
- metadata +64 -56
- data/CHANGELOG.md +0 -62
- data/LICENCE +0 -24
- data/README.md +0 -229
- data/lib/lenna/base.rb +0 -52
@@ -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
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
-
|
41
|
+
private
|
42
42
|
|
43
|
-
|
44
|
-
def split_path(path) = path.split('/').reject(&:empty?)
|
43
|
+
def split_path(path) = path.split('/').reject(&:empty?)
|
45
44
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
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
|
-
|
58
|
-
|
56
|
+
part = parts.shift
|
57
|
+
child_node = node.children[part]
|
59
58
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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
|
-
#
|
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
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
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
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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)
|
data/lib/lennarb/version.rb
CHANGED
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
|
-
|
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.
|