blob: 472391807f455630a1a50d8b1760689a62bbd894 [file] [log] [blame]
// Copyright 2015 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "libweave/src/commands/dbus_conversion.h"
#include <set>
#include <string>
#include <vector>
#include "libweave/src/commands/object_schema.h"
#include "libweave/src/commands/prop_types.h"
#include "libweave/src/commands/prop_values.h"
namespace weave {
namespace {
// Helpers for JsonToAny().
template <typename T>
chromeos::Any ValueToAny(const base::Value& json,
bool (base::Value::*fnc)(T*) const) {
T val;
CHECK((json.*fnc)(&val));
return val;
}
chromeos::Any ValueToAny(const base::Value& json);
template <typename T>
chromeos::Any ListToAny(const base::ListValue& list,
bool (base::Value::*fnc)(T*) const) {
std::vector<T> result;
result.reserve(list.GetSize());
for (const base::Value* v : list) {
T val;
CHECK((v->*fnc)(&val));
result.push_back(val);
}
return result;
}
chromeos::Any DictListToAny(const base::ListValue& list) {
std::vector<chromeos::VariantDictionary> result;
result.reserve(list.GetSize());
for (const base::Value* v : list) {
const base::DictionaryValue* dict = nullptr;
CHECK(v->GetAsDictionary(&dict));
result.push_back(DictionaryToDBusVariantDictionary(*dict));
}
return result;
}
chromeos::Any ListListToAny(const base::ListValue& list) {
std::vector<chromeos::Any> result;
result.reserve(list.GetSize());
for (const base::Value* v : list)
result.push_back(ValueToAny(*v));
return result;
}
// Converts a JSON value into an Any so it can be sent over D-Bus using
// UpdateState D-Bus method from Buffet.
chromeos::Any ValueToAny(const base::Value& json) {
chromeos::Any prop_value;
switch (json.GetType()) {
case base::Value::TYPE_BOOLEAN:
prop_value = ValueToAny<bool>(json, &base::Value::GetAsBoolean);
break;
case base::Value::TYPE_INTEGER:
prop_value = ValueToAny<int>(json, &base::Value::GetAsInteger);
break;
case base::Value::TYPE_DOUBLE:
prop_value = ValueToAny<double>(json, &base::Value::GetAsDouble);
break;
case base::Value::TYPE_STRING:
prop_value = ValueToAny<std::string>(json, &base::Value::GetAsString);
break;
case base::Value::TYPE_DICTIONARY: {
const base::DictionaryValue* dict = nullptr;
CHECK(json.GetAsDictionary(&dict));
prop_value = DictionaryToDBusVariantDictionary(*dict);
break;
}
case base::Value::TYPE_LIST: {
const base::ListValue* list = nullptr;
CHECK(json.GetAsList(&list));
if (list->empty()) {
// We don't know type of objects this list intended for, so we just use
// vector<chromeos::Any>.
prop_value = ListListToAny(*list);
break;
}
auto type = (*list->begin())->GetType();
for (const base::Value* v : *list)
CHECK_EQ(v->GetType(), type);
switch (type) {
case base::Value::TYPE_BOOLEAN:
prop_value = ListToAny<bool>(*list, &base::Value::GetAsBoolean);
break;
case base::Value::TYPE_INTEGER:
prop_value = ListToAny<int>(*list, &base::Value::GetAsInteger);
break;
case base::Value::TYPE_DOUBLE:
prop_value = ListToAny<double>(*list, &base::Value::GetAsDouble);
break;
case base::Value::TYPE_STRING:
prop_value = ListToAny<std::string>(*list, &base::Value::GetAsString);
break;
case base::Value::TYPE_DICTIONARY:
prop_value = DictListToAny(*list);
break;
case base::Value::TYPE_LIST:
// We can't support Any{vector<vector<>>} as the type is only known
// in runtime when we need to instantiate templates in compile time.
// We can use Any{vector<Any>} instead.
prop_value = ListListToAny(*list);
break;
default:
LOG(FATAL) << "Unsupported JSON value type for list element: "
<< (*list->begin())->GetType();
}
break;
}
default:
LOG(FATAL) << "Unexpected JSON value type: " << json.GetType();
break;
}
return prop_value;
}
bool ErrorMissingProperty(chromeos::ErrorPtr* error,
const tracked_objects::Location& location,
const char* param_name) {
chromeos::Error::AddToPrintf(error, location, errors::commands::kDomain,
errors::commands::kPropertyMissing,
"Required parameter missing: %s", param_name);
return false;
}
} // namespace
chromeos::Any PropValueToDBusVariant(const PropValue* value) {
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 ObjectToDBusVariant(const ValueMap& object) {
chromeos::VariantDictionary dict;
for (const auto& pair : object) {
// Since we are inserting the elements from ValueMap 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 dict;
}
std::unique_ptr<const PropValue> PropValueFromDBusVariant(
const PropType* type,
const chromeos::Any& value,
chromeos::ErrorPtr* error) {
std::unique_ptr<const PropValue> result;
if (type->GetType() == ValueType::Array) {
// Special case for array types.
// We expect the |value| to contain std::vector<T>, while PropValue must use
// ValueVector instead. Do the conversion.
ValueVector 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 ValueMap instead. Do the conversion.
if (!value.IsTypeCompatible<chromeos::VariantDictionary>()) {
type->GenerateErrorValueTypeMismatch(error);
return result;
}
CHECK(nullptr != type->GetObject()->GetObjectSchemaPtr())
<< "An object type must have a schema defined for it";
ValueMap obj;
if (!ObjectFromDBusVariant(type->GetObject()->GetObjectSchemaPtr(),
value.Get<chromeos::VariantDictionary>(), &obj,
error)) {
return result;
}
result = type->CreateValue(std::move(obj), error);
} else {
result = type->CreateValue(value, error);
}
return result;
}
bool ObjectFromDBusVariant(const ObjectSchema* object_schema,
const chromeos::VariantDictionary& dict,
ValueMap* obj,
chromeos::ErrorPtr* error) {
std::set<std::string> keys_processed;
obj->clear();
// 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 : object_schema->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, FROM_HERE, errors::commands::kDomain,
errors::commands::kInvalidPropValue,
"Invalid value for property '%s'", pair.first.c_str());
return false;
}
obj->emplace_hint(obj->end(), pair.first, std::move(prop_value));
} else if (def_value) {
obj->emplace_hint(obj->end(), pair.first, def_value->Clone());
} else {
ErrorMissingProperty(error, FROM_HERE, pair.first.c_str());
return false;
}
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 (!object_schema->GetExtraPropertiesAllowed()) {
for (const auto& pair : dict) {
if (keys_processed.find(pair.first) == keys_processed.end()) {
chromeos::Error::AddToPrintf(
error, FROM_HERE, errors::commands::kDomain,
errors::commands::kUnknownProperty, "Unrecognized property '%s'",
pair.first.c_str());
return false;
}
}
}
return true;
}
// TODO(vitalybuka): Use in buffet_client.
chromeos::VariantDictionary DictionaryToDBusVariantDictionary(
const base::DictionaryValue& object) {
chromeos::VariantDictionary result;
for (base::DictionaryValue::Iterator it(object); !it.IsAtEnd(); it.Advance())
result.emplace(it.key(), ValueToAny(it.value()));
return result;
}
} // namespace weave