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
data/lib/lenna/router/builder.rb
CHANGED
@@ -1,99 +1,124 @@
|
|
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
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
7
|
+
class Router
|
8
|
+
# The Route::Builder class is responsible for building the tree of routes.
|
9
|
+
#
|
10
|
+
# The tree of routes is built by adding routes to the tree. Each route is
|
11
|
+
# represented by a node in the tree and each node has a path and an
|
12
|
+
# endpoint. The path is the path of the route and the endpoint is then
|
13
|
+
# action to be executed when the route is matched.
|
14
|
+
#
|
15
|
+
# Those nodes are stored in a cache to avoid rebuilding the tree of routes
|
16
|
+
# for each request.
|
17
|
+
#
|
18
|
+
# The tree use `Trie` data structures to optimize the search for a route.
|
19
|
+
# The trie is a tree where each node is a character of the path.
|
20
|
+
# This way, the search for a route is O(n) where n is the length of the
|
21
|
+
# path.
|
22
|
+
#
|
23
|
+
# @private
|
24
|
+
#
|
25
|
+
class Builder
|
26
|
+
def initialize(root_node) = @root_node = root_node
|
22
27
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
28
|
+
# This method will add a route to the tree of routes.
|
29
|
+
#
|
30
|
+
# @parameter method [String] the HTTP method
|
31
|
+
# @parameter path [String] the path to be matched
|
32
|
+
# @parameter action [Proc] the action to be executed
|
33
|
+
# @parameter cache [Cache] the cache to be used
|
34
|
+
#
|
35
|
+
# @return [void]
|
36
|
+
#
|
37
|
+
def call(method, path, action, cache)
|
38
|
+
path_key = cache.cache_key(method, path)
|
30
39
|
|
31
|
-
|
40
|
+
return if cache.exist?(path_key)
|
32
41
|
|
33
|
-
|
34
|
-
|
42
|
+
current_node = find_or_create_route_node(path)
|
43
|
+
setup_endpoint(current_node, method, action)
|
35
44
|
|
36
|
-
|
37
|
-
|
45
|
+
cache.add(path_key, current_node)
|
46
|
+
end
|
38
47
|
|
39
|
-
|
48
|
+
private
|
40
49
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
50
|
+
# This method will create routes that are missing.
|
51
|
+
# @parameter path [String] the path to be matched
|
52
|
+
#
|
53
|
+
# @return [Node] the node that matches the path
|
54
|
+
#
|
55
|
+
def find_or_create_route_node(path)
|
56
|
+
current_node = @root_node
|
57
|
+
split_path(path).each do |part|
|
58
|
+
current_node = find_or_create_node(current_node, part)
|
59
|
+
end
|
60
|
+
current_node
|
61
|
+
end
|
51
62
|
|
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
|
-
|
63
|
+
# This method will create the nodes that are missing.
|
64
|
+
#
|
65
|
+
# @parameter current_node [Node] the current node
|
66
|
+
# @parameter part [String] the part of the path
|
67
|
+
#
|
68
|
+
# @return [Node] the node that matches the part of the path
|
69
|
+
#
|
70
|
+
# This way, the tree of routes is built.
|
71
|
+
# @example Given the part ':id' and the tree bellow:
|
72
|
+
# root
|
73
|
+
# └── users
|
74
|
+
# └── :id
|
75
|
+
# The method will return the node :id.
|
76
|
+
# If the node :id does not exist, it will be created.
|
77
|
+
# The tree will be:
|
78
|
+
# root
|
79
|
+
# └── users
|
80
|
+
# └── :id
|
81
|
+
#
|
82
|
+
def find_or_create_node(current_node, part)
|
83
|
+
if part.start_with?(':')
|
84
|
+
# If it is a placeholder, then we just create or update
|
85
|
+
# the placeholder node with the placeholder name.
|
86
|
+
placeholder_name = part[1..].to_sym
|
87
|
+
current_node.children[:placeholder] ||= Node.new(
|
88
|
+
{},
|
89
|
+
nil,
|
90
|
+
placeholder_name
|
91
|
+
)
|
92
|
+
else
|
93
|
+
current_node.children[part] ||= Node.new
|
94
|
+
end
|
95
|
+
current_node.children[part.start_with?(':') ? :placeholder : part]
|
96
|
+
end
|
82
97
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
98
|
+
# This method will setup the endpoint of the current node.
|
99
|
+
#
|
100
|
+
# @parameter current_node [Node] the current node
|
101
|
+
# @parameter method [String] the HTTP method
|
102
|
+
# @parameter action [Proc] the action to be executed
|
103
|
+
#
|
104
|
+
# @return [void]
|
105
|
+
#
|
106
|
+
def setup_endpoint(current_node, method, action)
|
107
|
+
current_node.endpoint ||= {}
|
108
|
+
current_node.endpoint[method] = action
|
109
|
+
end
|
90
110
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
111
|
+
# This method will split the path into parts.
|
112
|
+
#
|
113
|
+
# @parameter path [String] the path to be split
|
114
|
+
#
|
115
|
+
# @return [Array] the splitted path
|
116
|
+
#
|
117
|
+
# TODO: Move this to a separate file and require it here.
|
118
|
+
# Maybe utils or something like that.
|
119
|
+
# Use Rack::Utils.split_path_info instead.
|
120
|
+
#
|
121
|
+
def split_path(path) = path.split('/').reject(&:empty?)
|
122
|
+
end
|
123
|
+
end
|
99
124
|
end
|
data/lib/lenna/router/cache.rb
CHANGED
@@ -1,38 +1,52 @@
|
|
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
|
-
|
7
|
+
class Router
|
8
|
+
# This class is used to cache the routes.
|
9
|
+
#
|
10
|
+
# @private
|
11
|
+
#
|
12
|
+
class Cache
|
13
|
+
def initialize = @cache = {}
|
9
14
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
15
|
+
# This method is used to generate a key for the cache.
|
16
|
+
#
|
17
|
+
# @parameter [String] method
|
18
|
+
# @parameter [String] path
|
19
|
+
#
|
20
|
+
# @return [String]
|
21
|
+
#
|
22
|
+
def cache_key(method, path) = "#{method} #{path}"
|
16
23
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
24
|
+
# This method is used to add a route to the cache.
|
25
|
+
#
|
26
|
+
# @parameter route_key [String] The key for the route.
|
27
|
+
# @parameter node [Lenna::Route::Node] The node for the route.
|
28
|
+
#
|
29
|
+
# @return [Lenna::Route::Node]
|
30
|
+
#
|
31
|
+
def add(route_key, node) = @cache[route_key] = node
|
23
32
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
33
|
+
# This method is used to get a route from the cache.
|
34
|
+
#
|
35
|
+
# @parameter route_key [String] The key for the route.
|
36
|
+
#
|
37
|
+
# @return [Lenna::Route::Node]
|
38
|
+
#
|
39
|
+
def get(route_key) = @cache[route_key]
|
29
40
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
41
|
+
# This method is used to check if a route is in the cache.
|
42
|
+
#
|
43
|
+
# @api public
|
44
|
+
#
|
45
|
+
# @parameter route_key [String] The key for the route.
|
46
|
+
#
|
47
|
+
# @return [Boolean]
|
48
|
+
#
|
49
|
+
def exist?(route_key) = @cache.key?(route_key)
|
50
|
+
end
|
51
|
+
end
|
38
52
|
end
|
@@ -1,73 +1,77 @@
|
|
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
|
-
|
7
|
+
class Router
|
8
|
+
# This class is used to manage the namespaces.
|
9
|
+
#
|
10
|
+
# @private `Since v0.1.0`
|
11
|
+
#
|
12
|
+
class NamespaceStack
|
13
|
+
# @return [Array] The stack of namespaces
|
14
|
+
#
|
15
|
+
# @private
|
16
|
+
#
|
17
|
+
attr_reader :stack
|
13
18
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
19
|
+
# The initialize method is used to initialize the stack of namespaces.
|
20
|
+
#
|
21
|
+
# @private
|
22
|
+
#
|
23
|
+
# @attibute stack [Array] The stack of namespaces
|
24
|
+
#
|
25
|
+
# @return [void]
|
26
|
+
#
|
27
|
+
def initialize = @stack = ['']
|
18
28
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
29
|
+
# This method is used to push a prefix to the stack.
|
30
|
+
#
|
31
|
+
# @parameter prefix [String] The prefix to be pushed
|
32
|
+
#
|
33
|
+
# @return [void]
|
34
|
+
#
|
35
|
+
# ex.
|
36
|
+
#
|
37
|
+
# stack = NamespaceStack.new
|
38
|
+
# stack.push('/users')
|
39
|
+
# stack.current_prefix # => '/users'
|
40
|
+
#
|
41
|
+
# @see #resolve_prefix
|
42
|
+
#
|
43
|
+
def push(prefix)
|
44
|
+
@stack.push(resolve_prefix(prefix))
|
45
|
+
end
|
36
46
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
47
|
+
# This method is used to remove the last prefix from the stack.
|
48
|
+
#
|
49
|
+
# @return [String] The popped prefix
|
50
|
+
#
|
51
|
+
def pop
|
52
|
+
@stack.pop unless @stack.size == 1
|
53
|
+
end
|
43
54
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
#
|
48
|
-
# @since 0.1.0
|
49
|
-
def current_prefix = @stack.last
|
55
|
+
# @return [String] The current prefix
|
56
|
+
#
|
57
|
+
def current_prefix = @stack.last
|
50
58
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
def to_s = current_prefix
|
59
|
+
# The to_s method is used to return the current prefix.
|
60
|
+
#
|
61
|
+
# @return [String] The current prefix
|
62
|
+
#
|
63
|
+
def to_s = current_prefix
|
57
64
|
|
58
|
-
|
65
|
+
private
|
59
66
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
67
|
+
# The resolve_prefix method is used to resolve the prefix.
|
68
|
+
#
|
69
|
+
# @parameter prefix [String] The prefix to be resolved
|
70
|
+
# @return [String] The resolved prefix
|
71
|
+
#
|
72
|
+
def resolve_prefix(prefix)
|
73
|
+
current_prefix + prefix
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
73
77
|
end
|