xref: /openbmc/bmcweb/redfish-core/include/sub_route_trie.hpp (revision a9da2b2b50758ec9657ab7034e57dadb3e62abd0)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 #pragma once
4 
5 #include "logging.hpp"
6 #include "routing/trie.hpp"
7 
8 #include <cstdlib>
9 #include <format>
10 #include <stdexcept>
11 #include <string>
12 #include <string_view>
13 #include <vector>
14 
15 namespace crow
16 {
17 
18 struct SubRouteNode : public crow::Node
19 {
20     using ChildMap = crow::Node::ChildMap;
21     ChildMap fragmentChildren;
22 
isSimpleNodecrow::SubRouteNode23     bool isSimpleNode() const
24     {
25         return crow::Node::isSimpleNode() && fragmentChildren.empty();
26     }
27 };
28 
29 template <typename ContainedType>
30 class SubRouteTrie : public crow::Trie<ContainedType>
31 {
32   public:
33     struct FindResult
34     {
35         std::vector<std::string> params;
36         std::vector<unsigned> fragmentRuleIndexes;
37     };
38 
39   private:
findHelper(const std::string_view reqUrl,const ContainedType & node,std::vector<std::string> & params) const40     FindResult findHelper(const std::string_view reqUrl,
41                           const ContainedType& node,
42                           std::vector<std::string>& params) const
43     {
44         if (reqUrl.empty())
45         {
46             FindResult result = {params, {}};
47             for (const auto& [fragment, fragmentRuleIndex] :
48                  node.fragmentChildren)
49             {
50                 result.fragmentRuleIndexes.push_back(fragmentRuleIndex);
51             }
52             return result;
53         }
54 
55         if (node.stringParamChild != 0U)
56         {
57             size_t epos = reqUrl.find('/');
58             if (epos == std::string_view::npos)
59             {
60                 params.emplace_back(reqUrl);
61                 FindResult ret =
62                     findHelper("", this->nodes[node.stringParamChild], params);
63                 if (!ret.fragmentRuleIndexes.empty())
64                 {
65                     return ret;
66                 }
67                 params.pop_back();
68             }
69             else
70             {
71                 params.emplace_back(reqUrl.substr(0, epos));
72                 FindResult ret =
73                     findHelper(reqUrl.substr(epos),
74                                this->nodes[node.stringParamChild], params);
75                 if (!ret.fragmentRuleIndexes.empty())
76                 {
77                     return ret;
78                 }
79                 params.pop_back();
80             }
81         }
82 
83         if (node.pathParamChild != 0U)
84         {
85             params.emplace_back(reqUrl);
86             FindResult ret =
87                 findHelper("", this->nodes[node.pathParamChild], params);
88             if (!ret.fragmentRuleIndexes.empty())
89             {
90                 return ret;
91             }
92             params.pop_back();
93         }
94 
95         for (const typename ContainedType::ChildMap::value_type& kv :
96              node.children)
97         {
98             const std::string& fragment = kv.first;
99             const ContainedType& child = this->nodes[kv.second];
100 
101             if (reqUrl.starts_with(fragment))
102             {
103                 FindResult ret =
104                     findHelper(reqUrl.substr(fragment.size()), child, params);
105                 if (!ret.fragmentRuleIndexes.empty())
106                 {
107                     return ret;
108                 }
109             }
110         }
111 
112         return {std::vector<std::string>(), {}};
113     }
114 
115   public:
find(const std::string_view reqUrl) const116     FindResult find(const std::string_view reqUrl) const
117     {
118         std::vector<std::string> start;
119         return findHelper(reqUrl, this->head(), start);
120     }
121 
add(std::string_view urlIn,unsigned ruleIndex)122     void add(std::string_view urlIn, unsigned ruleIndex)
123     {
124         size_t idx = 0;
125 
126         std::string_view url = urlIn;
127 
128         std::string_view fragment;
129         // Check if the URL contains a fragment (#)
130         size_t fragmentPos = urlIn.find('#');
131         size_t queryPos = urlIn.find('?');
132         if (fragmentPos != std::string::npos && queryPos == std::string::npos &&
133             fragmentPos != urlIn.length() - 1)
134         {
135             fragment = urlIn.substr(fragmentPos + 1);
136             url = urlIn.substr(0, fragmentPos);
137         }
138 
139         if (fragment.empty())
140         {
141             BMCWEB_LOG_CRITICAL("empty fragment on rule \"{}\"", urlIn);
142             throw std::runtime_error(
143                 std::format("empty fragment on rule \"{}\"", urlIn));
144         }
145 
146         while (!url.empty())
147         {
148             char c = url[0];
149             if (c == '<')
150             {
151                 bool found = false;
152                 for (const std::string_view str1 :
153                      {"<str>", "<string>", "<path>"})
154                 {
155                     if (!url.starts_with(str1))
156                     {
157                         continue;
158                     }
159                     found = true;
160                     if (str1 == "<path>")
161                     {
162                         if (this->nodes[idx].pathParamChild == 0U)
163                         {
164                             unsigned newNodeIdx = this->newNode();
165                             this->nodes[idx].pathParamChild = newNodeIdx;
166                         }
167                         idx = this->nodes[idx].pathParamChild;
168                     }
169                     else
170                     {
171                         if (this->nodes[idx].stringParamChild == 0U)
172                         {
173                             unsigned newNodeIdx = this->newNode();
174                             this->nodes[idx].stringParamChild = newNodeIdx;
175                         }
176                         idx = this->nodes[idx].stringParamChild;
177                     }
178 
179                     url.remove_prefix(str1.size());
180                     break;
181                 }
182                 if (found)
183                 {
184                     continue;
185                 }
186 
187                 BMCWEB_LOG_CRITICAL("Can't find tag for {}", urlIn);
188                 return;
189             }
190             std::string piece(&c, 1);
191             if (!this->nodes[idx].children.contains(piece))
192             {
193                 unsigned newNodeIdx = this->newNode();
194                 this->nodes[idx].children.emplace(piece, newNodeIdx);
195             }
196             idx = this->nodes[idx].children[piece];
197             url.remove_prefix(1);
198         }
199         ContainedType& node = this->nodes[idx];
200         if (node.fragmentChildren.find(fragment) != node.fragmentChildren.end())
201         {
202             BMCWEB_LOG_CRITICAL(
203                 R"(fragment handler already exists for "{}" fragment "{}")",
204                 urlIn, fragment);
205             throw std::runtime_error(std::format(
206                 R"(handler already exists for url "{}" fragment "{}")", urlIn,
207                 fragment));
208         }
209 
210         node.fragmentChildren.emplace(fragment, ruleIndex);
211     }
212 
213   private:
debugNodePrint(ContainedType & n,size_t level)214     void debugNodePrint(ContainedType& n, size_t level)
215     {
216         std::string spaces(level, ' ');
217         if (n.stringParamChild != 0U)
218         {
219             BMCWEB_LOG_DEBUG("{}<str>", spaces);
220             debugNodePrint(this->nodes[n.stringParamChild], level + 5);
221         }
222         if (n.pathParamChild != 0U)
223         {
224             BMCWEB_LOG_DEBUG("{} <path>", spaces);
225             debugNodePrint(this->nodes[n.pathParamChild], level + 6);
226         }
227         for (const typename ContainedType::ChildMap::value_type& kv :
228              n.fragmentChildren)
229         {
230             BMCWEB_LOG_DEBUG("{}#{}", spaces, kv.first);
231         }
232         for (const typename ContainedType::ChildMap::value_type& kv :
233              n.children)
234         {
235             BMCWEB_LOG_DEBUG("{}{}", spaces, kv.first);
236             debugNodePrint(this->nodes[kv.second], level + kv.first.size());
237         }
238     }
239 
240   public:
debugPrint()241     void debugPrint()
242     {
243         debugNodePrint(this->head(), 0U);
244     }
245 };
246 
247 } // namespace crow
248