pseq: Parsing support for chassis_template array

Add JSON parsing support for an array of chassis_template elements in
the phosphor-power-sequencer configuration file.

Tested:
* Ran automated tests.

Change-Id: Id9277c1eb938fd8c08c292fbe6c898507d2faced
Signed-off-by: Shawn McCarney <shawnmm@us.ibm.com>
diff --git a/phosphor-power-sequencer/src/config_file_parser.cpp b/phosphor-power-sequencer/src/config_file_parser.cpp
index 18fe32e..9e4a18d 100644
--- a/phosphor-power-sequencer/src/config_file_parser.cpp
+++ b/phosphor-power-sequencer/src/config_file_parser.cpp
@@ -95,7 +95,8 @@
 {
 
 std::unique_ptr<Chassis> parseChassis(
-    const json& element, std::map<std::string, JSONRefWrapper> chassisTemplates,
+    const json& element,
+    const std::map<std::string, JSONRefWrapper>& chassisTemplates,
     Services& services)
 {
     verifyIsObject(element);
@@ -148,7 +149,8 @@
 }
 
 std::vector<std::unique_ptr<Chassis>> parseChassisArray(
-    const json& element, std::map<std::string, JSONRefWrapper> chassisTemplates,
+    const json& element,
+    const std::map<std::string, JSONRefWrapper>& chassisTemplates,
     Services& services)
 {
     verifyIsArray(element);
@@ -251,6 +253,18 @@
     return {id, JSONRefWrapper{element}};
 }
 
+std::map<std::string, JSONRefWrapper> parseChassisTemplateArray(
+    const json& element)
+{
+    verifyIsArray(element);
+    std::map<std::string, JSONRefWrapper> chassisTemplates;
+    for (auto& chassisTemplateElement : element)
+    {
+        chassisTemplates.emplace(parseChassisTemplate(chassisTemplateElement));
+    }
+    return chassisTemplates;
+}
+
 GPIO parseGPIO(const json& element,
                const std::map<std::string, std::string>& variables)
 {
diff --git a/phosphor-power-sequencer/src/config_file_parser.hpp b/phosphor-power-sequencer/src/config_file_parser.hpp
index 665029e..f43ad0e 100644
--- a/phosphor-power-sequencer/src/config_file_parser.hpp
+++ b/phosphor-power-sequencer/src/config_file_parser.hpp
@@ -99,7 +99,8 @@
  * @return Chassis object
  */
 std::unique_ptr<Chassis> parseChassis(
-    const json& element, std::map<std::string, JSONRefWrapper> chassisTemplates,
+    const json& element,
+    const std::map<std::string, JSONRefWrapper>& chassisTemplates,
     Services& services);
 
 /**
@@ -115,7 +116,8 @@
  * @return vector of Chassis objects
  */
 std::vector<std::unique_ptr<Chassis>> parseChassisArray(
-    const json& element, std::map<std::string, JSONRefWrapper> chassisTemplates,
+    const json& element,
+    const std::map<std::string, JSONRefWrapper>& chassisTemplates,
     Services& services);
 
 /**
@@ -142,6 +144,17 @@
  *
  * Returns the template ID and a C++ reference_wrapper to the JSON element.
  *
+ * A chassis_template object cannot be fully parsed in isolation. It is a
+ * template that contains variables.
+ *
+ * The chassis_template object is used by one or more chassis objects to avoid
+ * duplicate JSON. The chassis objects define chassis-specific values for the
+ * template variables.
+ *
+ * When the chassis object is parsed, the chassis_template JSON will be
+ * re-parsed, and the template variables will be replaced with the
+ * chassis-specific values.
+ *
  * Throws an exception if parsing fails.
  *
  * @param element JSON element
@@ -151,6 +164,22 @@
     const json& element);
 
 /**
+ * Parses a JSON element containing an array of chassis_template objects.
+ *
+ * Returns a map of template IDs to chassis_template JSON elements.
+ *
+ * Note that chassis_template objects cannot be fully parsed in isolation. See
+ * parseChassisTemplate() for more information.
+ *
+ * Throws an exception if parsing fails.
+ *
+ * @param element JSON element
+ * @return chassis templates map
+ */
+std::map<std::string, JSONRefWrapper> parseChassisTemplateArray(
+    const json& element);
+
+/**
  * Parses a JSON element containing a GPIO.
  *
  * Returns the corresponding C++ GPIO object.
diff --git a/phosphor-power-sequencer/test/config_file_parser_tests.cpp b/phosphor-power-sequencer/test/config_file_parser_tests.cpp
index 3f0797c..2fdf5af 100644
--- a/phosphor-power-sequencer/test/config_file_parser_tests.cpp
+++ b/phosphor-power-sequencer/test/config_file_parser_tests.cpp
@@ -1198,6 +1198,90 @@
     }
 }
 
+TEST(ConfigFileParserTests, ParseChassisTemplateArray)
+{
+    // Test where works: Array is empty
+    {
+        const json element = R"(
+            [
+            ]
+        )"_json;
+        auto chassisTemplates = parseChassisTemplateArray(element);
+        EXPECT_EQ(chassisTemplates.size(), 0);
+    }
+
+    // Test where works: Array is not empty
+    {
+        const json element = R"(
+            [
+              {
+                "id": "foo_chassis",
+                "number": "${chassis_number}",
+                "inventory_path": "/xyz/openbmc_project/inventory/system/chassis${chassis_number}",
+                "power_sequencers": []
+              },
+              {
+                "id": "bar_chassis",
+                "number": "${number}",
+                "inventory_path": "/xyz/openbmc_project/inventory/system/bar_chassis${number}",
+                "power_sequencers": []
+              }
+            ]
+        )"_json;
+        auto chassisTemplates = parseChassisTemplateArray(element);
+        EXPECT_EQ(chassisTemplates.size(), 2);
+        EXPECT_EQ(chassisTemplates.at("foo_chassis").get()["number"],
+                  "${chassis_number}");
+        EXPECT_EQ(
+            chassisTemplates.at("foo_chassis").get()["inventory_path"],
+            "/xyz/openbmc_project/inventory/system/chassis${chassis_number}");
+        EXPECT_EQ(chassisTemplates.at("bar_chassis").get()["number"],
+                  "${number}");
+        EXPECT_EQ(chassisTemplates.at("bar_chassis").get()["inventory_path"],
+                  "/xyz/openbmc_project/inventory/system/bar_chassis${number}");
+    }
+
+    // Test where fails: Element is not an array
+    try
+    {
+        const json element = R"(
+            {
+                "foo": "bar"
+            }
+        )"_json;
+        parseChassisTemplateArray(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not an array");
+    }
+
+    // Test where fails: Element within array is invalid
+    try
+    {
+        const json element = R"(
+            [
+              {
+                "id": "foo_chassis",
+                "number": "${chassis_number}",
+                "inventory_path": "/xyz/openbmc_project/inventory/system/chassis${chassis_number}",
+                "power_sequencers": []
+              },
+              {
+                "id": "bar_chassis"
+              }
+            ]
+        )"_json;
+        parseChassisTemplateArray(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Required property missing: number");
+    }
+}
+
 TEST(ConfigFileParserTests, ParseGPIO)
 {
     // Test where works: Only required properties specified