topology: class AssocName for association

Define a class AssocName to represent the association definition.

Since the association definition has a forward and reverse name, the
class has 2 members.

So we will always be able to reverse the association definition without
doing any further lookups.

Which removes some branches and optionals and simplifies the code.

Tested: Topology Unit Tests Pass.

Change-Id: I8805d6cb45ba07030d45fc3ca32625c8a6c1b03e
Signed-off-by: Alexander Hansen <alexander.hansen@9elements.com>
diff --git a/src/entity_manager/topology.cpp b/src/entity_manager/topology.cpp
index 16af9cd..0581991 100644
--- a/src/entity_manager/topology.cpp
+++ b/src/entity_manager/topology.cpp
@@ -2,6 +2,25 @@
 
 #include "phosphor-logging/lg2.hpp"
 
+const AssocName assocContaining = AssocName("containing", "contained_by");
+const AssocName assocContainedBy = assocContaining.getReverse();
+const AssocName assocPowering = AssocName("powering", "powered_by");
+const AssocName assocPoweredBy = assocPowering.getReverse();
+
+AssocName::AssocName(const std::string& name, const std::string& reverse) :
+    name(name), reverse(reverse)
+{}
+
+AssocName AssocName::getReverse() const
+{
+    return {reverse, name};
+}
+
+bool AssocName::operator<(const AssocName& other) const
+{
+    return name < other.name;
+}
+
 void Topology::addBoard(const std::string& path, const std::string& boardType,
                         const std::string& boardName,
                         const nlohmann::json& exposesItem)
@@ -22,7 +41,7 @@
     }
     else if (exposesType.ends_with("Port"))
     {
-        addPort(exposesType, path, "containing");
+        addPort(exposesType, path, assocContaining);
     }
     else
     {
@@ -43,12 +62,12 @@
     }
     PortType connectsTo = findConnectsTo->get<std::string>();
 
-    addPort(connectsTo, path, "contained_by");
+    addPort(connectsTo, path, assocContainedBy);
 
     auto findPoweredBy = exposesItem.find("PowerPort");
     if (findPoweredBy != exposesItem.end())
     {
-        addPort(connectsTo, path, "powering");
+        addPort(connectsTo, path, assocPowering);
     }
 }
 
@@ -94,20 +113,15 @@
             }
             for (const auto& assocName : member.second)
             {
-                auto optReverse = getOppositeAssoc(assocName);
-                if (!optReverse.has_value())
-                {
-                    continue;
-                }
                 // if the other end of the assocation does not declare
                 // the reverse association, do not associate
                 const bool otherAgrees =
-                    other.second.contains(optReverse.value());
+                    other.second.contains(assocName.getReverse());
 
                 // quirk: since the other side of the association cannot declare
                 // to be powered_by in the legacy schema, in case of "powering",
                 // the two associations do not have to agree.
-                if (!otherAgrees && assocName != "powering")
+                if (!otherAgrees && assocName != assocPowering)
                 {
                     continue;
                 }
@@ -134,44 +148,14 @@
         return;
     }
 
-    auto optReverse = getOppositeAssoc(assocName);
-
-    if (!optReverse)
-    {
-        return;
-    }
-
     // quirk: legacy code did not associate from both sides
     // TODO(alexander): revisit this
-    if (assocName == "containing" || assocName == "powered_by")
+    if (assocName == assocContaining || assocName == assocPoweredBy)
     {
         return;
     }
 
-    result[upstream].insert({assocName, optReverse.value(), downstream});
-}
-
-const std::set<std::pair<std::string, std::string>> assocs = {
-    {"powering", "powered_by"}, {"containing", "contained_by"},
-    // ... extend as needed
-};
-
-std::optional<std::string> Topology::getOppositeAssoc(
-    const AssocName& assocName)
-{
-    for (const auto& entry : assocs)
-    {
-        if (entry.first == assocName)
-        {
-            return entry.second;
-        }
-        if (entry.second == assocName)
-        {
-            return entry.first;
-        }
-    }
-
-    return std::nullopt;
+    result[upstream].insert({assocName.name, assocName.reverse, downstream});
 }
 
 void Topology::remove(const std::string& boardName)
diff --git a/src/entity_manager/topology.hpp b/src/entity_manager/topology.hpp
index 9fc3536..cab4533 100644
--- a/src/entity_manager/topology.hpp
+++ b/src/entity_manager/topology.hpp
@@ -10,6 +10,21 @@
 using BoardPathsView = decltype(std::views::keys(
     std::declval<std::map<std::string, std::string>&>()));
 
+class AssocName
+{
+  public:
+    std::string name;
+    std::string reverse;
+
+    AssocName(const std::string& name, const std::string& reverse);
+    AssocName() = delete;
+
+    AssocName getReverse() const;
+
+    bool operator==(const AssocName& other) const = default;
+    bool operator<(const AssocName& other) const;
+};
+
 class Topology
 {
   public:
@@ -30,9 +45,6 @@
 
     void addDownstreamPort(const Path& path, const nlohmann::json& exposesItem);
 
-    // e.g. contained_by, containing, powered_by, ...
-    using AssocName = std::string;
-
     // @brief: fill associations map with the associations for a port identifier
     // such as 'MB Upstream Port'
     void fillAssocsForPortId(
@@ -48,9 +60,6 @@
     void addPort(const PortType& port, const Path& path,
                  const AssocName& assocName);
 
-    static std::optional<std::string> getOppositeAssoc(
-        const AssocName& assocName);
-
     // Maps the port name to the participating paths.
     // each path also has their role(s) in the association.
     // For example a PSU path which is part of "MB Upstream Port"