libweave: Add DictionaryFromDBusVariantDictionary

Added unittests for DictionaryFromDBusVariantDictionary and test with
reverse conversion.

BUG=brillo:1245
TEST='FEATURES=test emerge-gizmo buffet'

Change-Id: I061f753d158f53398aa7ef0ec2757e50ed6cec7a
Reviewed-on: https://chromium-review.googlesource.com/287946
Reviewed-by: Vitaly Buka <vitalybuka@chromium.org>
Commit-Queue: Vitaly Buka <vitalybuka@chromium.org>
Tested-by: Vitaly Buka <vitalybuka@chromium.org>
diff --git a/libweave/src/commands/dbus_conversion.cc b/libweave/src/commands/dbus_conversion.cc
index 4723918..cadbafa 100644
--- a/libweave/src/commands/dbus_conversion.cc
+++ b/libweave/src/commands/dbus_conversion.cc
@@ -8,6 +8,8 @@
 #include <string>
 #include <vector>
 
+#include <chromeos/type_name_undecorate.h>
+
 #include "libweave/src/commands/object_schema.h"
 #include "libweave/src/commands/prop_types.h"
 #include "libweave/src/commands/prop_values.h"
@@ -93,7 +95,7 @@
       }
       auto type = (*list->begin())->GetType();
       for (const base::Value* v : *list)
-        CHECK_EQ(v->GetType(), type);
+        CHECK_EQ(v->GetType(), type) << "Unsupported different type elements";
 
       switch (type) {
         case base::Value::TYPE_BOOLEAN:
@@ -130,6 +132,94 @@
   return prop_value;
 }
 
+template <typename T>
+std::unique_ptr<base::Value> CreateValue(const T& value,
+                                         chromeos::ErrorPtr* error) {
+  return std::unique_ptr<base::Value>{new base::FundamentalValue{value}};
+}
+
+template <>
+std::unique_ptr<base::Value> CreateValue<std::string>(
+    const std::string& value,
+    chromeos::ErrorPtr* error) {
+  return std::unique_ptr<base::Value>{new base::StringValue{value}};
+}
+
+template <>
+std::unique_ptr<base::Value> CreateValue<chromeos::VariantDictionary>(
+    const chromeos::VariantDictionary& value,
+    chromeos::ErrorPtr* error) {
+  return DictionaryFromDBusVariantDictionary(value, error);
+}
+
+template <typename T>
+std::unique_ptr<base::ListValue> CreateListValue(const std::vector<T>& value,
+                                                 chromeos::ErrorPtr* error) {
+  std::unique_ptr<base::ListValue> list{new base::ListValue};
+
+  for (const T& i : value) {
+    auto item = CreateValue(i, error);
+    if (!item)
+      return nullptr;
+    list->Append(item.release());
+  }
+
+  return list;
+}
+
+// Returns false only in case of error. True can be returned if type is not
+// matched.
+template <typename T>
+bool TryCreateValue(const chromeos::Any& any,
+                    std::unique_ptr<base::Value>* value,
+                    chromeos::ErrorPtr* error) {
+  if (any.IsTypeCompatible<T>()) {
+    *value = CreateValue(any.Get<T>(), error);
+    return *value != nullptr;
+  }
+
+  if (any.IsTypeCompatible<std::vector<T>>()) {
+    *value = CreateListValue(any.Get<std::vector<T>>(), error);
+    return *value != nullptr;
+  }
+
+  return true;  // Not an error, we will try different type.
+}
+
+template <>
+std::unique_ptr<base::Value> CreateValue<chromeos::Any>(
+    const chromeos::Any& any,
+    chromeos::ErrorPtr* error) {
+  std::unique_ptr<base::Value> result;
+  if (!TryCreateValue<bool>(any, &result, error) || result)
+    return result;
+
+  if (!TryCreateValue<int>(any, &result, error) || result)
+    return result;
+
+  if (!TryCreateValue<double>(any, &result, error) || result)
+    return result;
+
+  if (!TryCreateValue<std::string>(any, &result, error) || result)
+    return result;
+
+  if (!TryCreateValue<chromeos::VariantDictionary>(any, &result, error) ||
+      result) {
+    return result;
+  }
+
+  // This will collapse Any{Any{T}} and vector{Any{T}}.
+  if (!TryCreateValue<chromeos::Any>(any, &result, error) || result)
+    return result;
+
+  chromeos::Error::AddToPrintf(
+      error, FROM_HERE, errors::commands::kDomain,
+      errors::commands::kUnknownType, "Type '%s' is not supported.",
+      chromeos::UndecorateTypeName(any.GetType().name()).c_str());
+
+  return nullptr;
+}
+
 bool ErrorMissingProperty(chromeos::ErrorPtr* error,
                           const tracked_objects::Location& location,
                           const char* param_name) {
@@ -264,4 +354,19 @@
   return result;
 }
 
+std::unique_ptr<base::DictionaryValue> DictionaryFromDBusVariantDictionary(
+    const chromeos::VariantDictionary& object,
+    chromeos::ErrorPtr* error) {
+  std::unique_ptr<base::DictionaryValue> result{new base::DictionaryValue};
+
+  for (const auto& pair : object) {
+    auto value = CreateValue(pair.second, error);
+    if (!value)
+      return nullptr;
+    result->Set(pair.first, value.release());
+  }
+
+  return result;
+}
+
 }  // namespace weave
diff --git a/libweave/src/commands/dbus_conversion.h b/libweave/src/commands/dbus_conversion.h
index ab49f70..5da2fef 100644
--- a/libweave/src/commands/dbus_conversion.h
+++ b/libweave/src/commands/dbus_conversion.h
@@ -41,6 +41,11 @@
 chromeos::VariantDictionary DictionaryToDBusVariantDictionary(
     const base::DictionaryValue& object);
 
+// Converts D-Bus variant dictionary to DictionaryValue.
+std::unique_ptr<base::DictionaryValue> DictionaryFromDBusVariantDictionary(
+    const chromeos::VariantDictionary& object,
+    chromeos::ErrorPtr* error);
+
 }  // namespace weave
 
 #endif  // LIBWEAVE_SRC_COMMANDS_DBUS_CONVERSION_H_
diff --git a/libweave/src/commands/dbus_conversion_unittest.cc b/libweave/src/commands/dbus_conversion_unittest.cc
index c25245e..06e1718 100644
--- a/libweave/src/commands/dbus_conversion_unittest.cc
+++ b/libweave/src/commands/dbus_conversion_unittest.cc
@@ -4,10 +4,13 @@
 
 #include "libweave/src/commands/dbus_conversion.h"
 
+#include <limits>
 #include <memory>
 #include <string>
 #include <vector>
 
+#include <base/guid.h>
+#include <base/rand_util.h>
 #include <base/values.h>
 #include <chromeos/variant_dictionary.h>
 #include <gtest/gtest.h>
@@ -17,6 +20,8 @@
 
 namespace weave {
 
+namespace {
+
 using chromeos::Any;
 using chromeos::VariantDictionary;
 using unittests::CreateDictionaryValue;
@@ -25,6 +30,98 @@
   return DictionaryToDBusVariantDictionary(object);
 }
 
+std::unique_ptr<base::DictionaryValue> FromDBus(
+    const chromeos::VariantDictionary& object) {
+  chromeos::ErrorPtr error;
+  auto result = DictionaryFromDBusVariantDictionary(object, &error);
+  EXPECT_TRUE(result || error);
+  return result;
+}
+
+std::unique_ptr<base::Value> CreateRandomValue(int children);
+std::unique_ptr<base::Value> CreateRandomValue(int children,
+                                               base::Value::Type type);
+
+const base::Value::Type kRandomTypes[] = {
+    base::Value::TYPE_BOOLEAN,    base::Value::TYPE_INTEGER,
+    base::Value::TYPE_DOUBLE,     base::Value::TYPE_STRING,
+    base::Value::TYPE_DICTIONARY, base::Value::TYPE_LIST,
+};
+
+const base::Value::Type kRandomTypesWithChildren[] = {
+    base::Value::TYPE_DICTIONARY, base::Value::TYPE_LIST,
+};
+
+base::Value::Type CreateRandomValueType(bool with_children) {
+  if (with_children) {
+    return kRandomTypesWithChildren[base::RandInt(
+        0, arraysize(kRandomTypesWithChildren) - 1)];
+  }
+  return kRandomTypes[base::RandInt(0, arraysize(kRandomTypes) - 1)];
+}
+
+std::unique_ptr<base::DictionaryValue> CreateRandomDictionary(int children) {
+  std::unique_ptr<base::DictionaryValue> result{new base::DictionaryValue};
+
+  while (children > 0) {
+    int sub_children = base::RandInt(1, children);
+    children -= sub_children;
+    result->Set(base::GenerateGUID(),
+                CreateRandomValue(sub_children).release());
+  }
+
+  return result;
+}
+
+std::unique_ptr<base::ListValue> CreateRandomList(int children) {
+  std::unique_ptr<base::ListValue> result{new base::ListValue};
+
+  base::Value::Type type = CreateRandomValueType(children > 0);
+  while (children > 0) {
+    size_t max_children =
+        (type != base::Value::TYPE_DICTIONARY && type != base::Value::TYPE_LIST)
+            ? 1
+            : children;
+    size_t sub_children = base::RandInt(1, max_children);
+    children -= sub_children;
+    result->Append(CreateRandomValue(sub_children, type).release());
+  }
+
+  return result;
+}
+
+std::unique_ptr<base::Value> CreateRandomValue(int children,
+                                               base::Value::Type type) {
+  CHECK_GE(children, 1);
+  switch (type) {
+    case base::Value::TYPE_INTEGER:
+      return std::unique_ptr<base::Value>{new base::FundamentalValue{
+          base::RandInt(std::numeric_limits<int>::min(),
+                        std::numeric_limits<int>::max())}};
+    case base::Value::TYPE_DOUBLE:
+      return std::unique_ptr<base::Value>{
+          new base::FundamentalValue{base::RandDouble()}};
+    case base::Value::TYPE_STRING:
+      return std::unique_ptr<base::Value>{
+          new base::StringValue{base::GenerateGUID()}};
+    case base::Value::TYPE_DICTIONARY:
+      CHECK_GE(children, 1);
+      return CreateRandomDictionary(children - 1);
+    case base::Value::TYPE_LIST:
+      CHECK_GE(children, 1);
+      return CreateRandomList(children - 1);
+    default:
+      return std::unique_ptr<base::Value>{
+          new base::FundamentalValue{base::RandInt(0, 1) != 0}};
+  }
+}
+
+std::unique_ptr<base::Value> CreateRandomValue(int children) {
+  return CreateRandomValue(children, CreateRandomValueType(children > 0));
+}
+
+}  // namespace
+
 TEST(DBusConversionTest, PropValueToDBusVariant) {
   IntPropType int_type;
   auto prop_value = int_type.CreateValue(5, nullptr);
@@ -211,4 +308,42 @@
             ToDBus(*CreateDictionaryValue("{'objList': [{'string': 'abc'}]}")));
 }
 
+TEST(DBusConversionTest, DictionaryFromDBusVariantDictionary) {
+  EXPECT_JSON_EQ("{'bool': true}", *FromDBus({{"bool", true}}));
+  EXPECT_JSON_EQ("{'int': 5}", *FromDBus({{"int", 5}}));
+  EXPECT_JSON_EQ("{'double': 6.7}", *FromDBus({{"double", 6.7}}));
+  EXPECT_JSON_EQ("{'string': 'abc'}",
+                 *FromDBus({{"string", std::string{"abc"}}}));
+  EXPECT_JSON_EQ("{'object': {'bool': true}}",
+                 *FromDBus({{"object", VariantDictionary{{"bool", true}}}}));
+  EXPECT_JSON_EQ("{'emptyList': []}",
+                 *FromDBus({{"emptyList", std::vector<bool>{}}}));
+  EXPECT_JSON_EQ("{'intList': [5]}",
+                 *FromDBus({{"intList", std::vector<int>{5}}}));
+  EXPECT_JSON_EQ(
+      "{'intListList': [[5], [6, 7]]}",
+      *FromDBus({{"intListList", std::vector<Any>{std::vector<int>{5},
+                                                  std::vector<int>{6, 7}}}}));
+  EXPECT_JSON_EQ(
+      "{'objList': [{'string': 'abc'}]}",
+      *FromDBus({{"objList", std::vector<VariantDictionary>{
+                                 {{"string", std::string{"abc"}}}}}}));
+  EXPECT_JSON_EQ("{'int': 5}", *FromDBus({{"int", Any{Any{5}}}}));
+}
+
+TEST(DBusConversionTest, DictionaryFromDBusVariantDictionary_Errors) {
+  EXPECT_FALSE(FromDBus({{"cString", "abc"}}));
+  EXPECT_FALSE(FromDBus({{"float", 1.0f}}));
+  EXPECT_FALSE(FromDBus({{"listList", std::vector<std::vector<int>>{}}}));
+  EXPECT_FALSE(FromDBus({{"any", Any{}}}));
+  EXPECT_FALSE(FromDBus({{"null", nullptr}}));
+}
+
+TEST(DBusConversionTest, DBusRandomDictionaryConversion) {
+  auto dict = CreateRandomDictionary(10000);
+  auto varian_dict = ToDBus(*dict);
+  auto dict_restored = FromDBus(varian_dict);
+  EXPECT_PRED2(unittests::IsEqualValue, *dict, *dict_restored);
+}
+
 }  // namespace weave