|  | // Copyright 2014 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 "buffet/commands/schema_utils.h" | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <set> | 
|  | #include <string> | 
|  |  | 
|  | #include <base/json/json_writer.h> | 
|  |  | 
|  | #include "buffet/commands/object_schema.h" | 
|  | #include "buffet/commands/prop_types.h" | 
|  | #include "buffet/commands/prop_values.h" | 
|  |  | 
|  | namespace buffet { | 
|  | namespace { | 
|  | // Helper function to report "type mismatch" errors when parsing JSON. | 
|  | void ReportJsonTypeMismatch(const base::Value* value_in, | 
|  | const std::string& expected_type, | 
|  | chromeos::ErrorPtr* error) { | 
|  | std::string value_as_string; | 
|  | base::JSONWriter::Write(value_in, &value_as_string); | 
|  | chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain, | 
|  | errors::commands::kTypeMismatch, | 
|  | "Unable to convert value %s into %s", | 
|  | value_as_string.c_str(), expected_type.c_str()); | 
|  | } | 
|  |  | 
|  | // Template version of ReportJsonTypeMismatch that deduces the type of expected | 
|  | // data from the value_out parameter passed to particular overload of | 
|  | // TypedValueFromJson() function. Always returns false. | 
|  | template<typename T> | 
|  | bool ReportUnexpectedJson(const base::Value* value_in, T*, | 
|  | chromeos::ErrorPtr* error) { | 
|  | ReportJsonTypeMismatch(value_in, | 
|  | PropType::GetTypeStringFromType(GetValueType<T>()), | 
|  | error); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool ErrorMissingProperty(chromeos::ErrorPtr* error, const char* param_name) { | 
|  | chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain, | 
|  | errors::commands::kPropertyMissing, | 
|  | "Required parameter missing: %s", param_name); | 
|  | return false; | 
|  | } | 
|  | }  // namespace | 
|  |  | 
|  | // Specializations of TypedValueToJson<T>() for supported C++ types. | 
|  | std::unique_ptr<base::Value> TypedValueToJson(bool value, | 
|  | chromeos::ErrorPtr* error) { | 
|  | return std::unique_ptr<base::Value>(new base::FundamentalValue(value)); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<base::Value> TypedValueToJson(int value, | 
|  | chromeos::ErrorPtr* error) { | 
|  | return std::unique_ptr<base::Value>(new base::FundamentalValue(value)); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<base::Value> TypedValueToJson(double value, | 
|  | chromeos::ErrorPtr* error) { | 
|  | return std::unique_ptr<base::Value>(new base::FundamentalValue(value)); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<base::Value> TypedValueToJson(const std::string& value, | 
|  | chromeos::ErrorPtr* error) { | 
|  | return std::unique_ptr<base::Value>(new base::StringValue(value)); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<base::Value> TypedValueToJson(const native_types::Object& value, | 
|  | chromeos::ErrorPtr* error) { | 
|  | std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue); | 
|  | for (const auto& pair : value) { | 
|  | auto prop_value = pair.second->ToJson(error); | 
|  | if (!prop_value) | 
|  | return prop_value; | 
|  | dict->SetWithoutPathExpansion(pair.first, prop_value.release()); | 
|  | } | 
|  | return std::move(dict); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<base::Value> TypedValueToJson(const native_types::Array& value, | 
|  | chromeos::ErrorPtr* error) { | 
|  | std::unique_ptr<base::ListValue> list(new base::ListValue); | 
|  | for (const auto& item : value) { | 
|  | auto json = item->ToJson(error); | 
|  | if (!json) | 
|  | return std::unique_ptr<base::Value>(); | 
|  | list->Append(json.release()); | 
|  | } | 
|  | return std::move(list); | 
|  | } | 
|  |  | 
|  | bool TypedValueFromJson(const base::Value* value_in, | 
|  | const PropType* type, | 
|  | bool* value_out, | 
|  | chromeos::ErrorPtr* error) { | 
|  | return value_in->GetAsBoolean(value_out) || | 
|  | ReportUnexpectedJson(value_in, value_out, error); | 
|  | } | 
|  |  | 
|  | bool TypedValueFromJson(const base::Value* value_in, | 
|  | const PropType* type, | 
|  | int* value_out, | 
|  | chromeos::ErrorPtr* error) { | 
|  | return value_in->GetAsInteger(value_out) || | 
|  | ReportUnexpectedJson(value_in, value_out, error); | 
|  | } | 
|  |  | 
|  | bool TypedValueFromJson(const base::Value* value_in, | 
|  | const PropType* type, | 
|  | double* value_out, | 
|  | chromeos::ErrorPtr* error) { | 
|  | return value_in->GetAsDouble(value_out) || | 
|  | ReportUnexpectedJson(value_in, value_out, error); | 
|  | } | 
|  |  | 
|  | bool TypedValueFromJson(const base::Value* value_in, | 
|  | const PropType* type, | 
|  | std::string* value_out, | 
|  | chromeos::ErrorPtr* error) { | 
|  | return value_in->GetAsString(value_out) || | 
|  | ReportUnexpectedJson(value_in, value_out, error); | 
|  | } | 
|  |  | 
|  | bool TypedValueFromJson(const base::Value* value_in, | 
|  | const PropType* type, | 
|  | native_types::Object* value_out, | 
|  | chromeos::ErrorPtr* error) { | 
|  | const base::DictionaryValue* dict = nullptr; | 
|  | if (!value_in->GetAsDictionary(&dict)) | 
|  | return ReportUnexpectedJson(value_in, value_out, error); | 
|  |  | 
|  | CHECK(type) << "Object definition must be provided"; | 
|  | CHECK(ValueType::Object == type->GetType()) << "Type must be Object"; | 
|  |  | 
|  | const ObjectSchema* object_schema = type->GetObject()->GetObjectSchemaPtr(); | 
|  | std::set<std::string> keys_processed; | 
|  | value_out->clear();  // Clear possible default values already in |value_out|. | 
|  | for (const auto& pair : object_schema->GetProps()) { | 
|  | const PropValue* def_value = pair.second->GetDefaultValue(); | 
|  | if (dict->HasKey(pair.first)) { | 
|  | auto value = pair.second->CreateValue(); | 
|  | const base::Value* param_value = nullptr; | 
|  | CHECK(dict->GetWithoutPathExpansion(pair.first, ¶m_value)) | 
|  | << "Unable to get parameter"; | 
|  | if (!value->FromJson(param_value, error) || | 
|  | !pair.second->ValidateValue(value->GetValueAsAny(), error)) { | 
|  | chromeos::Error::AddToPrintf(error, FROM_HERE, | 
|  | errors::commands::kDomain, | 
|  | errors::commands::kInvalidPropValue, | 
|  | "Invalid value for property '%s'", | 
|  | pair.first.c_str()); | 
|  | return false; | 
|  | } | 
|  | value_out->emplace_hint(value_out->end(), pair.first, std::move(value)); | 
|  | } else if (def_value) { | 
|  | value_out->emplace_hint(value_out->end(), pair.first, def_value->Clone()); | 
|  | } else { | 
|  | return ErrorMissingProperty(error, pair.first.c_str()); | 
|  | } | 
|  | keys_processed.insert(pair.first); | 
|  | } | 
|  |  | 
|  | // Just for sanity, make sure that we processed all the necessary properties | 
|  | // and there weren't any extra (unknown) ones specified. If so, ignore | 
|  | // them, but log as warnings... | 
|  | base::DictionaryValue::Iterator iter(*dict); | 
|  | while (!iter.IsAtEnd()) { | 
|  | std::string key = iter.key(); | 
|  | if (keys_processed.find(key) == keys_processed.end() && | 
|  | !object_schema->GetExtraPropertiesAllowed()) { | 
|  | chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain, | 
|  | errors::commands::kUnknownProperty, | 
|  | "Unrecognized parameter '%s'", key.c_str()); | 
|  | return false; | 
|  | } | 
|  | iter.Advance(); | 
|  | } | 
|  |  | 
|  | // Now go over all property values and validate them. | 
|  | for (const auto& pair : *value_out) { | 
|  | const PropType* prop_type = pair.second->GetPropType(); | 
|  | CHECK(prop_type) << "Value property type must be available"; | 
|  | if (!prop_type->ValidateConstraints(*pair.second, error)) { | 
|  | chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain, | 
|  | errors::commands::kInvalidPropValue, | 
|  | "Invalid value for property '%s'", | 
|  | pair.first.c_str()); | 
|  | return false; | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool TypedValueFromJson(const base::Value* value_in, | 
|  | const PropType* type, | 
|  | native_types::Array* value_out, | 
|  | chromeos::ErrorPtr* error) { | 
|  | const base::ListValue* list = nullptr; | 
|  | if (!value_in->GetAsList(&list)) | 
|  | return ReportUnexpectedJson(value_in, value_out, error); | 
|  |  | 
|  | CHECK(type) << "Array type definition must be provided"; | 
|  | CHECK(ValueType::Array == type->GetType()) << "Type must be Array"; | 
|  | const PropType* item_type = type->GetArray()->GetItemTypePtr(); | 
|  | CHECK(item_type) << "Incomplete array type definition"; | 
|  |  | 
|  | // This value might already contain values from the type defaults. | 
|  | // Clear them first before proceeding. | 
|  | value_out->clear(); | 
|  | value_out->reserve(list->GetSize()); | 
|  | for (const base::Value* item : *list) { | 
|  | std::unique_ptr<PropValue> prop_value = item_type->CreateValue(); | 
|  | if (!prop_value->FromJson(item, error) || | 
|  | !item_type->ValidateValue(prop_value->GetValueAsAny(), error)) { | 
|  | return false; | 
|  | } | 
|  | value_out->push_back(std::move(prop_value)); | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // Compares two sets of key-value pairs from two Objects. | 
|  | static bool obj_cmp(const native_types::Object::value_type& v1, | 
|  | const native_types::Object::value_type& v2) { | 
|  | return (v1.first == v2.first) && v1.second->IsEqual(v2.second.get()); | 
|  | } | 
|  |  | 
|  | bool operator==(const native_types::Object& obj1, | 
|  | const native_types::Object& obj2) { | 
|  | if (obj1.size() != obj2.size()) | 
|  | return false; | 
|  |  | 
|  | auto pair = std::mismatch(obj1.begin(), obj1.end(), obj2.begin(), obj_cmp); | 
|  | return pair == std::make_pair(obj1.end(), obj2.end()); | 
|  | } | 
|  |  | 
|  | bool operator==(const native_types::Array& arr1, | 
|  | const native_types::Array& arr2) { | 
|  | if (arr1.size() != arr2.size()) | 
|  | return false; | 
|  |  | 
|  | using Type = const native_types::Array::value_type; | 
|  | // Compare two array items. | 
|  | auto arr_cmp = [](const Type& v1, const Type& v2) { | 
|  | return v1->IsEqual(v2.get()); | 
|  | }; | 
|  | auto pair = std::mismatch(arr1.begin(), arr1.end(), arr2.begin(), arr_cmp); | 
|  | return pair == std::make_pair(arr1.end(), arr2.end()); | 
|  | } | 
|  |  | 
|  | std::string ToString(const native_types::Object& obj) { | 
|  | auto val = TypedValueToJson(obj, nullptr); | 
|  | std::string str; | 
|  | base::JSONWriter::Write(val.get(), &str); | 
|  | return str; | 
|  | } | 
|  |  | 
|  | std::string ToString(const native_types::Array& arr) { | 
|  | auto val = TypedValueToJson(arr, nullptr); | 
|  | std::string str; | 
|  | base::JSONWriter::Write(val.get(), &str); | 
|  | return str; | 
|  | } | 
|  |  | 
|  | 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 native_types::Object& object) { | 
|  | chromeos::VariantDictionary dict; | 
|  | for (const auto& pair : object) { | 
|  | // 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 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 | 
|  | // 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 result; | 
|  | } | 
|  | CHECK(nullptr != type->GetObject()->GetObjectSchemaPtr()) | 
|  | << "An object type must have a schema defined for it"; | 
|  | native_types::Object 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, | 
|  | native_types::Object* 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, 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; | 
|  | } | 
|  |  | 
|  | }  // namespace buffet |