Added utility functions getAllProperties and unpackProperties

Tested:
  - Added example to verify that functions work correctly
  - Added new unit tests that are passing
  - All other tests are still passing after this change
  - Added handling of new type (std::monostate) which can
    be used as first type in variant to represent that none
    of the other types was matched

Change-Id: Ic8e7c8d3116d64b94be37147ae8a80ebb5d3811d
Signed-off-by: Krzysztof Grobelny <krzysztof.grobelny@intel.com>
diff --git a/test/meson.build b/test/meson.build
index 374dada..02e4550 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -28,6 +28,7 @@
     'message/native_types',
     'message/types',
     'timer',
+    'unpack_properties',
     'utility/tuple_to_array',
     'utility/type_traits',
 ]
diff --git a/test/unpack_properties.cpp b/test/unpack_properties.cpp
new file mode 100644
index 0000000..90415cb
--- /dev/null
+++ b/test/unpack_properties.cpp
@@ -0,0 +1,193 @@
+#include <boost/container/flat_map.hpp>
+#include <sdbusplus/unpack_properties.hpp>
+
+#include <gmock/gmock.h>
+
+namespace sdbusplus
+{
+
+using VariantType = std::variant<std::string, uint32_t, float, double>;
+using ContainerTypes =
+    testing::Types<std::vector<std::pair<std::string, VariantType>>,
+                   boost::container::flat_map<std::string, VariantType>,
+                   std::map<std::string, VariantType>>;
+
+template <typename Exception, typename F>
+std::optional<Exception> captureException(F&& code)
+{
+    try
+    {
+        code();
+    }
+    catch (const Exception& e)
+    {
+        return e;
+    }
+
+    return std::nullopt;
+}
+
+template <typename Container>
+struct UnpackPropertiesTest : public testing::Test
+{
+    void SetUp() override
+    {
+        using namespace std::string_literals;
+
+        data.insert(data.end(),
+                    std::make_pair("Key-1"s, VariantType("string"s)));
+        data.insert(data.end(), std::make_pair("Key-2"s, VariantType(42.f)));
+        data.insert(data.end(), std::make_pair("Key-3"s, VariantType(15.)));
+    }
+
+    Container data;
+};
+
+TYPED_TEST_SUITE(UnpackPropertiesTest, ContainerTypes);
+
+TYPED_TEST(UnpackPropertiesTest, returnsValueWhenKeyIsPresentAndTypeMatches)
+{
+    using namespace testing;
+
+    std::string val1;
+    float val2 = 0.f;
+    double val3 = 0.;
+
+    unpackProperties(this->data, "Key-1", val1, "Key-2", val2, "Key-3", val3);
+
+    ASSERT_THAT(val1, Eq("string"));
+    ASSERT_THAT(val2, FloatEq(42.f));
+    ASSERT_THAT(val3, DoubleEq(15.));
+}
+
+TYPED_TEST(UnpackPropertiesTest,
+           unpackChangesOriginalDataWhenPassedAsNonConstReference)
+{
+    using namespace testing;
+
+    std::string val1, val2;
+
+    unpackProperties(this->data, "Key-1", val1);
+    unpackProperties(this->data, "Key-1", val2);
+
+    ASSERT_THAT(val1, Eq("string"));
+    ASSERT_THAT(val2, Not(Eq("string")));
+}
+
+TYPED_TEST(UnpackPropertiesTest,
+           unpackDoesntChangeOriginalDataWhenPassesAsConstReference)
+{
+    using namespace testing;
+
+    std::string val1, val2;
+
+    unpackProperties(Const(this->data), "Key-1", val1);
+    unpackProperties(Const(this->data), "Key-1", val2);
+
+    ASSERT_THAT(val1, Eq("string"));
+    ASSERT_THAT(val2, Eq("string"));
+}
+
+TYPED_TEST(UnpackPropertiesTest, throwsErrorWhenKeyIsMissing)
+{
+    using namespace testing;
+
+    std::string val1;
+    float val2 = 0.f;
+    double val3 = 0.;
+
+    auto error = captureException<exception::UnpackPropertyError>([&] {
+        unpackProperties(this->data, "Key-1", val1, "Key-4", val2, "Key-3",
+                         val3);
+    });
+
+    ASSERT_TRUE(error);
+    ASSERT_THAT(error->reason,
+                Eq(exception::UnpackPropertyError::reasonMissingProperty));
+    ASSERT_THAT(error->propertyName, Eq("Key-4"));
+}
+
+TYPED_TEST(UnpackPropertiesTest, throwsErrorWhenTypeDoesntMatch)
+{
+    using namespace testing;
+
+    std::string val1;
+    std::string val2;
+    double val3 = 0.;
+
+    auto error = captureException<exception::UnpackPropertyError>([&] {
+        unpackProperties(this->data, "Key-1", val1, "Key-2", val2, "Key-3",
+                         val3);
+    });
+
+    ASSERT_TRUE(error);
+    ASSERT_THAT(error->reason,
+                Eq(exception::UnpackPropertyError::reasonTypeNotMatched));
+    ASSERT_THAT(error->propertyName, Eq("Key-2"));
+}
+
+TYPED_TEST(UnpackPropertiesTest,
+           returnsUndefinedValueForDuplicatedKeysWhenDataIsNonConstReference)
+{
+    using namespace testing;
+    using namespace std::string_literals;
+
+    std::string val1;
+    float val2 = 0.f;
+    double val3 = 0.;
+    std::string val4;
+
+    unpackProperties(this->data, "Key-1", val1, "Key-2", val2, "Key-3", val3,
+                     "Key-1", val4);
+
+    ASSERT_THAT(val1, Eq("string"));
+    ASSERT_THAT(val2, FloatEq(42.f));
+    ASSERT_THAT(val3, DoubleEq(15.));
+    ASSERT_THAT(val4, Not(Eq("string")));
+}
+
+TYPED_TEST(UnpackPropertiesTest,
+           returnsValueForDuplicatedKeysWhenDataIsConstReference)
+{
+    using namespace testing;
+    using namespace std::string_literals;
+
+    std::string val1;
+    float val2 = 0.f;
+    double val3 = 0.;
+    std::string val4;
+
+    unpackProperties(Const(this->data), "Key-1", val1, "Key-2", val2, "Key-3",
+                     val3, "Key-1", val4);
+
+    ASSERT_THAT(val1, Eq("string"));
+    ASSERT_THAT(val2, FloatEq(42.f));
+    ASSERT_THAT(val3, DoubleEq(15.));
+    ASSERT_THAT(val4, Eq("string"));
+}
+
+struct UnpackPropertiesTest_ForVector :
+    public UnpackPropertiesTest<
+        std::vector<std::pair<std::string, VariantType>>>
+{};
+
+TEST_F(UnpackPropertiesTest_ForVector, silentlyDiscardsDuplicatedKeyInData)
+{
+    using namespace testing;
+    using namespace std::string_literals;
+
+    std::string val1;
+    float val2 = 0.f;
+    double val3 = 0.;
+
+    this->data.insert(this->data.end(),
+                      std::make_pair("Key-1"s, VariantType("string2"s)));
+
+    unpackProperties(this->data, "Key-1", val1, "Key-2", val2, "Key-3", val3);
+
+    ASSERT_THAT(val1, Eq("string"));
+    ASSERT_THAT(val2, FloatEq(42.f));
+    ASSERT_THAT(val3, DoubleEq(15.));
+}
+
+} // namespace sdbusplus
diff --git a/test/utility/type_traits.cpp b/test/utility/type_traits.cpp
index 58ade36..33bf8c1 100644
--- a/test/utility/type_traits.cpp
+++ b/test/utility/type_traits.cpp
@@ -2,7 +2,7 @@
 
 #include <type_traits>
 
-#include <gtest/gtest.h>
+#include <gmock/gmock.h>
 
 namespace
 {
@@ -26,4 +26,60 @@
                   "array_to_ptr_t<int, char[100]> != char[100]");
 }
 
+TEST(TypeTraits, HasMemberFind)
+{
+    using sdbusplus::utility::has_member_find_v;
+    using namespace testing;
+
+    ASSERT_THAT((has_member_find_v<std::map<std::string, int>>), Eq(true));
+    ASSERT_THAT((has_member_find_v<std::vector<std::pair<std::string, int>>>),
+                Eq(false));
+
+    struct Foo
+    {
+        using value_type = std::pair<int, int>;
+
+        void find(std::tuple_element_t<0, value_type>)
+        {}
+    };
+
+    struct Bar
+    {};
+
+    ASSERT_THAT(has_member_find_v<Foo>, Eq(true));
+    ASSERT_THAT(has_member_find_v<Foo&>, Eq(true));
+    ASSERT_THAT(has_member_find_v<const Foo&>, Eq(true));
+
+    ASSERT_THAT(has_member_find_v<Bar>, Eq(false));
+}
+
+TEST(TypeTraits, HasMemberContains)
+{
+    using sdbusplus::utility::has_member_contains_v;
+    using namespace testing;
+
+    // std::map has member_contains from c++20
+    ASSERT_THAT((has_member_contains_v<std::map<std::string, int>>), Eq(false));
+    ASSERT_THAT(
+        (has_member_contains_v<std::vector<std::pair<std::string, int>>>),
+        Eq(false));
+
+    struct Foo
+    {
+        using value_type = std::pair<int, int>;
+
+        void contains(std::tuple_element_t<0, value_type>)
+        {}
+    };
+
+    struct Bar
+    {};
+
+    ASSERT_THAT(has_member_contains_v<Foo>, Eq(true));
+    ASSERT_THAT(has_member_contains_v<Foo&>, Eq(true));
+    ASSERT_THAT(has_member_contains_v<const Foo&>, Eq(true));
+
+    ASSERT_THAT(has_member_contains_v<Bar>, Eq(false));
+}
+
 } // namespace