blob: 6e60714b7bbf4e9653e7bd4e3d8f26e9f2f31906 [file] [log] [blame]
Ed Tanous5b607ea2025-03-26 12:13:39 -07001// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright OpenBMC Authors
3
4#pragma once
5
6#include "logging.hpp"
7
8#include <boost/container/flat_map.hpp>
9#include <boost/container/small_vector.hpp>
10
11#include <cstddef>
12#include <format>
13#include <functional>
14#include <stdexcept>
15#include <string>
16#include <string_view>
17#include <utility>
18#include <vector>
19
20namespace crow
21{
22
23template <typename ContainedType>
24class Trie
25{
26 public:
27 struct Node
28 {
29 unsigned ruleIndex = 0U;
30
31 size_t stringParamChild = 0U;
32 size_t pathParamChild = 0U;
33
34 using ChildMap = boost::container::flat_map<
35 std::string, unsigned, std::less<>,
36 boost::container::small_vector<std::pair<std::string, unsigned>,
37 1>>;
38 ChildMap children;
39
40 bool isSimpleNode() const
41 {
42 return ruleIndex == 0 && stringParamChild == 0 &&
43 pathParamChild == 0;
44 }
45 };
46
47 Trie() : nodes(1) {}
48
49 private:
50 void optimizeNode(Node& node)
51 {
52 if (node.stringParamChild != 0U)
53 {
54 optimizeNode(nodes[node.stringParamChild]);
55 }
56 if (node.pathParamChild != 0U)
57 {
58 optimizeNode(nodes[node.pathParamChild]);
59 }
60
61 if (node.children.empty())
62 {
63 return;
64 }
65 while (true)
66 {
67 bool didMerge = false;
68 typename Node::ChildMap merged;
69 for (const typename Node::ChildMap::value_type& kv : node.children)
70 {
71 Node& child = nodes[kv.second];
72 if (child.isSimpleNode())
73 {
74 for (const typename Node::ChildMap::value_type& childKv :
75 child.children)
76 {
77 merged[kv.first + childKv.first] = childKv.second;
78 didMerge = true;
79 }
80 }
81 else
82 {
83 merged[kv.first] = kv.second;
84 }
85 }
86 node.children = std::move(merged);
87 if (!didMerge)
88 {
89 break;
90 }
91 }
92
93 for (const typename Node::ChildMap::value_type& kv : node.children)
94 {
95 optimizeNode(nodes[kv.second]);
96 }
97 }
98
99 void optimize()
100 {
101 optimizeNode(head());
102 }
103
104 public:
105 void validate()
106 {
107 optimize();
108 }
109
110 void findRouteIndexesHelper(std::string_view reqUrl,
111 std::vector<unsigned>& routeIndexes,
112 const Node& node) const
113 {
114 for (const typename Node::ChildMap::value_type& kv : node.children)
115 {
116 const std::string& fragment = kv.first;
117 const Node& child = nodes[kv.second];
118 if (reqUrl.empty())
119 {
120 if (child.ruleIndex != 0 && fragment != "/")
121 {
122 routeIndexes.push_back(child.ruleIndex);
123 }
124 findRouteIndexesHelper(reqUrl, routeIndexes, child);
125 }
126 else
127 {
128 if (reqUrl.starts_with(fragment))
129 {
130 findRouteIndexesHelper(reqUrl.substr(fragment.size()),
131 routeIndexes, child);
132 }
133 }
134 }
135 }
136
137 void findRouteIndexes(const std::string& reqUrl,
138 std::vector<unsigned>& routeIndexes) const
139 {
140 findRouteIndexesHelper(reqUrl, routeIndexes, head());
141 }
142
143 struct FindResult
144 {
145 unsigned ruleIndex = 0;
146 std::vector<std::string> params;
147 };
148
149 private:
150 FindResult findHelper(const std::string_view reqUrl, const Node& node,
151 std::vector<std::string>& params) const
152 {
153 if (reqUrl.empty())
154 {
155 return {node.ruleIndex, params};
156 }
157
158 if (node.stringParamChild != 0U)
159 {
160 size_t epos = 0;
161 for (; epos < reqUrl.size(); epos++)
162 {
163 if (reqUrl[epos] == '/')
164 {
165 break;
166 }
167 }
168
169 if (epos != 0)
170 {
171 params.emplace_back(reqUrl.substr(0, epos));
172 FindResult ret = findHelper(
173 reqUrl.substr(epos), nodes[node.stringParamChild], params);
174 if (ret.ruleIndex != 0U)
175 {
176 return {ret.ruleIndex, std::move(ret.params)};
177 }
178 params.pop_back();
179 }
180 }
181
182 if (node.pathParamChild != 0U)
183 {
184 params.emplace_back(reqUrl);
185 FindResult ret = findHelper("", nodes[node.pathParamChild], params);
186 if (ret.ruleIndex != 0U)
187 {
188 return {ret.ruleIndex, std::move(ret.params)};
189 }
190 params.pop_back();
191 }
192
193 for (const typename Node::ChildMap::value_type& kv : node.children)
194 {
195 const std::string& fragment = kv.first;
196 const Node& child = nodes[kv.second];
197
198 if (reqUrl.starts_with(fragment))
199 {
200 FindResult ret =
201 findHelper(reqUrl.substr(fragment.size()), child, params);
202 if (ret.ruleIndex != 0U)
203 {
204 return {ret.ruleIndex, std::move(ret.params)};
205 }
206 }
207 }
208
209 return {0U, std::vector<std::string>()};
210 }
211
212 public:
213 FindResult find(const std::string_view reqUrl) const
214 {
215 std::vector<std::string> start;
216 return findHelper(reqUrl, head(), start);
217 }
218
219 void add(std::string_view urlIn, unsigned ruleIndex)
220 {
221 size_t idx = 0;
222
223 std::string_view url = urlIn;
224
225 while (!url.empty())
226 {
227 char c = url[0];
228 if (c == '<')
229 {
230 bool found = false;
231 for (const std::string_view str1 :
232 {"<str>", "<string>", "<path>"})
233 {
234 if (!url.starts_with(str1))
235 {
236 continue;
237 }
238 found = true;
239 Node& node = nodes[idx];
240 size_t* param = &node.stringParamChild;
241 if (str1 == "<path>")
242 {
243 param = &node.pathParamChild;
244 }
245 if (*param == 0U)
246 {
247 *param = newNode();
248 }
249 idx = *param;
250
251 url.remove_prefix(str1.size());
252 break;
253 }
254 if (found)
255 {
256 continue;
257 }
258
259 BMCWEB_LOG_CRITICAL("Can't find tag for {}", urlIn);
260 return;
261 }
262 std::string piece(&c, 1);
263 if (!nodes[idx].children.contains(piece))
264 {
265 unsigned newNodeIdx = newNode();
266 nodes[idx].children.emplace(piece, newNodeIdx);
267 }
268 idx = nodes[idx].children[piece];
269 url.remove_prefix(1);
270 }
271 Node& node = nodes[idx];
272 if (node.ruleIndex != 0U)
273 {
274 BMCWEB_LOG_CRITICAL("handler already exists for \"{}\"", urlIn);
275 throw std::runtime_error(
276 std::format("handler already exists for \"{}\"", urlIn));
277 }
278 node.ruleIndex = ruleIndex;
279 }
280
281 private:
282 void debugNodePrint(Node& n, size_t level)
283 {
284 std::string spaces(level, ' ');
285 if (n.stringParamChild != 0U)
286 {
287 BMCWEB_LOG_DEBUG("{}<str>", spaces);
288 debugNodePrint(nodes[n.stringParamChild], level + 5);
289 }
290 if (n.pathParamChild != 0U)
291 {
292 BMCWEB_LOG_DEBUG("{} <path>", spaces);
293 debugNodePrint(nodes[n.pathParamChild], level + 6);
294 }
295 for (const typename Node::ChildMap::value_type& kv : n.children)
296 {
297 BMCWEB_LOG_DEBUG("{}{}", spaces, kv.first);
298 debugNodePrint(nodes[kv.second], level + kv.first.size());
299 }
300 }
301
302 public:
303 void debugPrint()
304 {
305 debugNodePrint(head(), 0U);
306 }
307
308 private:
309 const Node& head() const
310 {
311 return nodes.front();
312 }
313
314 Node& head()
315 {
316 return nodes.front();
317 }
318
319 unsigned newNode()
320 {
321 nodes.resize(nodes.size() + 1);
322 return static_cast<unsigned>(nodes.size() - 1);
323 }
324
325 std::vector<Node> nodes{};
326};
327} // namespace crow