regulators: Implements support for device

Enhance the configuration file parser to support the device and
i2c_interface elements.

Signed-off-by: Bob King <Bob_King@wistron.com>
Change-Id: I8cf55d4fc793357113cdfcb6c543556b5eb09a8d
diff --git a/phosphor-regulators/src/config_file_parser.cpp b/phosphor-regulators/src/config_file_parser.cpp
index 99a2d15..3d56629 100644
--- a/phosphor-regulators/src/config_file_parser.cpp
+++ b/phosphor-regulators/src/config_file_parser.cpp
@@ -225,15 +225,91 @@
     return chassis;
 }
 
+std::unique_ptr<Device> parseDevice(const json& element)
+{
+    verifyIsObject(element);
+    unsigned int propertyCount{0};
+
+    // Optional comments property; value not stored
+    if (element.contains("comments"))
+    {
+        ++propertyCount;
+    }
+
+    // Required id property
+    const json& idElement = getRequiredProperty(element, "id");
+    std::string id = parseString(idElement);
+    ++propertyCount;
+
+    // Required is_regulator property
+    const json& isRegulatorElement =
+        getRequiredProperty(element, "is_regulator");
+    bool isRegulator = parseBoolean(isRegulatorElement);
+    ++propertyCount;
+
+    // Required fru property
+    const json& fruElement = getRequiredProperty(element, "fru");
+    std::string fru = parseString(fruElement);
+    ++propertyCount;
+
+    // Required i2c_interface property
+    const json& i2cInterfaceElement =
+        getRequiredProperty(element, "i2c_interface");
+    std::unique_ptr<i2c::I2CInterface> i2cInterface =
+        parseI2CInterface(i2cInterfaceElement);
+    ++propertyCount;
+
+    // Optional presence_detection property
+    // TODO: Not implemented yet
+    std::unique_ptr<PresenceDetection> presenceDetection{};
+    // auto presenceDetectionIt = element.find("presence_detection");
+    // if (presenceDetectionIt != element.end())
+    // {
+    //     presenceDetection = parsePresenceDetection(*presenceDetectionIt);
+    //     ++propertyCount;
+    // }
+
+    // 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;
+    // }
+
+    // Optional rails property
+    std::vector<std::unique_ptr<Rail>> rails{};
+    auto railsIt = element.find("rails");
+    if (railsIt != element.end())
+    {
+        if (!isRegulator)
+        {
+            throw std::invalid_argument{
+                "Invalid rails property when is_regulator is false"};
+        }
+        rails = parseRailArray(*railsIt);
+        ++propertyCount;
+    }
+
+    // Verify no invalid properties exist
+    verifyPropertyCount(element, propertyCount);
+
+    return std::make_unique<Device>(id, isRegulator, fru,
+                                    std::move(i2cInterface),
+                                    std::move(presenceDetection),
+                                    std::move(configuration), std::move(rails));
+}
+
 std::vector<std::unique_ptr<Device>> parseDeviceArray(const json& element)
 {
     verifyIsArray(element);
     std::vector<std::unique_ptr<Device>> devices;
-    // TODO: Not implemented yet
-    // for (auto& deviceElement : element)
-    // {
-    //     devices.emplace_back(parseDevice(deviceElement));
-    // }
+    for (auto& deviceElement : element)
+    {
+        devices.emplace_back(parseDevice(deviceElement));
+    }
     return devices;
 }
 
@@ -248,6 +324,25 @@
     return values;
 }
 
+std::unique_ptr<i2c::I2CInterface> parseI2CInterface(const json& element)
+{
+    verifyIsObject(element);
+    unsigned int propertyCount{0};
+
+    // Required bus property
+    const json& busElement = getRequiredProperty(element, "bus");
+    uint8_t bus = parseUint8(busElement);
+    ++propertyCount;
+
+    // Required address property
+    const json& addressElement = getRequiredProperty(element, "address");
+    uint8_t address = parseHexByte(addressElement);
+    ++propertyCount;
+
+    verifyPropertyCount(element, propertyCount);
+    return i2c::create(bus, address, i2c::I2CInterface::InitialState::CLOSED);
+}
+
 std::unique_ptr<I2CWriteBitAction> parseI2CWriteBit(const json& element)
 {
     verifyIsObject(element);
@@ -394,6 +489,18 @@
                                                          exponent, isVerified);
 }
 
+std::vector<std::unique_ptr<Rail>> parseRailArray(const json& element)
+{
+    verifyIsArray(element);
+    std::vector<std::unique_ptr<Rail>> rails;
+    // TODO: Not implemented yet
+    // for (auto& railElement : element)
+    // {
+    //     rails.emplace_back(parseRail(railElement));
+    // }
+    return rails;
+}
+
 std::tuple<std::vector<std::unique_ptr<Rule>>,
            std::vector<std::unique_ptr<Chassis>>>
     parseRoot(const json& element)
diff --git a/phosphor-regulators/src/config_file_parser.hpp b/phosphor-regulators/src/config_file_parser.hpp
index 3ebab4b..35178ea 100644
--- a/phosphor-regulators/src/config_file_parser.hpp
+++ b/phosphor-regulators/src/config_file_parser.hpp
@@ -17,11 +17,15 @@
 
 #include "action.hpp"
 #include "chassis.hpp"
+#include "configuration.hpp"
 #include "device.hpp"
+#include "i2c_interface.hpp"
 #include "i2c_write_bit_action.hpp"
 #include "i2c_write_byte_action.hpp"
 #include "i2c_write_bytes_action.hpp"
 #include "pmbus_write_vout_command_action.hpp"
+#include "presence_detection.hpp"
+#include "rail.hpp"
 #include "rule.hpp"
 #include "run_rule_action.hpp"
 
@@ -198,6 +202,18 @@
     parseChassisArray(const nlohmann::json& element);
 
 /**
+ * Parses a JSON element containing a device.
+ *
+ * Returns the corresponding C++ Device object.
+ *
+ * Throws an exception if parsing fails.
+ *
+ * @param element JSON element
+ * @return Device object
+ */
+std::unique_ptr<Device> parseDevice(const nlohmann::json& element);
+
+/**
  * Parses a JSON element containing an array of devices.
  *
  * Returns the corresponding C++ Device objects.
@@ -278,6 +294,19 @@
 std::vector<uint8_t> parseHexByteArray(const nlohmann::json& element);
 
 /**
+ * Parses a JSON element containing an i2c_interface.
+ *
+ * Returns the corresponding C++ i2c::I2CInterface object.
+ *
+ * Throws an exception if parsing fails.
+ *
+ * @param element JSON element
+ * @return i2c::I2CInterface object
+ */
+std::unique_ptr<i2c::I2CInterface>
+    parseI2CInterface(const nlohmann::json& element);
+
+/**
  * Parses a JSON element containing an i2c_write_bit action.
  *
  * Returns the corresponding C++ I2CWriteBitAction object.
@@ -355,6 +384,19 @@
     parsePMBusWriteVoutCommand(const nlohmann::json& element);
 
 /**
+ * Parses a JSON element containing an array of rails.
+ *
+ * Returns the corresponding C++ Rail objects.
+ *
+ * Throws an exception if parsing fails.
+ *
+ * @param element JSON element
+ * @return vector of Rail objects
+ */
+std::vector<std::unique_ptr<Rail>>
+    parseRailArray(const nlohmann::json& element);
+
+/**
  * Parses the JSON root element of the entire configuration file.
  *
  * Returns the corresponding C++ Rule and Chassis objects.
diff --git a/phosphor-regulators/test/config_file_parser_tests.cpp b/phosphor-regulators/test/config_file_parser_tests.cpp
index 92706a3..1999b7b 100644
--- a/phosphor-regulators/test/config_file_parser_tests.cpp
+++ b/phosphor-regulators/test/config_file_parser_tests.cpp
@@ -17,6 +17,7 @@
 #include "chassis.hpp"
 #include "config_file_parser.hpp"
 #include "config_file_parser_error.hpp"
+#include "configuration.hpp"
 #include "device.hpp"
 #include "i2c_interface.hpp"
 #include "i2c_write_bit_action.hpp"
@@ -24,6 +25,8 @@
 #include "i2c_write_bytes_action.hpp"
 #include "pmbus_utils.hpp"
 #include "pmbus_write_vout_command_action.hpp"
+#include "presence_detection.hpp"
+#include "rail.hpp"
 #include "rule.hpp"
 #include "run_rule_action.hpp"
 #include "tmp_file.hpp"
@@ -575,8 +578,7 @@
         )"_json;
         std::unique_ptr<Chassis> chassis = parseChassis(element);
         EXPECT_EQ(chassis->getNumber(), 1);
-        // TODO: Not implemented yet
-        // EXPECT_EQ(chassis->getDevices().size(), 0);
+        EXPECT_EQ(chassis->getDevices().size(), 0);
     }
 
     // Test where works: All properties specified
@@ -601,9 +603,8 @@
         )"_json;
         std::unique_ptr<Chassis> chassis = parseChassis(element);
         EXPECT_EQ(chassis->getNumber(), 2);
-        // TODO: Not implemented yet
-        // EXPECT_EQ(chassis->getDevices().size(), 1);
-        // EXPECT_EQ(chassis->getDevices()[0]->getID(), "vdd_regulator")
+        EXPECT_EQ(chassis->getDevices().size(), 1);
+        EXPECT_EQ(chassis->getDevices()[0]->getID(), "vdd_regulator");
     }
 
     // Test where fails: number value is invalid
@@ -746,6 +747,280 @@
     }
 }
 
+TEST(ConfigFileParserTests, ParseDevice)
+{
+    // Test where works: Only required properties specified
+    {
+        const json element = R"(
+            {
+              "id": "vdd_regulator",
+              "is_regulator": true,
+              "fru": "/system/chassis/motherboard/regulator2",
+              "i2c_interface": { "bus": 1, "address": "0x70" }
+            }
+        )"_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_EQ(device->getPresenceDetection(), nullptr);
+        EXPECT_EQ(device->getConfiguration(), nullptr);
+        EXPECT_EQ(device->getRails().size(), 0);
+    }
+
+    // Test where works: All properties specified
+    // TODO: Not implemented yet
+
+    // Test where fails: id value is invalid
+    try
+    {
+        const json element = R"(
+            {
+              "id": 3,
+              "is_regulator": true,
+              "fru": "/system/chassis/motherboard/regulator2",
+              "i2c_interface":
+              {
+                  "bus": 1,
+                  "address": "0x70"
+              }
+            }
+        )"_json;
+        parseDevice(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: is_regulator value is invalid
+    try
+    {
+        const json element = R"(
+            {
+              "id": "vdd_regulator",
+              "is_regulator": 3,
+              "fru": "/system/chassis/motherboard/regulator2",
+              "i2c_interface":
+              {
+                  "bus": 1,
+                  "address": "0x70"
+              }
+            }
+        )"_json;
+        parseDevice(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not a boolean");
+    }
+
+    // Test where fails: fru value is invalid
+    try
+    {
+        const json element = R"(
+            {
+              "id": "vdd_regulator",
+              "is_regulator": true,
+              "fru": 2,
+              "i2c_interface":
+              {
+                  "bus": 1,
+                  "address": "0x70"
+              }
+            }
+        )"_json;
+        parseDevice(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: i2c_interface value is invalid
+    try
+    {
+        const json element = R"(
+            {
+              "id": "vdd_regulator",
+              "is_regulator": true,
+              "fru": "/system/chassis/motherboard/regulator2",
+              "i2c_interface": 3
+            }
+        )"_json;
+        parseDevice(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: Required id property not specified
+    try
+    {
+        const json element = R"(
+            {
+              "is_regulator": true,
+              "fru": "/system/chassis/motherboard/regulator2",
+              "i2c_interface":
+              {
+                  "bus": 1,
+                  "address": "0x70"
+              }
+            }
+        )"_json;
+        parseDevice(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Required property missing: id");
+    }
+
+    // Test where fails: Required is_regulator property not specified
+    try
+    {
+        const json element = R"(
+            {
+              "id": "vdd_regulator",
+              "fru": "/system/chassis/motherboard/regulator2",
+              "i2c_interface":
+              {
+                  "bus": 1,
+                  "address": "0x70"
+              }
+            }
+        )"_json;
+        parseDevice(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Required property missing: is_regulator");
+    }
+
+    // Test where fails: Required fru property not specified
+    try
+    {
+        const json element = R"(
+            {
+              "id": "vdd_regulator",
+              "is_regulator": true,
+              "i2c_interface":
+              {
+                  "bus": 1,
+                  "address": "0x70"
+              }
+            }
+        )"_json;
+        parseDevice(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Required property missing: fru");
+    }
+
+    // Test where fails: Required i2c_interface property not specified
+    try
+    {
+        const json element = R"(
+            {
+              "id": "vdd_regulator",
+              "is_regulator": true,
+              "fru": "/system/chassis/motherboard/regulator2"
+            }
+        )"_json;
+        parseDevice(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Required property missing: i2c_interface");
+    }
+
+    // Test where fails: Element is not an object
+    try
+    {
+        const json element = R"( [ "0xFF", "0x01" ] )"_json;
+        parseDevice(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"(
+            {
+              "id": "vdd_regulator",
+              "is_regulator": true,
+              "fru": "/system/chassis/motherboard/regulator2",
+              "i2c_interface": { "bus": 1, "address": "0x70" },
+              "foo" : true
+            }
+        )"_json;
+        parseDevice(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, ParseDeviceArray)
+{
+    // Test where works
+    {
+        const json element = R"(
+            [
+              {
+                "id": "vdd_regulator",
+                "is_regulator": true,
+                "fru": "/system/chassis/motherboard/regulator2",
+                "i2c_interface": { "bus": 1, "address": "0x70" }
+              },
+              {
+                "id": "vio_regulator",
+                "is_regulator": true,
+                "fru": "/system/chassis/motherboard/regulator2",
+                "i2c_interface": { "bus": 1, "address": "0x71" }
+              }
+            ]
+        )"_json;
+        std::vector<std::unique_ptr<Device>> devices =
+            parseDeviceArray(element);
+        EXPECT_EQ(devices.size(), 2);
+        EXPECT_EQ(devices[0]->getID(), "vdd_regulator");
+        EXPECT_EQ(devices[1]->getID(), "vio_regulator");
+    }
+
+    // Test where fails: Element is not an array
+    try
+    {
+        const json element = R"(
+            {
+              "foo": "bar"
+            }
+        )"_json;
+        parseDeviceArray(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(ConfigFileParserTests, ParseDouble)
 {
     // Test where works: floating point value