buffet: Add D-Bus serialization support for buffet Array types

Added methods for converting native_types::Array to chromeos::Any,
containing std::vector<T> and back. These are used to marshal
ArrayPropValue over D-Bus when sending commands to vendor daemons
for processing.

BUG=brillo:107
TEST=`FEATURES=test emerge-link buffet`

Change-Id: I44197ef9cf2379c298b081d7ce6522e6d22facfa
Reviewed-on: https://chromium-review.googlesource.com/262206
Tested-by: Alex Vakulenko <avakulenko@chromium.org>
Reviewed-by: Vitaly Buka <vitalybuka@chromium.org>
Commit-Queue: Alex Vakulenko <avakulenko@chromium.org>
diff --git a/buffet/commands/prop_types.cc b/buffet/commands/prop_types.cc
index 74e2be6..6365dcc 100644
--- a/buffet/commands/prop_types.cc
+++ b/buffet/commands/prop_types.cc
@@ -520,6 +520,36 @@
   return true;
 }
 
+chromeos::Any ObjectPropType::ConvertArrayToDBusVariant(
+    const native_types::Array& source) const {
+  std::vector<chromeos::VariantDictionary> result;
+  result.reserve(source.size());
+  for (const auto& prop_value : source) {
+    chromeos::Any dict = PropValueToDBusVariant(prop_value.get());
+    result.push_back(std::move(*dict.GetPtr<chromeos::VariantDictionary>()));
+  }
+  return result;
+}
+
+bool ObjectPropType::ConvertDBusVariantToArray(
+    const chromeos::Any& source,
+    native_types::Array* result,
+    chromeos::ErrorPtr* error) const {
+  if (!source.IsTypeCompatible<std::vector<chromeos::VariantDictionary>>())
+    return GenerateErrorValueTypeMismatch(error);
+
+  const auto& source_array =
+      source.Get<std::vector<chromeos::VariantDictionary>>();
+  result->reserve(source_array.size());
+  for (const auto& value : source_array) {
+    auto prop_value = PropValueFromDBusVariant(this, value, error);
+    if (!prop_value)
+      return false;
+    result->push_back(std::move(prop_value));
+  }
+  return true;
+}
+
 void ObjectPropType::SetObjectSchema(
     std::unique_ptr<const ObjectSchema> schema) {
   object_schema_.value = std::move(schema);
diff --git a/buffet/commands/prop_types.h b/buffet/commands/prop_types.h
index 473a6c8..4841db6 100644
--- a/buffet/commands/prop_types.h
+++ b/buffet/commands/prop_types.h
@@ -89,6 +89,21 @@
   virtual std::unique_ptr<PropValue> CreateValue(
       const chromeos::Any& val, chromeos::ErrorPtr* error) const = 0;
 
+  // Converts an array of PropValue containing the values of the types described
+  // by this instance of PropType into an Any containing std::vector<T>, where
+  // T corresponds to the native representation of this PropType.
+  virtual chromeos::Any ConvertArrayToDBusVariant(
+      const native_types::Array& source) const = 0;
+
+  // ConvertAnyToArray is the opposite of ConvertArrayToAny().
+  // Given an Any containing std::vector<T>, converts each value into the
+  // corresponding PropValue of type of this PropType and adds them to
+  // |result| array. If type conversion fails, this function returns false
+  // and specifies the error details in |error|.
+  virtual bool ConvertDBusVariantToArray(const chromeos::Any& source,
+                                         native_types::Array* result,
+                                         chromeos::ErrorPtr* error) const = 0;
+
   // Saves the parameter type definition as a JSON object.
   // If |full_schema| is set to true, the full type definition is saved,
   // otherwise only the overridden properties and attributes from the base
@@ -184,12 +199,15 @@
 template<class Derived, class Value, typename T>
 class PropTypeBase : public PropType {
  public:
+  // Overrides from PropType.
   ValueType GetType() const override { return GetValueType<T>(); }
+
   std::unique_ptr<PropValue> CreateValue() const override {
     if (GetDefaultValue())
       return GetDefaultValue()->Clone();
     return std::unique_ptr<PropValue>{new Value{Clone()}};
   }
+
   std::unique_ptr<PropValue> CreateValue(
       const chromeos::Any& v, chromeos::ErrorPtr* error) const override {
     std::unique_ptr<PropValue> prop_value;
@@ -203,6 +221,34 @@
     }
     return prop_value;
   }
+
+  chromeos::Any ConvertArrayToDBusVariant(
+      const native_types::Array& source) const override {
+    std::vector<T> result;
+    result.reserve(source.size());
+    for (const auto& prop_value : source) {
+      result.push_back(PropValueToDBusVariant(prop_value.get()).Get<T>());
+    }
+    return result;
+  }
+
+  bool ConvertDBusVariantToArray(const chromeos::Any& source,
+                                 native_types::Array* result,
+                                 chromeos::ErrorPtr* error) const override {
+    if (!source.IsTypeCompatible<std::vector<T>>())
+      return GenerateErrorValueTypeMismatch(error);
+
+    const auto& source_array = source.Get<std::vector<T>>();
+    result->reserve(source_array.size());
+    for (const auto& value : source_array) {
+      auto prop_value = PropValueFromDBusVariant(this, value, error);
+      if (!prop_value)
+        return false;
+      result->push_back(std::move(prop_value));
+    }
+    return true;
+  }
+
   bool ConstraintsFromJson(const base::DictionaryValue* value,
                            std::set<std::string>* processed_keys,
                            chromeos::ErrorPtr* error) override;
@@ -307,6 +353,13 @@
                             std::set<std::string>* processed_keys,
                             chromeos::ErrorPtr* error) override;
 
+  chromeos::Any ConvertArrayToDBusVariant(
+    const native_types::Array& source) const override;
+
+  bool ConvertDBusVariantToArray(const chromeos::Any& source,
+                                 native_types::Array* result,
+                                 chromeos::ErrorPtr* error) const override;
+
   // Returns a schema for Object-type parameter.
   inline const ObjectSchema* GetObjectSchemaPtr() const {
     return object_schema_.value.get();
diff --git a/buffet/commands/schema_utils.cc b/buffet/commands/schema_utils.cc
index 06e5ded..47b757f 100644
--- a/buffet/commands/schema_utils.cc
+++ b/buffet/commands/schema_utils.cc
@@ -267,9 +267,15 @@
 }
 
 chromeos::Any PropValueToDBusVariant(const PropValue* value) {
-  if (value->GetType() != ValueType::Object)
-    return value->GetValueAsAny();
-  return ObjectToDBusVariant(value->GetObject()->GetValue());
+  if (value->GetType() == ValueType::Object)
+    return ObjectToDBusVariant(value->GetObject()->GetValue());
+
+  if (value->GetType() == ValueType::Array) {
+    const PropType* item_type =
+        value->GetPropType()->GetArray()->GetItemTypePtr();
+    return item_type->ConvertArrayToDBusVariant(value->GetArray()->GetValue());
+  }
+  return value->GetValueAsAny();
 }
 
 chromeos::VariantDictionary
@@ -291,13 +297,21 @@
     const chromeos::Any& value,
     chromeos::ErrorPtr* error) {
   std::unique_ptr<const PropValue> result;
-  if (type->GetType() == ValueType::Object) {
+  if (type->GetType() == ValueType::Array) {
+    // Special case for array types.
+    // We expect the |value| to contain std::vector<T>, while PropValue must use
+    // native_types::Array instead. Do the conversion.
+    native_types::Array arr;
+    const PropType* item_type = type->GetArray()->GetItemTypePtr();
+    if (item_type->ConvertDBusVariantToArray(value, &arr, error))
+      result = type->CreateValue(arr, error);
+  } else if (type->GetType() == ValueType::Object) {
     // Special case for object types.
     // We expect the |value| to contain chromeos::VariantDictionary, while
     // PropValue must use native_types::Object instead. Do the conversion.
     if (!value.IsTypeCompatible<chromeos::VariantDictionary>()) {
       type->GenerateErrorValueTypeMismatch(error);
-      return {};
+      return result;
     }
     CHECK(nullptr != type->GetObject()->GetObjectSchemaPtr())
         << "An object type must have a schema defined for it";
@@ -305,8 +319,9 @@
     if (!ObjectFromDBusVariant(type->GetObject()->GetObjectSchemaPtr(),
                                value.Get<chromeos::VariantDictionary>(),
                                &obj,
-                               error))
-      return {};
+                               error)) {
+      return result;
+    }
 
     result = type->CreateValue(std::move(obj), error);
   } else {
diff --git a/buffet/commands/schema_utils_unittest.cc b/buffet/commands/schema_utils_unittest.cc
index 25fa553..8405722 100644
--- a/buffet/commands/schema_utils_unittest.cc
+++ b/buffet/commands/schema_utils_unittest.cc
@@ -249,6 +249,18 @@
       PropValueToDBusVariant(prop_value.get()).Get<VariantDictionary>();
   EXPECT_EQ(20, dict["height"].Get<int>());
   EXPECT_EQ(10, dict["width"].Get<int>());
+
+  buffet::ArrayPropType arr_type;
+  arr_type.SetItemType(str_type.Clone());
+  buffet::native_types::Array arr;
+  arr.push_back(str_type.CreateValue(std::string{"foo"}, nullptr));
+  arr.push_back(str_type.CreateValue(std::string{"bar"}, nullptr));
+  arr.push_back(str_type.CreateValue(std::string{"baz"}, nullptr));
+  prop_value = arr_type.CreateValue(arr, nullptr);
+  chromeos::Any any = PropValueToDBusVariant(prop_value.get());
+  ASSERT_TRUE(any.IsTypeCompatible<std::vector<std::string>>());
+  EXPECT_EQ((std::vector<std::string>{"foo", "bar", "baz"}),
+            any.Get<std::vector<std::string>>());
 }
 
 TEST(CommandSchemaUtils, PropValueFromDBusVariant_Int) {
@@ -340,3 +352,26 @@
   ASSERT_NE(nullptr, error.get());
   EXPECT_EQ(buffet::errors::commands::kOutOfRange, error->GetCode());
 }
+
+TEST(CommandSchemaUtils, PropValueFromDBusVariant_Array) {
+  buffet::ArrayPropType arr_type;
+  buffet::IntPropType int_type;
+  int_type.AddMinMaxConstraint(0, 100);
+  arr_type.SetItemType(int_type.Clone());
+  std::vector<int> data{0, 1, 1, 100};
+  auto prop_value = PropValueFromDBusVariant(&arr_type, data, nullptr);
+  ASSERT_NE(nullptr, prop_value.get());
+  auto arr = prop_value->GetValueAsAny().Get<buffet::native_types::Array>();
+  ASSERT_EQ(4u, arr.size());
+  EXPECT_EQ(0, arr[0]->GetInt()->GetValue());
+  EXPECT_EQ(1, arr[1]->GetInt()->GetValue());
+  EXPECT_EQ(1, arr[2]->GetInt()->GetValue());
+  EXPECT_EQ(100, arr[3]->GetInt()->GetValue());
+
+  chromeos::ErrorPtr error;
+  data.push_back(-1);  // This value is out of bounds for |int_type|.
+  prop_value = PropValueFromDBusVariant(&arr_type, data, &error);
+  EXPECT_EQ(nullptr, prop_value.get());
+  ASSERT_NE(nullptr, error.get());
+  EXPECT_EQ(buffet::errors::commands::kOutOfRange, error->GetCode());
+}