pseq: Parsing support for i2c_interface element

Add JSON parsing support for the new i2c_interface element in the
phosphor-power-sequencer configuration file.

Tested:
* Ran automated tests.

Change-Id: I2841078b0dd69635dbb6f71461d33d885b380f08
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 07566ae..3163aff 100644
--- a/phosphor-power-sequencer/src/config_file_parser.cpp
+++ b/phosphor-power-sequencer/src/config_file_parser.cpp
@@ -118,6 +118,29 @@
     return GPIO(line, activeLow);
 }
 
+std::tuple<uint8_t, uint16_t> parseI2CInterface(
+    const nlohmann::json& element,
+    const std::map<std::string, std::string>& variables)
+{
+    verifyIsObject(element);
+    unsigned int propertyCount{0};
+
+    // Required bus property
+    const json& busElement = getRequiredProperty(element, "bus");
+    uint8_t bus = parseUint8(busElement, variables);
+    ++propertyCount;
+
+    // Required address property
+    const json& addressElement = getRequiredProperty(element, "address");
+    uint16_t address = parseHexByte(addressElement, variables);
+    ++propertyCount;
+
+    // Verify no invalid properties exist
+    verifyPropertyCount(element, propertyCount);
+
+    return {bus, address};
+}
+
 std::unique_ptr<Rail> parseRail(const json& element)
 {
     verifyIsObject(element);
diff --git a/phosphor-power-sequencer/src/config_file_parser.hpp b/phosphor-power-sequencer/src/config_file_parser.hpp
index ee12690..a57083e 100644
--- a/phosphor-power-sequencer/src/config_file_parser.hpp
+++ b/phosphor-power-sequencer/src/config_file_parser.hpp
@@ -19,9 +19,12 @@
 
 #include <nlohmann/json.hpp>
 
+#include <cstdint>
 #include <filesystem>
+#include <map>
 #include <memory>
 #include <string>
+#include <tuple>
 #include <vector>
 
 namespace phosphor::power::sequencer::config_file_parser
@@ -88,6 +91,21 @@
 GPIO parseGPIO(const nlohmann::json& element);
 
 /**
+ * Parses a JSON element containing an i2c_interface object.
+ *
+ * Returns the corresponding I2C bus and address.
+ *
+ * Throws an exception if parsing fails.
+ *
+ * @param element JSON element
+ * @param variables variables map used to expand variables in element value
+ * @return tuple containing bus and address
+ */
+std::tuple<uint8_t, uint16_t> parseI2CInterface(
+    const nlohmann::json& element,
+    const std::map<std::string, std::string>& variables);
+
+/**
  * Parses a JSON element containing a rail.
  *
  * Returns the corresponding C++ Rail object.
diff --git a/phosphor-power-sequencer/test/config_file_parser_tests.cpp b/phosphor-power-sequencer/test/config_file_parser_tests.cpp
index 9ad21c8..0fbf5fe 100644
--- a/phosphor-power-sequencer/test/config_file_parser_tests.cpp
+++ b/phosphor-power-sequencer/test/config_file_parser_tests.cpp
@@ -23,13 +23,16 @@
 
 #include <nlohmann/json.hpp>
 
+#include <cstdint>
 #include <exception>
 #include <filesystem>
 #include <fstream>
+#include <map>
 #include <memory>
 #include <optional>
 #include <stdexcept>
 #include <string>
+#include <tuple>
 #include <vector>
 
 #include <gtest/gtest.h>
@@ -428,6 +431,159 @@
     }
 }
 
+TEST(ConfigFileParserTests, ParseI2CInterface)
+{
+    // Test where works: No variables
+    {
+        const json element = R"(
+            {
+                "bus": 2,
+                "address": "0x70"
+            }
+        )"_json;
+        std::map<std::string, std::string> variables{};
+        auto [bus, address] = parseI2CInterface(element, variables);
+        EXPECT_EQ(bus, 2);
+        EXPECT_EQ(address, 0x70);
+    }
+
+    // Test where works: Variables specified
+    {
+        const json element = R"(
+            {
+                "bus": "${bus}",
+                "address": "${address}"
+            }
+        )"_json;
+        std::map<std::string, std::string> variables{{"bus", "3"},
+                                                     {"address", "0x23"}};
+        auto [bus, address] = parseI2CInterface(element, variables);
+        EXPECT_EQ(bus, 3);
+        EXPECT_EQ(address, 0x23);
+    }
+
+    // Test where fails: Element is not an object
+    try
+    {
+        const json element = R"( [ 1, "0x70" ] )"_json;
+        std::map<std::string, std::string> variables{};
+        parseI2CInterface(element, variables);
+        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 bus property not specified
+    try
+    {
+        const json element = R"(
+            {
+                "address": "0x70"
+            }
+        )"_json;
+        std::map<std::string, std::string> variables{};
+        parseI2CInterface(element, variables);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Required property missing: bus");
+    }
+
+    // Test where fails: Required address property not specified
+    try
+    {
+        const json element = R"(
+            {
+                "bus": 2
+            }
+        )"_json;
+        std::map<std::string, std::string> variables{};
+        parseI2CInterface(element, variables);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Required property missing: address");
+    }
+
+    // Test where fails: bus value is invalid
+    try
+    {
+        const json element = R"(
+            {
+                "bus": 1.1,
+                "address": "0x70"
+            }
+        )"_json;
+        std::map<std::string, std::string> variables{};
+        parseI2CInterface(element, variables);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not an integer");
+    }
+
+    // Test where fails: address value is invalid
+    try
+    {
+        const json element = R"(
+            {
+                "bus": 2,
+                "address": 70
+            }
+        )"_json;
+        std::map<std::string, std::string> variables{};
+        parseI2CInterface(element, variables);
+        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: Invalid property specified
+    try
+    {
+        const json element = R"(
+            {
+                "bus": 2,
+                "address": "0x70",
+                "foo": "bar"
+            }
+        )"_json;
+        std::map<std::string, std::string> variables{};
+        parseI2CInterface(element, variables);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element contains an invalid property");
+    }
+
+    // Test where fails: Invalid variable value specified
+    try
+    {
+        const json element = R"(
+            {
+                "bus": "${bus}",
+                "address": "${address}"
+            }
+        )"_json;
+        std::map<std::string, std::string> variables{{"bus", "foo"},
+                                                     {"address", "0x23"}};
+        parseI2CInterface(element, variables);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not an integer");
+    }
+}
+
 TEST(ConfigFileParserTests, ParseRail)
 {
     // Test where works: Only required properties specified