json_parser_utils: Add variable support
Enhance the json_parser_utils functions to support optional usage of
variables in JSON values.
Variables are specified using the syntax `${variable_name}`.
Variable values are specified in an optional new parameter to the
parsing functions. Parsing functions will replace the variable with the
corresponding variable value.
Example:
```
{
"inventory_path": "/xyz/openbmc_project/inventory/system/chassis${chassis_number}"
}
```
Tested:
* Ran automated test cases.
Change-Id: Ib8f5d9b27ccc96ca9d16eb9a044321233f81ba18
Signed-off-by: Shawn McCarney <shawnmm@us.ibm.com>
diff --git a/json_parser_utils.cpp b/json_parser_utils.cpp
index be34323..25b2e9a 100644
--- a/json_parser_utils.cpp
+++ b/json_parser_utils.cpp
@@ -16,18 +16,217 @@
#include "json_parser_utils.hpp"
+#include <charconv>
+#include <regex>
+
namespace phosphor::power::json_parser_utils
{
-std::vector<uint8_t> parseHexByteArray(const nlohmann::json& element)
+const std::map<std::string, std::string> NO_VARIABLES{};
+
+static std::regex VARIABLE_REGEX{R"(\$\{([A-Za-z0-9_]+)\})"};
+
+uint8_t parseBitPosition(const nlohmann::json& element,
+ const std::map<std::string, std::string>& variables)
+{
+ int value = parseInteger(element, variables);
+ if ((value < 0) || (value > 7))
+ {
+ throw std::invalid_argument{"Element is not a bit position"};
+ }
+ return static_cast<uint8_t>(value);
+}
+
+uint8_t parseBitValue(const nlohmann::json& element,
+ const std::map<std::string, std::string>& variables)
+{
+ int value = parseInteger(element, variables);
+ if ((value < 0) || (value > 1))
+ {
+ throw std::invalid_argument{"Element is not a bit value"};
+ }
+ return static_cast<uint8_t>(value);
+}
+
+bool parseBoolean(const nlohmann::json& element,
+ const std::map<std::string, std::string>& variables)
+{
+ if (element.is_boolean())
+ {
+ return element.get<bool>();
+ }
+
+ if (element.is_string() && !variables.empty())
+ {
+ std::string value = parseString(element, true, variables);
+ if (value == "true")
+ {
+ return true;
+ }
+ else if (value == "false")
+ {
+ return false;
+ }
+ }
+
+ throw std::invalid_argument{"Element is not a boolean"};
+}
+
+double parseDouble(const nlohmann::json& element,
+ const std::map<std::string, std::string>& variables)
+{
+ if (element.is_number())
+ {
+ return element.get<double>();
+ }
+
+ if (element.is_string() && !variables.empty())
+ {
+ std::string strValue = parseString(element, true, variables);
+ const char* first = strValue.data();
+ const char* last = strValue.data() + strValue.size();
+ double value;
+ auto [ptr, ec] = std::from_chars(first, last, value);
+ if ((ptr == last) && (ec == std::errc()))
+ {
+ return value;
+ }
+ }
+
+ throw std::invalid_argument{"Element is not a double"};
+}
+
+uint8_t parseHexByte(const nlohmann::json& element,
+ const std::map<std::string, std::string>& variables)
+{
+ std::string value = parseString(element, true, variables);
+ bool isHex = (value.compare(0, 2, "0x") == 0) && (value.size() > 2) &&
+ (value.size() < 5) &&
+ (value.find_first_not_of("0123456789abcdefABCDEF", 2) ==
+ std::string::npos);
+ if (!isHex)
+ {
+ throw std::invalid_argument{"Element is not hexadecimal string"};
+ }
+ return static_cast<uint8_t>(std::stoul(value, nullptr, 0));
+}
+
+std::vector<uint8_t> parseHexByteArray(
+ const nlohmann::json& element,
+ const std::map<std::string, std::string>& variables)
{
verifyIsArray(element);
std::vector<uint8_t> values;
for (auto& valueElement : element)
{
- values.emplace_back(parseHexByte(valueElement));
+ values.emplace_back(parseHexByte(valueElement, variables));
}
return values;
}
+int8_t parseInt8(const nlohmann::json& element,
+ const std::map<std::string, std::string>& variables)
+{
+ int value = parseInteger(element, variables);
+ if ((value < INT8_MIN) || (value > INT8_MAX))
+ {
+ throw std::invalid_argument{"Element is not an 8-bit signed integer"};
+ }
+ return static_cast<int8_t>(value);
+}
+
+int parseInteger(const nlohmann::json& element,
+ const std::map<std::string, std::string>& variables)
+{
+ if (element.is_number_integer())
+ {
+ return element.get<int>();
+ }
+
+ if (element.is_string() && !variables.empty())
+ {
+ std::string strValue = parseString(element, true, variables);
+ const char* first = strValue.data();
+ const char* last = strValue.data() + strValue.size();
+ int value;
+ auto [ptr, ec] = std::from_chars(first, last, value);
+ if ((ptr == last) && (ec == std::errc()))
+ {
+ return value;
+ }
+ }
+
+ throw std::invalid_argument{"Element is not an integer"};
+}
+
+std::string parseString(const nlohmann::json& element, bool isEmptyValid,
+ const std::map<std::string, std::string>& variables)
+{
+ if (!element.is_string())
+ {
+ throw std::invalid_argument{"Element is not a string"};
+ }
+ std::string value = element.get<std::string>();
+ internal::expandVariables(value, variables);
+ if (value.empty() && !isEmptyValid)
+ {
+ throw std::invalid_argument{"Element contains an empty string"};
+ }
+ return value;
+}
+
+uint8_t parseUint8(const nlohmann::json& element,
+ const std::map<std::string, std::string>& variables)
+{
+ int value = parseInteger(element, variables);
+ if ((value < 0) || (value > UINT8_MAX))
+ {
+ throw std::invalid_argument{"Element is not an 8-bit unsigned integer"};
+ }
+ return static_cast<uint8_t>(value);
+}
+
+unsigned int parseUnsignedInteger(
+ const nlohmann::json& element,
+ const std::map<std::string, std::string>& variables)
+{
+ int value = parseInteger(element, variables);
+ if (value < 0)
+ {
+ throw std::invalid_argument{"Element is not an unsigned integer"};
+ }
+ return static_cast<unsigned int>(value);
+}
+
+namespace internal
+{
+
+void expandVariables(std::string& value,
+ const std::map<std::string, std::string>& variables)
+{
+ if (variables.empty())
+ {
+ return;
+ }
+
+ std::smatch results;
+ while (std::regex_search(value, results, VARIABLE_REGEX))
+ {
+ if (results.size() != 2)
+ {
+ throw std::runtime_error{
+ "Unexpected regular expression match result while parsing string"};
+ }
+ const std::string& variable = results[1];
+ auto it = variables.find(variable);
+ if (it == variables.end())
+ {
+ throw std::invalid_argument{"Undefined variable: " + variable};
+ }
+ value.replace(results.position(0), results.length(0), it->second);
+ }
+}
+
+} // namespace internal
+
} // namespace phosphor::power::json_parser_utils
diff --git a/json_parser_utils.hpp b/json_parser_utils.hpp
index 6dba42b..06ac19f 100644
--- a/json_parser_utils.hpp
+++ b/json_parser_utils.hpp
@@ -18,6 +18,7 @@
#include <nlohmann/json.hpp>
#include <cstdint>
+#include <map>
#include <stdexcept>
#include <string>
#include <vector>
@@ -26,11 +27,28 @@
* @namespace json_parser_utils
*
* Contains utility functions for parsing JSON data.
+ *
+ * ## Variables
+ * The parsing functions support optional usage of variables. JSON string values
+ * can contain one or more variables. A variable is specified using the format
+ * `${variable_name}`. A variables map is specified to parsing functions that
+ * provides the variable values. Any variable in a JSON string value will be
+ * replaced by the variable value.
+ *
+ * Variables can only appear in a JSON string value. The parsing functions for
+ * other data types, such as integer and double, support a string value if it
+ * contains a variable. After variable expansion, the string is converted to the
+ * expected data type.
*/
namespace phosphor::power::json_parser_utils
{
/**
+ * Empty variables map used as a default value for parsing functions.
+ */
+extern const std::map<std::string, std::string> NO_VARIABLES;
+
+/**
* Returns the specified property of the specified JSON element.
*
* Throws an invalid_argument exception if the property does not exist.
@@ -62,22 +80,12 @@
* Throws an exception if parsing fails.
*
* @param element JSON element
+ * @param variables variables map used to expand variables in element value
* @return uint8_t value
*/
-inline uint8_t parseBitPosition(const nlohmann::json& element)
-{
- // Verify element contains an integer
- if (!element.is_number_integer())
- {
- throw std::invalid_argument{"Element is not an integer"};
- }
- int value = element.get<int>();
- if ((value < 0) || (value > 7))
- {
- throw std::invalid_argument{"Element is not a bit position"};
- }
- return static_cast<uint8_t>(value);
-}
+uint8_t parseBitPosition(
+ const nlohmann::json& element,
+ const std::map<std::string, std::string>& variables = NO_VARIABLES);
/**
* Parses a JSON element containing a bit value (0 or 1).
@@ -87,22 +95,12 @@
* Throws an exception if parsing fails.
*
* @param element JSON element
+ * @param variables variables map used to expand variables in element value
* @return uint8_t value
*/
-inline uint8_t parseBitValue(const nlohmann::json& element)
-{
- // Verify element contains an integer
- if (!element.is_number_integer())
- {
- throw std::invalid_argument{"Element is not an integer"};
- }
- int value = element.get<int>();
- if ((value < 0) || (value > 1))
- {
- throw std::invalid_argument{"Element is not a bit value"};
- }
- return static_cast<uint8_t>(value);
-}
+uint8_t parseBitValue(
+ const nlohmann::json& element,
+ const std::map<std::string, std::string>& variables = NO_VARIABLES);
/**
* Parses a JSON element containing a boolean.
@@ -112,17 +110,12 @@
* Throws an exception if parsing fails.
*
* @param element JSON element
+ * @param variables variables map used to expand variables in element value
* @return boolean value
*/
-inline bool parseBoolean(const nlohmann::json& element)
-{
- // Verify element contains a boolean
- if (!element.is_boolean())
- {
- throw std::invalid_argument{"Element is not a boolean"};
- }
- return element.get<bool>();
-}
+bool parseBoolean(
+ const nlohmann::json& element,
+ const std::map<std::string, std::string>& variables = NO_VARIABLES);
/**
* Parses a JSON element containing a double (floating point number).
@@ -132,51 +125,31 @@
* Throws an exception if parsing fails.
*
* @param element JSON element
+ * @param variables variables map used to expand variables in element value
* @return double value
*/
-inline double parseDouble(const nlohmann::json& element)
-{
- // Verify element contains a number (integer or floating point)
- if (!element.is_number())
- {
- throw std::invalid_argument{"Element is not a number"};
- }
- return element.get<double>();
-}
+double parseDouble(
+ const nlohmann::json& element,
+ const std::map<std::string, std::string>& variables = NO_VARIABLES);
/**
* Parses a JSON element containing a byte value expressed as a hexadecimal
* string.
*
* The JSON number data type does not support the hexadecimal format. For this
- * reason, hexadecimal byte values are stored as strings in the configuration
- * file.
+ * reason, a hexadecimal byte value is stored in a JSON string.
*
* Returns the corresponding C++ uint8_t value.
*
* Throws an exception if parsing fails.
*
* @param element JSON element
+ * @param variables variables map used to expand variables in element value
* @return uint8_t value
*/
-inline uint8_t parseHexByte(const nlohmann::json& element)
-{
- if (!element.is_string())
- {
- throw std::invalid_argument{"Element is not a string"};
- }
- std::string value = element.get<std::string>();
-
- bool isHex = (value.compare(0, 2, "0x") == 0) && (value.size() > 2) &&
- (value.size() < 5) &&
- (value.find_first_not_of("0123456789abcdefABCDEF", 2) ==
- std::string::npos);
- if (!isHex)
- {
- throw std::invalid_argument{"Element is not hexadecimal string"};
- }
- return static_cast<uint8_t>(std::stoul(value, nullptr, 0));
-}
+uint8_t parseHexByte(
+ const nlohmann::json& element,
+ const std::map<std::string, std::string>& variables = NO_VARIABLES);
/**
* Parses a JSON element containing an array of byte values expressed as a
@@ -187,9 +160,12 @@
* Throws an exception if parsing fails.
*
* @param element JSON element
+ * @param variables variables map used to expand variables in element value
* @return vector of uint8_t
*/
-std::vector<uint8_t> parseHexByteArray(const nlohmann::json& element);
+std::vector<uint8_t> parseHexByteArray(
+ const nlohmann::json& element,
+ const std::map<std::string, std::string>& variables = NO_VARIABLES);
/**
* Parses a JSON element containing an 8-bit signed integer.
@@ -199,22 +175,27 @@
* Throws an exception if parsing fails.
*
* @param element JSON element
+ * @param variables variables map used to expand variables in element value
* @return int8_t value
*/
-inline int8_t parseInt8(const nlohmann::json& element)
-{
- // Verify element contains an integer
- if (!element.is_number_integer())
- {
- throw std::invalid_argument{"Element is not an integer"};
- }
- int value = element.get<int>();
- if ((value < INT8_MIN) || (value > INT8_MAX))
- {
- throw std::invalid_argument{"Element is not an 8-bit signed integer"};
- }
- return static_cast<int8_t>(value);
-}
+int8_t parseInt8(
+ const nlohmann::json& element,
+ const std::map<std::string, std::string>& variables = NO_VARIABLES);
+
+/**
+ * Parses a JSON element containing an integer.
+ *
+ * Returns the corresponding C++ int value.
+ *
+ * Throws an exception if parsing fails.
+ *
+ * @param element JSON element
+ * @param variables variables map used to expand variables in element value
+ * @return integer value
+ */
+int parseInteger(
+ const nlohmann::json& element,
+ const std::map<std::string, std::string>& variables = NO_VARIABLES);
/**
* Parses a JSON element containing a string.
@@ -225,22 +206,12 @@
*
* @param element JSON element
* @param isEmptyValid indicates whether an empty string value is valid
+ * @param variables variables map used to expand variables in element value
* @return string value
*/
-inline std::string parseString(const nlohmann::json& element,
- bool isEmptyValid = false)
-{
- if (!element.is_string())
- {
- throw std::invalid_argument{"Element is not a string"};
- }
- std::string value = element.get<std::string>();
- if (value.empty() && !isEmptyValid)
- {
- throw std::invalid_argument{"Element contains an empty string"};
- }
- return value;
-}
+std::string parseString(
+ const nlohmann::json& element, bool isEmptyValid = false,
+ const std::map<std::string, std::string>& variables = NO_VARIABLES);
/**
* Parses a JSON element containing an 8-bit unsigned integer.
@@ -250,22 +221,12 @@
* Throws an exception if parsing fails.
*
* @param element JSON element
+ * @param variables variables map used to expand variables in element value
* @return uint8_t value
*/
-inline uint8_t parseUint8(const nlohmann::json& element)
-{
- // Verify element contains an integer
- if (!element.is_number_integer())
- {
- throw std::invalid_argument{"Element is not an integer"};
- }
- int value = element.get<int>();
- if ((value < 0) || (value > UINT8_MAX))
- {
- throw std::invalid_argument{"Element is not an 8-bit unsigned integer"};
- }
- return static_cast<uint8_t>(value);
-}
+uint8_t parseUint8(
+ const nlohmann::json& element,
+ const std::map<std::string, std::string>& variables = NO_VARIABLES);
/**
* Parses a JSON element containing an unsigned integer.
@@ -275,17 +236,12 @@
* Throws an exception if parsing fails.
*
* @param element JSON element
+ * @param variables variables map used to expand variables in element value
* @return unsigned int value
*/
-inline unsigned int parseUnsignedInteger(const nlohmann::json& element)
-{
- // Verify element contains an unsigned integer
- if (!element.is_number_unsigned())
- {
- throw std::invalid_argument{"Element is not an unsigned integer"};
- }
- return element.get<unsigned int>();
-}
+unsigned int parseUnsignedInteger(
+ const nlohmann::json& element,
+ const std::map<std::string, std::string>& variables = NO_VARIABLES);
/**
* Verifies that the specified JSON element is a JSON array.
@@ -337,4 +293,24 @@
}
}
+namespace internal
+{
+
+/**
+ * Expands any variables that appear in the specified string value.
+ *
+ * Does nothing if the variables map is empty or the value contains no
+ * variables.
+ *
+ * Throws an invalid_argument exception if a variable occurs in the value that
+ * does not exist in the variables map.
+ *
+ * @param value string value in which to perform variable expansion
+ * @param variables variables map containing variable values
+ */
+void expandVariables(std::string& value,
+ const std::map<std::string, std::string>& variables);
+
+} // namespace internal
+
} // namespace phosphor::power::json_parser_utils
diff --git a/phosphor-power-sequencer/src/chassis.hpp b/phosphor-power-sequencer/src/chassis.hpp
index 8227953..d40ad7a 100644
--- a/phosphor-power-sequencer/src/chassis.hpp
+++ b/phosphor-power-sequencer/src/chassis.hpp
@@ -40,7 +40,6 @@
class Chassis
{
public:
- // Specify which compiler-generated methods we want
Chassis() = delete;
Chassis(const Chassis&) = delete;
Chassis(Chassis&&) = delete;
diff --git a/phosphor-power-sequencer/src/system.hpp b/phosphor-power-sequencer/src/system.hpp
index 0ae8e62..e86a096 100644
--- a/phosphor-power-sequencer/src/system.hpp
+++ b/phosphor-power-sequencer/src/system.hpp
@@ -34,7 +34,6 @@
class System
{
public:
- // Specify which compiler-generated methods we want
System() = delete;
System(const System&) = delete;
System(System&&) = delete;
diff --git a/phosphor-regulators/test/config_file_parser_tests.cpp b/phosphor-regulators/test/config_file_parser_tests.cpp
index 9a68990..c218f5f 100644
--- a/phosphor-regulators/test/config_file_parser_tests.cpp
+++ b/phosphor-regulators/test/config_file_parser_tests.cpp
@@ -693,7 +693,7 @@
}
catch (const std::invalid_argument& e)
{
- EXPECT_STREQ(e.what(), "Element is not an unsigned integer");
+ EXPECT_STREQ(e.what(), "Element is not an integer");
}
// Test where fails: inventory_path is invalid: Not a string
@@ -1303,7 +1303,7 @@
}
catch (const std::invalid_argument& e)
{
- EXPECT_STREQ(e.what(), "Element is not a number");
+ EXPECT_STREQ(e.what(), "Element is not a double");
}
// Test where fails: actions object is invalid
@@ -3664,7 +3664,7 @@
}
catch (const std::invalid_argument& e)
{
- EXPECT_STREQ(e.what(), "Element is not a number");
+ EXPECT_STREQ(e.what(), "Element is not a double");
}
// Test where fails: Required format property not specified
diff --git a/test/json_parser_utils_tests.cpp b/test/json_parser_utils_tests.cpp
index e4f591c..af49977 100644
--- a/test/json_parser_utils_tests.cpp
+++ b/test/json_parser_utils_tests.cpp
@@ -26,6 +26,7 @@
#include <gtest/gtest.h>
using namespace phosphor::power::json_parser_utils;
+using namespace phosphor::power::json_parser_utils::internal;
using json = nlohmann::json;
TEST(JSONParserUtilsTests, GetRequiredProperty)
@@ -66,6 +67,14 @@
EXPECT_EQ(value, 7);
}
+ // Test where works: Variable specified
+ {
+ std::map<std::string, std::string> variables{{"bit_pos", "3"}};
+ const json element = R"( "${bit_pos}" )"_json;
+ uint8_t value = parseBitPosition(element, variables);
+ EXPECT_EQ(value, 3);
+ }
+
// Test where fails: Element is not an integer
try
{
@@ -101,6 +110,19 @@
{
EXPECT_STREQ(e.what(), "Element is not a bit position");
}
+
+ // Test where fails: Variable specified: Value < 0
+ try
+ {
+ std::map<std::string, std::string> variables{{"bit_pos", "-1"}};
+ const json element = R"( "${bit_pos}" )"_json;
+ parseBitPosition(element, variables);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not a bit position");
+ }
}
TEST(JSONParserUtilsTests, ParseBitValue)
@@ -119,6 +141,14 @@
EXPECT_EQ(value, 1);
}
+ // Test where works: Variable specified
+ {
+ std::map<std::string, std::string> variables{{"bit_val", "1"}};
+ const json element = R"( "${bit_val}" )"_json;
+ uint8_t value = parseBitValue(element, variables);
+ EXPECT_EQ(value, 1);
+ }
+
// Test where fails: Element is not an integer
try
{
@@ -154,6 +184,19 @@
{
EXPECT_STREQ(e.what(), "Element is not a bit value");
}
+
+ // Test where fails: Variable specified: Not an integer
+ try
+ {
+ std::map<std::string, std::string> variables{{"bit_val", "one"}};
+ const json element = R"( "${bit_val}" )"_json;
+ parseBitValue(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(JSONParserUtilsTests, ParseBoolean)
@@ -172,6 +215,22 @@
EXPECT_EQ(value, false);
}
+ // Test where works: Variable specified: true
+ {
+ std::map<std::string, std::string> variables{{"bool_val", "true"}};
+ const json element = R"( "${bool_val}" )"_json;
+ bool value = parseBoolean(element, variables);
+ EXPECT_EQ(value, true);
+ }
+
+ // Test where works: Variable specified: false
+ {
+ std::map<std::string, std::string> variables{{"bool_val", "false"}};
+ const json element = R"( "${bool_val}" )"_json;
+ bool value = parseBoolean(element, variables);
+ EXPECT_EQ(value, false);
+ }
+
// Test where fails: Element is not a boolean
try
{
@@ -183,25 +242,66 @@
{
EXPECT_STREQ(e.what(), "Element is not a boolean");
}
+
+ // Test where fails: Variable specified: Variables map not specified
+ try
+ {
+ const json element = R"( "${bool_val}" )"_json;
+ parseBoolean(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: Variable specified: Value is not a boolean
+ try
+ {
+ std::map<std::string, std::string> variables{{"bool_val", "3.2"}};
+ const json element = R"( "${bool_val}" )"_json;
+ parseBoolean(element, variables);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not a boolean");
+ }
}
TEST(JSONParserUtilsTests, ParseDouble)
{
- // Test where works: floating point value
+ // Test where works: Floating point value
{
const json element = R"( 1.03 )"_json;
double value = parseDouble(element);
EXPECT_EQ(value, 1.03);
}
- // Test where works: integer value
+ // Test where works: Integer value
{
- const json element = R"( 24 )"_json;
+ const json element = R"( -24 )"_json;
double value = parseDouble(element);
+ EXPECT_EQ(value, -24.0);
+ }
+
+ // Test where works: Variable specified: Floating point value
+ {
+ std::map<std::string, std::string> variables{{"var", "-1.03"}};
+ const json element = R"( "${var}" )"_json;
+ double value = parseDouble(element, variables);
+ EXPECT_EQ(value, -1.03);
+ }
+
+ // Test where works: Variable specified: Integer value
+ {
+ std::map<std::string, std::string> variables{{"var", "24"}};
+ const json element = R"( "${var}" )"_json;
+ double value = parseDouble(element, variables);
EXPECT_EQ(value, 24.0);
}
- // Test where fails: Element is not a number
+ // Test where fails: Element is not a double
try
{
const json element = R"( true )"_json;
@@ -210,7 +310,84 @@
}
catch (const std::invalid_argument& e)
{
- EXPECT_STREQ(e.what(), "Element is not a number");
+ EXPECT_STREQ(e.what(), "Element is not a double");
+ }
+
+ // Test where fails: Variable specified: Variables map not specified
+ try
+ {
+ const json element = R"( "${var}" )"_json;
+ parseDouble(element);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not a double");
+ }
+
+ // Test where fails: Variable specified: Leading whitespace
+ try
+ {
+ std::map<std::string, std::string> variables{{"var", " -1.03"}};
+ const json element = R"( "${var}" )"_json;
+ parseDouble(element, variables);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not a double");
+ }
+
+ // Test where fails: Variable specified: Trailing whitespace
+ try
+ {
+ std::map<std::string, std::string> variables{{"var", "-1.03 "}};
+ const json element = R"( "${var}" )"_json;
+ parseDouble(element, variables);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not a double");
+ }
+
+ // Test where fails: Variable specified: Starts with non-number character
+ try
+ {
+ std::map<std::string, std::string> variables{{"var", "x-1.03"}};
+ const json element = R"( "${var}" )"_json;
+ parseDouble(element, variables);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not a double");
+ }
+
+ // Test where fails: Variable specified: Ends with non-number character
+ try
+ {
+ std::map<std::string, std::string> variables{{"var", "-1.03x"}};
+ const json element = R"( "${var}" )"_json;
+ parseDouble(element, variables);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not a double");
+ }
+
+ // Test where fails: Variable specified: Not a double
+ try
+ {
+ std::map<std::string, std::string> variables{{"var", "foo"}};
+ const json element = R"( "${var}" )"_json;
+ parseDouble(element, variables);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not a double");
}
}
@@ -237,6 +414,14 @@
EXPECT_EQ(value, 0xf);
}
+ // Test where works: Variable specified
+ {
+ std::map<std::string, std::string> variables{{"var", "ed"}};
+ const json element = R"( "0x${var}" )"_json;
+ uint8_t value = parseHexByte(element, variables);
+ EXPECT_EQ(value, 0xed);
+ }
+
// Test where fails: "0xfff"
try
{
@@ -320,6 +505,19 @@
{
EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
}
+
+ // Test where fails: Variable specified: Not a hex string
+ try
+ {
+ std::map<std::string, std::string> variables{{"var", "0xsz"}};
+ const json element = R"( "${var}" )"_json;
+ parseHexByte(element, variables);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
+ }
}
TEST(JSONParserUtilsTests, ParseHexByteArray)
@@ -332,6 +530,16 @@
EXPECT_EQ(hexBytes, expected);
}
+ // Test where works: Variables specified
+ {
+ std::map<std::string, std::string> variables{{"var1", "0xCC"},
+ {"var2", "0xFF"}};
+ const json element = R"( [ "${var1}", "${var2}" ] )"_json;
+ std::vector<uint8_t> hexBytes = parseHexByteArray(element, variables);
+ std::vector<uint8_t> expected = {0xcc, 0xff};
+ EXPECT_EQ(hexBytes, expected);
+ }
+
// Test where fails: Element is not an array
try
{
@@ -343,6 +551,20 @@
{
EXPECT_STREQ(e.what(), "Element is not an array");
}
+
+ // Test where fails: Variables specified: Invalid byte value
+ try
+ {
+ std::map<std::string, std::string> variables{{"var1", "0xCC"},
+ {"var2", "99"}};
+ const json element = R"( [ "${var1}", "${var2}" ] )"_json;
+ parseHexByteArray(element, variables);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
+ }
}
TEST(JSONParserUtilsTests, ParseInt8)
@@ -361,6 +583,14 @@
EXPECT_EQ(value, 127);
}
+ // Test where works: Variable specified
+ {
+ std::map<std::string, std::string> variables{{"var", "-23"}};
+ const json element = R"( "${var}" )"_json;
+ int8_t value = parseInt8(element, variables);
+ EXPECT_EQ(value, -23);
+ }
+
// Test where fails: Element is not an integer
try
{
@@ -396,6 +626,148 @@
{
EXPECT_STREQ(e.what(), "Element is not an 8-bit signed integer");
}
+
+ // Test where fails: Variable specified: Value > INT8_MAX
+ try
+ {
+ std::map<std::string, std::string> variables{{"var", "128"}};
+ const json element = R"( "${var}" )"_json;
+ parseInt8(element, variables);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not an 8-bit signed integer");
+ }
+}
+
+TEST(JSONParserUtilsTests, ParseInteger)
+{
+ // Test where works: Zero
+ {
+ const json element = R"( 0 )"_json;
+ int value = parseInteger(element);
+ EXPECT_EQ(value, 0);
+ }
+
+ // Test where works: Positive value
+ {
+ const json element = R"( 103 )"_json;
+ int value = parseInteger(element);
+ EXPECT_EQ(value, 103);
+ }
+
+ // Test where works: Negative value
+ {
+ const json element = R"( -24 )"_json;
+ int value = parseInteger(element);
+ EXPECT_EQ(value, -24);
+ }
+
+ // Test where works: Variable specified: Positive value
+ {
+ std::map<std::string, std::string> variables{{"var", "1024"}};
+ const json element = R"( "${var}" )"_json;
+ int value = parseInteger(element, variables);
+ EXPECT_EQ(value, 1024);
+ }
+
+ // Test where works: Variable specified: Negative value
+ {
+ std::map<std::string, std::string> variables{{"var", "-9924"}};
+ const json element = R"( "${var}" )"_json;
+ int value = parseInteger(element, variables);
+ EXPECT_EQ(value, -9924);
+ }
+
+ // Test where fails: Element is not a integer
+ try
+ {
+ const json element = R"( true )"_json;
+ parseInteger(element);
+ 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: Variable specified: Variables map not specified
+ try
+ {
+ const json element = R"( "${var}" )"_json;
+ parseInteger(element);
+ 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: Variable specified: Leading whitespace
+ try
+ {
+ std::map<std::string, std::string> variables{{"var", " -13"}};
+ const json element = R"( "${var}" )"_json;
+ parseInteger(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: Variable specified: Trailing whitespace
+ try
+ {
+ std::map<std::string, std::string> variables{{"var", "-13 "}};
+ const json element = R"( "${var}" )"_json;
+ parseInteger(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: Variable specified: Starts with non-number character
+ try
+ {
+ std::map<std::string, std::string> variables{{"var", "x-13"}};
+ const json element = R"( "${var}" )"_json;
+ parseInteger(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: Variable specified: Ends with non-number character
+ try
+ {
+ std::map<std::string, std::string> variables{{"var", "-13x"}};
+ const json element = R"( "${var}" )"_json;
+ parseInteger(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: Variable specified: Not an integer
+ try
+ {
+ std::map<std::string, std::string> variables{{"var", "foo"}};
+ const json element = R"( "${var}" )"_json;
+ parseInteger(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(JSONParserUtilsTests, ParseString)
@@ -414,6 +786,22 @@
EXPECT_EQ(value, "vdd_regulator");
}
+ // Test where works: Variable specified: Empty string
+ {
+ std::map<std::string, std::string> variables{{"var", ""}};
+ const json element = R"( "${var}" )"_json;
+ std::string value = parseString(element, true, variables);
+ EXPECT_EQ(value, "");
+ }
+
+ // Test where works: Variable specified: Non-empty string
+ {
+ std::map<std::string, std::string> variables{{"var", "vio_regulator"}};
+ const json element = R"( "${var}" )"_json;
+ std::string value = parseString(element, false, variables);
+ EXPECT_EQ(value, "vio_regulator");
+ }
+
// Test where fails: Element is not a string
try
{
@@ -437,6 +825,32 @@
{
EXPECT_STREQ(e.what(), "Element contains an empty string");
}
+
+ // Test where fails: Variable specified: Empty string
+ try
+ {
+ std::map<std::string, std::string> variables{{"var", ""}};
+ const json element = R"( "${var}" )"_json;
+ parseString(element, false, variables);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element contains an empty string");
+ }
+
+ // Test where fails: Variable specified: Variable not defined
+ try
+ {
+ std::map<std::string, std::string> variables{{"var1", "foo"}};
+ const json element = R"( "${var2}" )"_json;
+ parseString(element, false, variables);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Undefined variable: var2");
+ }
}
TEST(JSONParserUtilsTests, ParseUint8)
@@ -455,6 +869,14 @@
EXPECT_EQ(value, 255);
}
+ // Test where works: Variable specified
+ {
+ std::map<std::string, std::string> variables{{"var", "19"}};
+ const json element = R"( "${var}" )"_json;
+ uint8_t value = parseUint8(element, variables);
+ EXPECT_EQ(value, 19);
+ }
+
// Test where fails: Element is not an integer
try
{
@@ -490,6 +912,19 @@
{
EXPECT_STREQ(e.what(), "Element is not an 8-bit unsigned integer");
}
+
+ // Test where fails: Variable specified: Value > UINT8_MAX
+ try
+ {
+ std::map<std::string, std::string> variables{{"var", "256"}};
+ const json element = R"( "${var}" )"_json;
+ parseUint8(element, variables);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not an 8-bit unsigned integer");
+ }
}
TEST(JSONParserUtilsTests, ParseUnsignedInteger)
@@ -501,6 +936,14 @@
EXPECT_EQ(value, 1);
}
+ // Test where works: Variable specified
+ {
+ std::map<std::string, std::string> variables{{"var", "25678"}};
+ const json element = R"( "${var}" )"_json;
+ unsigned int value = parseUnsignedInteger(element, variables);
+ EXPECT_EQ(value, 25678);
+ }
+
// Test where fails: Element is not an integer
try
{
@@ -510,7 +953,7 @@
}
catch (const std::invalid_argument& e)
{
- EXPECT_STREQ(e.what(), "Element is not an unsigned integer");
+ EXPECT_STREQ(e.what(), "Element is not an integer");
}
// Test where fails: Value < 0
@@ -524,6 +967,19 @@
{
EXPECT_STREQ(e.what(), "Element is not an unsigned integer");
}
+
+ // Test where fails: Variable specified: Value < 0
+ try
+ {
+ std::map<std::string, std::string> variables{{"var", "-23"}};
+ const json element = R"( "${var}" )"_json;
+ parseUnsignedInteger(element, variables);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Element is not an unsigned integer");
+ }
}
TEST(JSONParserUtilsTests, VerifyIsArray)
@@ -599,3 +1055,154 @@
EXPECT_STREQ(e.what(), "Element contains an invalid property");
}
}
+
+TEST(JSONParserUtilsTests, ExpandVariables)
+{
+ // Test where works: Single variable: Variable is entire value: Lower case
+ // in variable name
+ {
+ std::map<std::string, std::string> variables{{"var", "vio_regulator"}};
+ std::string value{"${var}"};
+ expandVariables(value, variables);
+ EXPECT_EQ(value, "vio_regulator");
+ }
+
+ // Test where works: Multiple variables: Variables are part of value: Upper
+ // case and underscore in variable name
+ {
+ std::map<std::string, std::string> variables{
+ {"CHASSIS_NUMBER", "1"}, {"REGULATOR", "vcs_vio"}, {"RAIL", "vio"}};
+ std::string value{
+ "chassis${CHASSIS_NUMBER}_${REGULATOR}_regulator_${RAIL}_rail"};
+ expandVariables(value, variables);
+ EXPECT_EQ(value, "chassis1_vcs_vio_regulator_vio_rail");
+ }
+
+ // Test where works: Variable at start of value: Number in variable name
+ {
+ std::map<std::string, std::string> variables{{"var1", "vio_regulator"}};
+ std::string value{"${var1}_rail"};
+ expandVariables(value, variables);
+ EXPECT_EQ(value, "vio_regulator_rail");
+ }
+
+ // Test where works: Variable at end of value
+ {
+ std::map<std::string, std::string> variables{{"chassis_number", "3"}};
+ std::string value{
+ "/xyz/openbmc_project/inventory/system/chassis${chassis_number}"};
+ expandVariables(value, variables);
+ EXPECT_EQ(value, "/xyz/openbmc_project/inventory/system/chassis3");
+ }
+
+ // Test where works: Variable has empty value: Start of value
+ {
+ std::map<std::string, std::string> variables{{"chassis_prefix", ""}};
+ std::string value{"${chassis_prefix}vio_regulator"};
+ expandVariables(value, variables);
+ EXPECT_EQ(value, "vio_regulator");
+ }
+
+ // Test where works: Variable has empty value: Middle of value
+ {
+ std::map<std::string, std::string> variables{{"chassis_number", ""}};
+ std::string value{"c${chassis_number}_vio_regulator"};
+ expandVariables(value, variables);
+ EXPECT_EQ(value, "c_vio_regulator");
+ }
+
+ // Test where works: Variable has empty value: End of value
+ {
+ std::map<std::string, std::string> variables{{"chassis_number", ""}};
+ std::string value{
+ "/xyz/openbmc_project/inventory/system/chassis${chassis_number}"};
+ expandVariables(value, variables);
+ EXPECT_EQ(value, "/xyz/openbmc_project/inventory/system/chassis");
+ }
+
+ // Test where works: No variables specified
+ {
+ std::map<std::string, std::string> variables{{"var", "vio_regulator"}};
+ std::string value{"vcs_rail"};
+ expandVariables(value, variables);
+ EXPECT_EQ(value, "vcs_rail");
+ }
+
+ // Test where works: Nested variable expansion
+ {
+ std::map<std::string, std::string> variables{{"var1", "${var2}"},
+ {"var2", "vio_reg"}};
+ std::string value{"${var1}_rail"};
+ expandVariables(value, variables);
+ EXPECT_EQ(value, "vio_reg_rail");
+ }
+
+ // Test where fails: Variables map is empty
+ {
+ std::map<std::string, std::string> variables{};
+ std::string value{"${var}_rail"};
+ expandVariables(value, variables);
+ EXPECT_EQ(value, "${var}_rail");
+ }
+
+ // Test where fails: Variable missing $
+ {
+ std::map<std::string, std::string> variables{{"var", "vio_reg"}};
+ std::string value{"{var}_rail"};
+ expandVariables(value, variables);
+ EXPECT_EQ(value, "{var}_rail");
+ }
+
+ // Test where fails: Variable missing {
+ {
+ std::map<std::string, std::string> variables{{"var", "vio_reg"}};
+ std::string value{"$var}_rail"};
+ expandVariables(value, variables);
+ EXPECT_EQ(value, "$var}_rail");
+ }
+
+ // Test where fails: Variable missing }
+ {
+ std::map<std::string, std::string> variables{{"var", "vio_reg"}};
+ std::string value{"${var_rail"};
+ expandVariables(value, variables);
+ EXPECT_EQ(value, "${var_rail");
+ }
+
+ // Test where fails: Variable missing name
+ {
+ std::map<std::string, std::string> variables{{"var", "vio_reg"}};
+ std::string value{"${}_rail"};
+ expandVariables(value, variables);
+ EXPECT_EQ(value, "${}_rail");
+ }
+
+ // Test where fails: Variable name has invalid characters
+ {
+ std::map<std::string, std::string> variables{{"var-2", "vio_reg"}};
+ std::string value{"${var-2}_rail"};
+ expandVariables(value, variables);
+ EXPECT_EQ(value, "${var-2}_rail");
+ }
+
+ // Test where fails: Variable has unexpected whitespace
+ {
+ std::map<std::string, std::string> variables{{"var", "vio_reg"}};
+ std::string value{"${ var }_rail"};
+ expandVariables(value, variables);
+ EXPECT_EQ(value, "${ var }_rail");
+ }
+
+ // Test where fails: Undefined variable
+ try
+ {
+ std::map<std::string, std::string> variables{{"var", "vio_reg"}};
+ std::string value{"${foo}_rail"};
+ expandVariables(value, variables);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::invalid_argument& e)
+ {
+ EXPECT_STREQ(e.what(), "Undefined variable: foo");
+ }
+}