regulators: Implements support for configuration

Enhance the configuration file parser to support the configuration
element.

Signed-off-by: Bob King <Bob_King@wistron.com>
Change-Id: I6862c9784a3ade6378eb7a1d19a174f1515fc845
diff --git a/phosphor-regulators/src/config_file_parser.cpp b/phosphor-regulators/src/config_file_parser.cpp
index 3d56629..ce95d2d 100644
--- a/phosphor-regulators/src/config_file_parser.cpp
+++ b/phosphor-regulators/src/config_file_parser.cpp
@@ -225,6 +225,37 @@
     return chassis;
 }
 
+std::unique_ptr<Configuration> parseConfiguration(const json& element)
+{
+    verifyIsObject(element);
+    unsigned int propertyCount{0};
+
+    // Optional comments property; value not stored
+    if (element.contains("comments"))
+    {
+        ++propertyCount;
+    }
+
+    // Optional volts property
+    std::optional<double> volts{};
+    auto voltsIt = element.find("volts");
+    if (voltsIt != element.end())
+    {
+        volts = parseDouble(*voltsIt);
+        ++propertyCount;
+    }
+
+    // Required rule_id or actions property
+    std::vector<std::unique_ptr<Action>> actions{};
+    actions = parseRuleIDOrActionsProperty(element);
+    ++propertyCount;
+
+    // Verify no invalid properties exist
+    verifyPropertyCount(element, propertyCount);
+
+    return std::make_unique<Configuration>(volts, std::move(actions));
+}
+
 std::unique_ptr<Device> parseDevice(const json& element)
 {
     verifyIsObject(element);
@@ -270,14 +301,13 @@
     // }
 
     // Optional configuration property
-    // TODO: Not implemented yet
     std::unique_ptr<Configuration> configuration{};
-    // auto configurationIt = element.find("configuration");
-    // if (configurationIt != element.end())
-    // {
-    //     configuration = parseConfiguration(*configurationIt);
-    //     ++propertyCount;
-    // }
+    auto configurationIt = element.find("configuration");
+    if (configurationIt != element.end())
+    {
+        configuration = parseConfiguration(*configurationIt);
+        ++propertyCount;
+    }
 
     // Optional rails property
     std::vector<std::unique_ptr<Rail>> rails{};
@@ -574,6 +604,32 @@
     return rules;
 }
 
+std::vector<std::unique_ptr<Action>>
+    parseRuleIDOrActionsProperty(const json& element)
+{
+    verifyIsObject(element);
+    // Required rule_id or actions property
+    std::vector<std::unique_ptr<Action>> actions{};
+    auto ruleIDIt = element.find("rule_id");
+    auto actionsIt = element.find("actions");
+    if ((actionsIt == element.end()) && (ruleIDIt != element.end()))
+    {
+        std::string ruleID = parseString(*ruleIDIt);
+        actions.emplace_back(std::make_unique<RunRuleAction>(ruleID));
+    }
+    else if ((actionsIt != element.end()) && (ruleIDIt == element.end()))
+    {
+        actions = parseActionArray(*actionsIt);
+    }
+    else
+    {
+        throw std::invalid_argument{"Invalid property combination: Must "
+                                    "contain either rule_id or actions"};
+    }
+
+    return actions;
+}
+
 std::unique_ptr<RunRuleAction> parseRunRule(const json& element)
 {
     // String ruleID
diff --git a/phosphor-regulators/src/config_file_parser.hpp b/phosphor-regulators/src/config_file_parser.hpp
index 35178ea..251f236 100644
--- a/phosphor-regulators/src/config_file_parser.hpp
+++ b/phosphor-regulators/src/config_file_parser.hpp
@@ -202,6 +202,19 @@
     parseChassisArray(const nlohmann::json& element);
 
 /**
+ * Parses a JSON element containing a configuration.
+ *
+ * Returns the corresponding C++ Configuration object.
+ *
+ * Throws an exception if parsing fails.
+ *
+ * @param element JSON element
+ * @return Configuration object
+ */
+std::unique_ptr<Configuration>
+    parseConfiguration(const nlohmann::json& element);
+
+/**
  * Parses a JSON element containing a device.
  *
  * Returns the corresponding C++ Device object.
@@ -436,6 +449,25 @@
     parseRuleArray(const nlohmann::json& element);
 
 /**
+ * Parses the "rule_id" or "actions" property in a JSON element.
+ *
+ * The element must contain one property or the other but not both.
+ *
+ * If the element contains a "rule_id" property, the corresponding C++
+ * RunRuleAction object is returned.
+ *
+ * If the element contains an "actions" property, the corresponding C++ Action
+ * objects are returned.
+ *
+ * Throws an exception if parsing fails.
+ *
+ * @param element JSON element
+ * @return vector of Action objects
+ */
+std::vector<std::unique_ptr<Action>>
+    parseRuleIDOrActionsProperty(const nlohmann::json& element);
+
+/**
  * Parses a JSON element containing a run_rule action.
  *
  * Returns the corresponding C++ RunRuleAction object.
diff --git a/phosphor-regulators/test/config_file_parser_tests.cpp b/phosphor-regulators/test/config_file_parser_tests.cpp
index 1999b7b..e848fd7 100644
--- a/phosphor-regulators/test/config_file_parser_tests.cpp
+++ b/phosphor-regulators/test/config_file_parser_tests.cpp
@@ -747,6 +747,191 @@
     }
 }
 
+TEST(ConfigFileParserTests, ParseConfiguration)
+{
+    // Test where works: actions required property specified
+    {
+        const json element = R"(
+            {
+              "actions": [
+                {
+                  "pmbus_write_vout_command": {
+                    "format": "linear"
+                  }
+                }
+              ]
+            }
+        )"_json;
+        std::unique_ptr<Configuration> configuration =
+            parseConfiguration(element);
+        EXPECT_EQ(configuration->getActions().size(), 1);
+        EXPECT_EQ(configuration->getVolts().has_value(), false);
+    }
+
+    // Test where works: volts and actions properties specified
+    {
+        const json element = R"(
+            {
+              "comments": [ "comments property" ],
+              "volts": 1.03,
+              "actions": [
+                { "pmbus_write_vout_command": { "format": "linear" } },
+                { "run_rule": "set_voltage_rule" }
+              ]
+            }
+        )"_json;
+        std::unique_ptr<Configuration> configuration =
+            parseConfiguration(element);
+        EXPECT_EQ(configuration->getVolts().has_value(), true);
+        EXPECT_EQ(configuration->getVolts().value(), 1.03);
+        EXPECT_EQ(configuration->getActions().size(), 2);
+    }
+
+    // Test where works: volts and rule_id properties specified
+    {
+        const json element = R"(
+            {
+              "volts": 1.05,
+              "rule_id": "set_voltage_rule"
+            }
+        )"_json;
+        std::unique_ptr<Configuration> configuration =
+            parseConfiguration(element);
+        EXPECT_EQ(configuration->getVolts().has_value(), true);
+        EXPECT_EQ(configuration->getVolts().value(), 1.05);
+        EXPECT_EQ(configuration->getActions().size(), 1);
+    }
+
+    // Test where fails: volts value is invalid
+    try
+    {
+        const json element = R"(
+            {
+              "volts": "foo",
+              "actions": [
+                {
+                  "pmbus_write_vout_command": {
+                    "format": "linear"
+                  }
+                }
+              ]
+            }
+        )"_json;
+        parseConfiguration(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not a number");
+    }
+
+    // Test where fails: actions object is invalid
+    try
+    {
+        const json element = R"(
+            {
+              "volts": 1.03,
+              "actions": 1
+            }
+        )"_json;
+        parseConfiguration(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: rule_id value is invalid
+    try
+    {
+        const json element = R"(
+            {
+              "volts": 1.05,
+              "rule_id": 1
+            }
+        )"_json;
+        parseConfiguration(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not a string");
+    }
+
+    // Test where fails: Required actions or rule_id property not specified
+    try
+    {
+        const json element = R"(
+            {
+              "volts": 1.03
+            }
+        )"_json;
+        parseConfiguration(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Invalid property combination: Must contain "
+                               "either rule_id or actions");
+    }
+
+    // Test where fails: Required actions or rule_id property both specified
+    try
+    {
+        const json element = R"(
+            {
+              "volts": 1.03,
+              "rule_id": "set_voltage_rule",
+              "actions": [
+                {
+                  "pmbus_write_vout_command": {
+                    "format": "linear"
+                  }
+                }
+              ]
+            }
+        )"_json;
+        parseConfiguration(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Invalid property combination: Must contain "
+                               "either rule_id or actions");
+    }
+
+    // Test where fails: Element is not an object
+    try
+    {
+        const json element = R"( [ "0xFF", "0x01" ] )"_json;
+        parseConfiguration(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not an object");
+    }
+
+    // Test where fails: Invalid property specified
+    try
+    {
+        const json element = R"(
+            {
+              "volts": 1.03,
+              "rule_id": "set_voltage_rule",
+              "foo": 1
+            }
+        )"_json;
+        parseConfiguration(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element contains an invalid property");
+    }
+}
+
 TEST(ConfigFileParserTests, ParseDevice)
 {
     // Test where works: Only required properties specified
@@ -770,7 +955,33 @@
     }
 
     // Test where works: All properties specified
-    // TODO: Not implemented yet
+    {
+        // TODO : add rails and presence_detection properties
+        const json element = R"(
+            {
+              "id": "vdd_regulator",
+              "is_regulator": true,
+              "fru": "/system/chassis/motherboard/regulator2",
+              "i2c_interface":
+              {
+                  "bus": 1,
+                  "address": "0x70"
+              },
+              "configuration":
+              {
+                  "rule_id": "configure_ir35221_rule"
+              }
+            }
+        )"_json;
+        std::unique_ptr<Device> device = parseDevice(element);
+        EXPECT_EQ(device->getID(), "vdd_regulator");
+        EXPECT_EQ(device->isRegulator(), true);
+        EXPECT_EQ(device->getFRU(), "/system/chassis/motherboard/regulator2");
+        EXPECT_NE(&(device->getI2CInterface()), nullptr);
+        // EXPECT_NE(device->getPresenceDetection(), nullptr);
+        EXPECT_NE(device->getConfiguration(), nullptr);
+        // EXPECT_NE(device->getRails().size(), 0);
+    }
 
     // Test where fails: id value is invalid
     try
@@ -2146,6 +2357,118 @@
     }
 }
 
+TEST(ConfigFileParserTests, ParseRuleIDOrActionsProperty)
+{
+    // Test where works: actions specified
+    {
+        const json element = R"(
+            {
+              "actions": [
+                { "pmbus_write_vout_command": { "format": "linear" } },
+                { "run_rule": "set_voltage_rule" }
+              ]
+            }
+        )"_json;
+        std::vector<std::unique_ptr<Action>> actions =
+            parseRuleIDOrActionsProperty(element);
+        EXPECT_EQ(actions.size(), 2);
+    }
+
+    // Test where works: rule_id specified
+    {
+        const json element = R"(
+            {
+              "rule_id": "set_voltage_rule"
+            }
+        )"_json;
+        std::vector<std::unique_ptr<Action>> actions =
+            parseRuleIDOrActionsProperty(element);
+        EXPECT_EQ(actions.size(), 1);
+    }
+
+    // Test where fails: Element is not an object
+    try
+    {
+        const json element = R"( [ "foo", "bar" ] )"_json;
+        parseRuleIDOrActionsProperty(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not an object");
+    }
+
+    // Test where fails: rule_id is invalid
+    try
+    {
+        const json element = R"(
+            { "rule_id": 1 }
+        )"_json;
+        parseRuleIDOrActionsProperty(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not a string");
+    }
+
+    // Test where fails: actions is invalid
+    try
+    {
+        const json element = R"(
+            { "actions": 1 }
+        )"_json;
+        parseRuleIDOrActionsProperty(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: Neither rule_id nor actions specified
+    try
+    {
+        const json element = R"(
+            {
+              "volts": 1.03
+            }
+        )"_json;
+        parseRuleIDOrActionsProperty(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Invalid property combination: Must contain "
+                               "either rule_id or actions");
+    }
+
+    // Test where fails: Both rule_id and actions specified
+    try
+    {
+        const json element = R"(
+            {
+              "volts": 1.03,
+              "rule_id": "set_voltage_rule",
+              "actions": [
+                {
+                  "pmbus_write_vout_command": {
+                    "format": "linear"
+                  }
+                }
+              ]
+            }
+        )"_json;
+        parseRuleIDOrActionsProperty(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Invalid property combination: Must contain "
+                               "either rule_id or actions");
+    }
+}
+
 TEST(ConfigFileParserTests, ParseRunRule)
 {
     // Test where works