blob: 47b757f9a16265f1f838249df348b2bf39591c4a [file] [log] [blame]
// 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, &param_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