variant support for append/read

Change-Id: I682275405e3c4f7c798317a60ea070b3b1a0846c
Signed-off-by: Patrick Williams <patrick@stwcx.xyz>
diff --git a/sdbusplus/message/append.hpp b/sdbusplus/message/append.hpp
index 2db790b..df0b08f 100644
--- a/sdbusplus/message/append.hpp
+++ b/sdbusplus/message/append.hpp
@@ -59,6 +59,9 @@
     // std::tuple needs to be broken down into components.
 template<typename ...Args>
 struct can_append_multiple<std::tuple<Args...>> : std::false_type {};
+    // variant needs to be broken down into components.
+template<typename ...Args>
+struct can_append_multiple<variant<Args...>> : std::false_type {};
 
 /** @struct append_single
  *  @brief Utility to append a single C++ element into a sd_bus_message.
@@ -188,6 +191,30 @@
     }
 };
 
+/** @brief Specialization of append_single for std::variant. */
+template <typename ...Args> struct append_single<variant<Args...>>
+{
+    template<typename S,
+             typename = std::enable_if_t<0 < sizeof...(Args)>>
+    static void op(sd_bus_message* m, S&& s)
+    {
+        auto apply =
+            [m](auto&& arg)
+            {
+                constexpr auto dbusType = utility::tuple_to_array(
+                    types::type_id<decltype(arg)>());
+
+                sd_bus_message_open_container(m,
+                                              SD_BUS_TYPE_VARIANT,
+                                              dbusType.data());
+                sdbusplus::message::append(m, arg);
+                sd_bus_message_close_container(m);
+            };
+
+        std::remove_reference_t<S>::visit(s, apply);
+    }
+};
+
 /** @brief Append a tuple of content into the sd_bus_message.
  *
  *  @tparam Tuple - The tuple type to append.
diff --git a/sdbusplus/message/read.hpp b/sdbusplus/message/read.hpp
index 47e2392..275e3a4 100644
--- a/sdbusplus/message/read.hpp
+++ b/sdbusplus/message/read.hpp
@@ -59,6 +59,9 @@
     // std::tuple needs to be broken down into components.
 template<typename ...Args>
 struct can_read_multiple<std::tuple<Args...>> : std::false_type {};
+    // variant needs to be broken down into components.
+template<typename ...Args>
+struct can_read_multiple<variant<Args...>> : std::false_type {};
 
 /** @struct read_single
  *  @brief Utility to read a single C++ element from a sd_bus_message.
@@ -207,6 +210,46 @@
     }
 };
 
+/** @brief Specialization of read_single for std::variant. */
+template <typename ...Args> struct read_single<variant<Args...>>
+{
+    template<typename S, typename S1, typename ...Args1>
+    static void read(sd_bus_message* m, S&& s)
+    {
+        constexpr auto dbusType = utility::tuple_to_array(types::type_id<S1>());
+
+        auto rc = sd_bus_message_verify_type(m,
+                                             SD_BUS_TYPE_VARIANT,
+                                             dbusType.data());
+        if (0 >= rc)
+        {
+            read<S, Args1...>(m, s);
+            return;
+        }
+
+        std::remove_reference_t<S1> s1;
+
+        sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, dbusType.data());
+        sdbusplus::message::read(m, s1);
+        sd_bus_message_exit_container(m);
+
+        s = std::move(s1);
+    }
+
+    template<typename S>
+    static void read(sd_bus_message* m, S&& s)
+    {
+        s = std::remove_reference_t<S>{};
+    }
+
+    template<typename S,
+             typename = std::enable_if_t<0 < sizeof...(Args)>>
+    static void op(sd_bus_message* m, S&& s)
+    {
+        read<S, Args...>(m, s);
+    }
+};
+
 /** @brief Read a tuple of content from the sd_bus_message.
  *
  *  @tparam Tuple - The tuple type to read.
diff --git a/sdbusplus/message/types.hpp b/sdbusplus/message/types.hpp
index 1876fa2..fd4a294 100644
--- a/sdbusplus/message/types.hpp
+++ b/sdbusplus/message/types.hpp
@@ -4,6 +4,7 @@
 #include <string>
 #include <vector>
 #include <map>
+#include <mapbox/variant.hpp>
 #include <systemd/sd-bus.h>
 
 #include <sdbusplus/utility/type_traits.hpp>
@@ -14,6 +15,9 @@
 namespace message
 {
 
+template <typename ...Args>
+using variant = mapbox::util::variant<Args...>;
+
 namespace types
 {
 
@@ -169,6 +173,9 @@
         tuple_type_id<SD_BUS_TYPE_STRUCT_END>::value);
 };
 
+template <typename ...Args>
+struct type_id<variant<Args...>> : tuple_type_id<SD_BUS_TYPE_VARIANT> {};
+
 template <typename T> constexpr auto& type_id_single()
 {
     static_assert(!std::is_base_of<undefined_type_id, type_id<T>>::value,
diff --git a/test/message/append.cpp b/test/message/append.cpp
index 03ab781..4f85b74 100644
--- a/test/message/append.cpp
+++ b/test/message/append.cpp
@@ -332,6 +332,89 @@
         b.call_noreply(m);
     }
 
+    // Test variant.
+    {
+        auto m = newMethodCall__test(b);
+        sdbusplus::message::variant<int, double> a1{3.1}, a2{4};
+        m.append(1, a1, a2, 2);
+        verifyTypeString = "ivvi";
+
+        struct verify
+        {
+            static void op(sd_bus_message* m)
+            {
+                int32_t a = 0;
+                double b = 0;
+
+                sd_bus_message_read(m, "i", &a);
+                assert(a == 1);
+
+                sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, "d");
+                sd_bus_message_read(m, "d", &b);
+                assert(b == 3.1);
+                sd_bus_message_exit_container(m);
+
+                sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, "i");
+                sd_bus_message_read(m, "i", &a);
+                assert(a == 4);
+                sd_bus_message_exit_container(m);
+
+                sd_bus_message_read(m, "i", &a);
+                assert(a == 2);
+            }
+        };
+        verifyCallback = &verify::op;
+
+        b.call_noreply(m);
+    }
+
+    // Test map-variant.
+    {
+        auto m = newMethodCall__test(b);
+        std::map<std::string, sdbusplus::message::variant<int, double>> a1 =
+                { { "asdf", 3 }, { "jkl;", 4.1 } };
+        m.append(1, a1, 2);
+        verifyTypeString = "ia{sv}i";
+
+        struct verify
+        {
+            static void op(sd_bus_message* m)
+            {
+                int32_t a = 0;
+                double b = 0;
+                const char* c = nullptr;
+
+                sd_bus_message_read(m, "i", &a);
+                assert(a == 1);
+
+                sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "{sv}");
+                sd_bus_message_enter_container(m, SD_BUS_TYPE_DICT_ENTRY, "sv");
+                sd_bus_message_read(m, "s", &c);
+                assert(0 == strcmp("asdf", c));
+                sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, "i");
+                sd_bus_message_read(m, "i", &a);
+                assert(a == 3);
+                sd_bus_message_exit_container(m);
+                sd_bus_message_exit_container(m);
+                sd_bus_message_enter_container(m, SD_BUS_TYPE_DICT_ENTRY, "sv");
+                sd_bus_message_read(m, "s", &c);
+                assert(0 == strcmp("jkl;", c));
+                sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, "d");
+                sd_bus_message_read(m, "d", &b);
+                assert(b == 4.1);
+                sd_bus_message_exit_container(m);
+                sd_bus_message_exit_container(m);
+                sd_bus_message_exit_container(m);
+
+                sd_bus_message_read(m, "i", &a);
+                assert(a == 2);
+            }
+        };
+        verifyCallback = &verify::op;
+
+        b.call_noreply(m);
+    }
+
     // Shutdown server.
     {
         auto m = b.new_method_call(SERVICE, "/", INTERFACE, QUIT_METHOD);
diff --git a/test/message/read.cpp b/test/message/read.cpp
index 25ccdb5..496e6ca 100644
--- a/test/message/read.cpp
+++ b/test/message/read.cpp
@@ -272,6 +272,61 @@
         b.call_noreply(m);
     }
 
+    // Test variant.
+    {
+        auto m = newMethodCall__test(b);
+        sdbusplus::message::variant<int, double> a1{3.1}, a2{4};
+        m.append(1, a1, a2, 2);
+        verifyTypeString = "ivvi";
+
+        struct verify
+        {
+            static void op(sdbusplus::message::message& m)
+            {
+                int32_t a, b;
+                sdbusplus::message::variant<int, double> a1{}, a2{};
+
+                m.read(a, a1, a2, b);
+                assert(a == 1);
+                assert(a1 == 3.1);
+                assert(a2 == 4);
+                assert(b == 2);
+            }
+        };
+        verifyCallback = &verify::op;
+
+        b.call_noreply(m);
+    }
+
+    // Test map-variant.
+    {
+        auto m = newMethodCall__test(b);
+        std::map<std::string, sdbusplus::message::variant<int, double>> a1 =
+                { { "asdf", 3 }, { "jkl;", 4.1 } };
+        m.append(1, a1, 2);
+        verifyTypeString = "ia{sv}i";
+
+        struct verify
+        {
+            static void op(sdbusplus::message::message& m)
+            {
+                int32_t a = 0, b = 0;
+                std::map<std::string,
+                         sdbusplus::message::variant<int, double>> a1{};
+
+                m.read(a, a1, b);
+                assert(a == 1);
+                assert(a1["asdf"] == 3);
+                assert(a1["jkl;"] == 4.1);
+                assert(b == 2);
+            }
+        };
+        verifyCallback = &verify::op;
+
+        b.call_noreply(m);
+    }
+
+
     // Shutdown server.
     {
         auto m = b.new_method_call(SERVICE, "/", INTERFACE, QUIT_METHOD);