buffet: Added PropValue <-> DBus Variant (Any) conversion

GCD object schema has been used predominantly with JSON values.
Now in order to make it easier to enable GCD data transfer over
D-Bus, we need to have more comprehensive utilities to marshal
GCD data over D-Bus. Specifically, GCD Object type should be sent
over D-Bus as "a{sv}".

Added PropValueToDBusVariant() and PropValueFromDBusVariant() to
convert between D-Bus types and GCD object schema types.

BUG=chromium:415364
TEST=FEATURES=test emerge-link buffet

Change-Id: Ib9feae3a12b499e6a196cb22d2f9736bb824afbe
Reviewed-on: https://chromium-review.googlesource.com/219136
Tested-by: Alex Vakulenko <avakulenko@chromium.org>
Reviewed-by: Christopher Wiley <wiley@chromium.org>
Commit-Queue: Alex Vakulenko <avakulenko@chromium.org>
diff --git a/buffet/commands/schema_utils.cc b/buffet/commands/schema_utils.cc
index aaa5050..4106511 100644
--- a/buffet/commands/schema_utils.cc
+++ b/buffet/commands/schema_utils.cc
@@ -127,8 +127,13 @@
       const base::Value* param_value = nullptr;
       CHECK(dict->GetWithoutPathExpansion(pair.first, &param_value))
           << "Unable to get parameter";
-      if (!value->FromJson(param_value, error))
+      if (!value->FromJson(param_value, error)) {
+        chromeos::Error::AddToPrintf(error, errors::commands::kDomain,
+                                     errors::commands::kInvalidPropValue,
+                                     "Invalid value for property '%s'",
+                                     pair.first.c_str());
         return false;
+      }
       value_out->insert(std::make_pair(pair.first, std::move(value)));
     } else if (def_value) {
       std::shared_ptr<PropValue> value = def_value->Clone();
@@ -162,7 +167,7 @@
     if (!prop_type->ValidateConstraints(*pair.second, error)) {
       chromeos::Error::AddToPrintf(error, errors::commands::kDomain,
                                    errors::commands::kInvalidPropValue,
-                                   "Invalid parameter value for property '%s'",
+                                   "Invalid value for property '%s'",
                                    pair.first.c_str());
       return false;
     }
@@ -192,5 +197,92 @@
   return str;
 }
 
+chromeos::Any PropValueToDBusVariant(const PropValue* value) {
+  if (value->GetType() != ValueType::Object)
+    return value->GetValueAsAny();
+  // Special case for object types.
+  // Convert native_types::Object to chromeos::dbus_utils::Dictionary
+  chromeos::dbus_utils::Dictionary dict;
+  for (const auto& pair : value->GetObject()->GetValue()) {
+    // Since we are inserting the elements from native_types::Object which is
+    // a map, the keys are already sorted. So use the "end()" position as a hint
+    // for dict.insert() so the destination map can optimize its insertion
+    // time.
+    chromeos::Any prop = PropValueToDBusVariant(pair.second.get());
+    dict.emplace_hint(dict.end(), pair.first, std::move(prop));
+  }
+  return chromeos::Any(std::move(dict));
+}
+
+std::shared_ptr<const PropValue> PropValueFromDBusVariant(
+    const PropType* type,
+    const chromeos::Any& value,
+    chromeos::ErrorPtr* error) {
+  std::shared_ptr<const PropValue> result;
+  if (type->GetType() != ValueType::Object) {
+    result = type->CreateValue(value, error);
+    if (result && !type->ValidateConstraints(*result, error))
+      result.reset();
+    return result;
+  }
+
+  // Special case for object types.
+  // We expect the |value| to contain chromeos::dbus_utils::Dictionary, while
+  // PropValue must use native_types::Object instead. Do the conversion.
+  if (!value.IsTypeCompatible<chromeos::dbus_utils::Dictionary>()) {
+    type->GenerateErrorValueTypeMismatch(error);
+    return result;
+  }
+  const auto& dict = value.Get<chromeos::dbus_utils::Dictionary>();
+  native_types::Object obj;
+  CHECK(nullptr != type->GetObjectSchemaPtr())
+      << "An object type must have a schema defined for it";
+  std::set<std::string> keys_processed;
+  // First go over all object parameters defined by type's object schema and
+  // extract the corresponding parameters from the source dictionary.
+  for (const auto& pair : type->GetObjectSchemaPtr()->GetProps()) {
+    const PropValue* def_value = pair.second->GetDefaultValue();
+    auto it = dict.find(pair.first);
+    if (it != dict.end()) {
+      const PropType* prop_type = pair.second.get();
+      CHECK(prop_type) << "Value property type must be available";
+      auto prop_value = PropValueFromDBusVariant(prop_type, it->second, error);
+      if (!prop_value) {
+        chromeos::Error::AddToPrintf(error, errors::commands::kDomain,
+                                     errors::commands::kInvalidPropValue,
+                                     "Invalid value for property '%s'",
+                                     pair.first.c_str());
+        return result;
+      }
+      obj.emplace_hint(obj.end(), pair.first, std::move(prop_value));
+    } else if (def_value) {
+      std::shared_ptr<const PropValue> prop_value = def_value->Clone();
+      obj.emplace_hint(obj.end(), pair.first, std::move(prop_value));
+    } else {
+      ErrorMissingProperty(error, pair.first.c_str());
+      return result;
+    }
+    keys_processed.insert(pair.first);
+  }
+
+  // Make sure that we processed all the necessary properties and there weren't
+  // any extra (unknown) ones specified, unless the schema allows them.
+  if (!type->GetObjectSchemaPtr()->GetExtraPropertiesAllowed()) {
+    for (const auto& pair : dict) {
+      if (keys_processed.find(pair.first) == keys_processed.end()) {
+        chromeos::Error::AddToPrintf(error, errors::commands::kDomain,
+                                     errors::commands::kUnknownProperty,
+                                     "Unrecognized property '%s'",
+                                     pair.first.c_str());
+        return result;
+      }
+    }
+  }
+
+  result = type->CreateValue(std::move(obj), error);
+  if (result && !type->ValidateConstraints(*result, error))
+    result.reset();
+  return result;
+}
 
 }  // namespace buffet