| // SPDX-License-Identifier: Apache-2.0 |
| // SPDX-FileCopyrightText: Copyright OpenBMC Authors |
| |
| #pragma once |
| |
| #include "logging.hpp" |
| |
| #include <boost/container/flat_map.hpp> |
| #include <boost/container/small_vector.hpp> |
| |
| #include <cstddef> |
| #include <format> |
| #include <functional> |
| #include <stdexcept> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| #include <vector> |
| |
| namespace crow |
| { |
| |
| struct Node |
| { |
| unsigned ruleIndex = 0U; |
| |
| size_t stringParamChild = 0U; |
| size_t pathParamChild = 0U; |
| |
| using ChildMap = boost::container::flat_map< |
| std::string, unsigned, std::less<>, |
| boost::container::small_vector<std::pair<std::string, unsigned>, 1>>; |
| ChildMap children; |
| |
| bool isSimpleNode() const |
| { |
| return ruleIndex == 0 && stringParamChild == 0 && pathParamChild == 0; |
| } |
| }; |
| template <typename ContainedType> |
| class Trie |
| { |
| public: |
| Trie() : nodes(1) {} |
| |
| private: |
| void optimizeNode(ContainedType& node) |
| { |
| if (node.stringParamChild != 0U) |
| { |
| optimizeNode(nodes[node.stringParamChild]); |
| } |
| if (node.pathParamChild != 0U) |
| { |
| optimizeNode(nodes[node.pathParamChild]); |
| } |
| |
| if (node.children.empty()) |
| { |
| return; |
| } |
| while (true) |
| { |
| bool didMerge = false; |
| typename ContainedType::ChildMap merged; |
| for (const typename ContainedType::ChildMap::value_type& kv : |
| node.children) |
| { |
| ContainedType& child = nodes[kv.second]; |
| if (child.isSimpleNode()) |
| { |
| for (const typename ContainedType::ChildMap::value_type& |
| childKv : child.children) |
| { |
| merged[kv.first + childKv.first] = childKv.second; |
| didMerge = true; |
| } |
| } |
| else |
| { |
| merged[kv.first] = kv.second; |
| } |
| } |
| node.children = std::move(merged); |
| if (!didMerge) |
| { |
| break; |
| } |
| } |
| |
| for (const typename ContainedType::ChildMap::value_type& kv : |
| node.children) |
| { |
| optimizeNode(nodes[kv.second]); |
| } |
| } |
| |
| void optimize() |
| { |
| optimizeNode(head()); |
| } |
| |
| public: |
| void validate() |
| { |
| optimize(); |
| } |
| |
| void findRouteIndexesHelper(std::string_view reqUrl, |
| std::vector<unsigned>& routeIndexes, |
| const ContainedType& node) const |
| { |
| for (const typename ContainedType::ChildMap::value_type& kv : |
| node.children) |
| { |
| const std::string& fragment = kv.first; |
| const ContainedType& child = nodes[kv.second]; |
| if (reqUrl.empty()) |
| { |
| if (child.ruleIndex != 0 && fragment != "/") |
| { |
| routeIndexes.push_back(child.ruleIndex); |
| } |
| findRouteIndexesHelper(reqUrl, routeIndexes, child); |
| } |
| else |
| { |
| if (reqUrl.starts_with(fragment)) |
| { |
| findRouteIndexesHelper(reqUrl.substr(fragment.size()), |
| routeIndexes, child); |
| } |
| } |
| } |
| } |
| |
| void findRouteIndexes(const std::string& reqUrl, |
| std::vector<unsigned>& routeIndexes) const |
| { |
| findRouteIndexesHelper(reqUrl, routeIndexes, head()); |
| } |
| |
| struct FindResult |
| { |
| unsigned ruleIndex = 0; |
| std::vector<std::string> params; |
| }; |
| |
| private: |
| FindResult findHelper(const std::string_view reqUrl, |
| const ContainedType& node, |
| std::vector<std::string>& params) const |
| { |
| if (reqUrl.empty()) |
| { |
| return {node.ruleIndex, params}; |
| } |
| |
| if (node.stringParamChild != 0U) |
| { |
| size_t epos = 0; |
| for (; epos < reqUrl.size(); epos++) |
| { |
| if (reqUrl[epos] == '/') |
| { |
| break; |
| } |
| } |
| |
| if (epos != 0) |
| { |
| params.emplace_back(reqUrl.substr(0, epos)); |
| FindResult ret = findHelper( |
| reqUrl.substr(epos), nodes[node.stringParamChild], params); |
| if (ret.ruleIndex != 0U) |
| { |
| return {ret.ruleIndex, std::move(ret.params)}; |
| } |
| params.pop_back(); |
| } |
| } |
| |
| if (node.pathParamChild != 0U) |
| { |
| params.emplace_back(reqUrl); |
| FindResult ret = findHelper("", nodes[node.pathParamChild], params); |
| if (ret.ruleIndex != 0U) |
| { |
| return {ret.ruleIndex, std::move(ret.params)}; |
| } |
| params.pop_back(); |
| } |
| |
| for (const typename ContainedType::ChildMap::value_type& kv : |
| node.children) |
| { |
| const std::string& fragment = kv.first; |
| const ContainedType& child = nodes[kv.second]; |
| |
| if (reqUrl.starts_with(fragment)) |
| { |
| FindResult ret = |
| findHelper(reqUrl.substr(fragment.size()), child, params); |
| if (ret.ruleIndex != 0U) |
| { |
| return {ret.ruleIndex, std::move(ret.params)}; |
| } |
| } |
| } |
| |
| return {0U, std::vector<std::string>()}; |
| } |
| |
| public: |
| FindResult find(const std::string_view reqUrl) const |
| { |
| std::vector<std::string> start; |
| return findHelper(reqUrl, head(), start); |
| } |
| |
| void add(std::string_view urlIn, unsigned ruleIndex) |
| { |
| size_t idx = 0; |
| |
| std::string_view url = urlIn; |
| |
| while (!url.empty()) |
| { |
| char c = url[0]; |
| if (c == '<') |
| { |
| bool found = false; |
| for (const std::string_view str1 : |
| {"<str>", "<string>", "<path>"}) |
| { |
| if (!url.starts_with(str1)) |
| { |
| continue; |
| } |
| found = true; |
| ContainedType& node = nodes[idx]; |
| size_t* param = &node.stringParamChild; |
| if (str1 == "<path>") |
| { |
| param = &node.pathParamChild; |
| } |
| if (*param == 0U) |
| { |
| *param = newNode(); |
| } |
| idx = *param; |
| |
| url.remove_prefix(str1.size()); |
| break; |
| } |
| if (found) |
| { |
| continue; |
| } |
| |
| BMCWEB_LOG_CRITICAL("Can't find tag for {}", urlIn); |
| return; |
| } |
| std::string piece(&c, 1); |
| if (!nodes[idx].children.contains(piece)) |
| { |
| unsigned newNodeIdx = newNode(); |
| nodes[idx].children.emplace(piece, newNodeIdx); |
| } |
| idx = nodes[idx].children[piece]; |
| url.remove_prefix(1); |
| } |
| ContainedType& node = nodes[idx]; |
| if (node.ruleIndex != 0U) |
| { |
| BMCWEB_LOG_CRITICAL("handler already exists for \"{}\"", urlIn); |
| throw std::runtime_error( |
| std::format("handler already exists for \"{}\"", urlIn)); |
| } |
| node.ruleIndex = ruleIndex; |
| } |
| |
| private: |
| void debugNodePrint(ContainedType& n, size_t level) |
| { |
| std::string spaces(level, ' '); |
| if (n.stringParamChild != 0U) |
| { |
| BMCWEB_LOG_DEBUG("{}<str>", spaces); |
| debugNodePrint(nodes[n.stringParamChild], level + 5); |
| } |
| if (n.pathParamChild != 0U) |
| { |
| BMCWEB_LOG_DEBUG("{} <path>", spaces); |
| debugNodePrint(nodes[n.pathParamChild], level + 6); |
| } |
| for (const typename ContainedType::ChildMap::value_type& kv : |
| n.children) |
| { |
| BMCWEB_LOG_DEBUG("{}{}", spaces, kv.first); |
| debugNodePrint(nodes[kv.second], level + kv.first.size()); |
| } |
| } |
| |
| public: |
| void debugPrint() |
| { |
| debugNodePrint(head(), 0U); |
| } |
| |
| protected: |
| const ContainedType& head() const |
| { |
| return nodes.front(); |
| } |
| |
| ContainedType& head() |
| { |
| return nodes.front(); |
| } |
| |
| unsigned newNode() |
| { |
| nodes.resize(nodes.size() + 1); |
| return static_cast<unsigned>(nodes.size() - 1); |
| } |
| |
| std::vector<ContainedType> nodes{}; |
| }; |
| } // namespace crow |