Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 1 | // Copyright 2014 The Chromium OS Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include "buffet/commands/schema_utils.h" |
| 6 | |
| 7 | #include <algorithm> |
| 8 | #include <set> |
| 9 | #include <string> |
| 10 | |
| 11 | #include <base/json/json_writer.h> |
Alex Vakulenko | 576c979 | 2014-09-22 16:49:45 -0700 | [diff] [blame] | 12 | #include <chromeos/variant_dictionary.h> |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 13 | |
| 14 | #include "buffet/commands/object_schema.h" |
| 15 | #include "buffet/commands/prop_types.h" |
| 16 | #include "buffet/commands/prop_values.h" |
| 17 | |
| 18 | namespace buffet { |
| 19 | namespace { |
| 20 | // Helper function to report "type mismatch" errors when parsing JSON. |
| 21 | void ReportJsonTypeMismatch(const base::Value* value_in, |
| 22 | const std::string& expected_type, |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 23 | chromeos::ErrorPtr* error) { |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 24 | std::string value_as_string; |
| 25 | base::JSONWriter::Write(value_in, &value_as_string); |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 26 | chromeos::Error::AddToPrintf(error, errors::commands::kDomain, |
| 27 | errors::commands::kTypeMismatch, |
| 28 | "Unable to convert value %s into %s", |
| 29 | value_as_string.c_str(), expected_type.c_str()); |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 30 | } |
| 31 | |
| 32 | // Template version of ReportJsonTypeMismatch that deduces the type of expected |
| 33 | // data from the value_out parameter passed to particular overload of |
| 34 | // TypedValueFromJson() function. Always returns false. |
| 35 | template<typename T> |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 36 | bool ReportUnexpectedJson(const base::Value* value_in, T*, |
| 37 | chromeos::ErrorPtr* error) { |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 38 | ReportJsonTypeMismatch(value_in, |
| 39 | PropType::GetTypeStringFromType(GetValueType<T>()), |
| 40 | error); |
| 41 | return false; |
| 42 | } |
| 43 | |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 44 | bool ErrorMissingProperty(chromeos::ErrorPtr* error, const char* param_name) { |
| 45 | chromeos::Error::AddToPrintf(error, errors::commands::kDomain, |
| 46 | errors::commands::kPropertyMissing, |
| 47 | "Required parameter missing: %s", param_name); |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 48 | return false; |
| 49 | } |
| 50 | } // namespace |
| 51 | |
| 52 | // Specializations of TypedValueToJson<T>() for supported C++ types. |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 53 | std::unique_ptr<base::Value> TypedValueToJson(bool value, |
| 54 | chromeos::ErrorPtr* error) { |
Ben Chan | d0ce0d7 | 2014-09-04 21:59:24 -0700 | [diff] [blame] | 55 | return std::unique_ptr<base::Value>(new base::FundamentalValue(value)); |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 56 | } |
| 57 | |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 58 | std::unique_ptr<base::Value> TypedValueToJson(int value, |
| 59 | chromeos::ErrorPtr* error) { |
Ben Chan | d0ce0d7 | 2014-09-04 21:59:24 -0700 | [diff] [blame] | 60 | return std::unique_ptr<base::Value>(new base::FundamentalValue(value)); |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 61 | } |
| 62 | |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 63 | std::unique_ptr<base::Value> TypedValueToJson(double value, |
| 64 | chromeos::ErrorPtr* error) { |
Ben Chan | d0ce0d7 | 2014-09-04 21:59:24 -0700 | [diff] [blame] | 65 | return std::unique_ptr<base::Value>(new base::FundamentalValue(value)); |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 66 | } |
| 67 | |
| 68 | std::unique_ptr<base::Value> TypedValueToJson(const std::string& value, |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 69 | chromeos::ErrorPtr* error) { |
Ben Chan | d0ce0d7 | 2014-09-04 21:59:24 -0700 | [diff] [blame] | 70 | return std::unique_ptr<base::Value>(new base::StringValue(value)); |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 71 | } |
| 72 | |
| 73 | std::unique_ptr<base::Value> TypedValueToJson(const native_types::Object& value, |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 74 | chromeos::ErrorPtr* error) { |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 75 | std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue); |
| 76 | for (const auto& pair : value) { |
| 77 | auto prop_value = pair.second->ToJson(error); |
| 78 | if (!prop_value) |
| 79 | return prop_value; |
| 80 | dict->SetWithoutPathExpansion(pair.first, prop_value.release()); |
| 81 | } |
| 82 | return std::move(dict); |
| 83 | } |
| 84 | |
| 85 | bool TypedValueFromJson(const base::Value* value_in, |
| 86 | const ObjectSchema* object_schema, |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 87 | bool* value_out, chromeos::ErrorPtr* error) { |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 88 | return value_in->GetAsBoolean(value_out) || |
| 89 | ReportUnexpectedJson(value_in, value_out, error); |
| 90 | } |
| 91 | |
| 92 | bool TypedValueFromJson(const base::Value* value_in, |
| 93 | const ObjectSchema* object_schema, |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 94 | int* value_out, chromeos::ErrorPtr* error) { |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 95 | return value_in->GetAsInteger(value_out) || |
| 96 | ReportUnexpectedJson(value_in, value_out, error); |
| 97 | } |
| 98 | |
| 99 | bool TypedValueFromJson(const base::Value* value_in, |
| 100 | const ObjectSchema* object_schema, |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 101 | double* value_out, chromeos::ErrorPtr* error) { |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 102 | return value_in->GetAsDouble(value_out) || |
| 103 | ReportUnexpectedJson(value_in, value_out, error); |
| 104 | } |
| 105 | |
| 106 | bool TypedValueFromJson(const base::Value* value_in, |
| 107 | const ObjectSchema* object_schema, |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 108 | std::string* value_out, chromeos::ErrorPtr* error) { |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 109 | return value_in->GetAsString(value_out) || |
| 110 | ReportUnexpectedJson(value_in, value_out, error); |
| 111 | } |
| 112 | |
| 113 | bool TypedValueFromJson(const base::Value* value_in, |
| 114 | const ObjectSchema* object_schema, |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 115 | native_types::Object* value_out, |
| 116 | chromeos::ErrorPtr* error) { |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 117 | const base::DictionaryValue* dict = nullptr; |
| 118 | if (!value_in->GetAsDictionary(&dict)) |
| 119 | return ReportUnexpectedJson(value_in, value_out, error); |
| 120 | |
| 121 | CHECK(object_schema) << "Object schema must be provided"; |
| 122 | |
| 123 | std::set<std::string> keys_processed; |
| 124 | for (const auto& pair : object_schema->GetProps()) { |
| 125 | const PropValue* def_value = pair.second->GetDefaultValue(); |
| 126 | if (dict->HasKey(pair.first)) { |
| 127 | std::shared_ptr<PropValue> value = pair.second->CreateValue(); |
| 128 | const base::Value* param_value = nullptr; |
| 129 | CHECK(dict->GetWithoutPathExpansion(pair.first, ¶m_value)) |
| 130 | << "Unable to get parameter"; |
Alex Vakulenko | a32d83a | 2014-09-19 15:05:24 -0700 | [diff] [blame] | 131 | if (!value->FromJson(param_value, error)) { |
| 132 | chromeos::Error::AddToPrintf(error, errors::commands::kDomain, |
| 133 | errors::commands::kInvalidPropValue, |
| 134 | "Invalid value for property '%s'", |
| 135 | pair.first.c_str()); |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 136 | return false; |
Alex Vakulenko | a32d83a | 2014-09-19 15:05:24 -0700 | [diff] [blame] | 137 | } |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 138 | value_out->insert(std::make_pair(pair.first, std::move(value))); |
| 139 | } else if (def_value) { |
| 140 | std::shared_ptr<PropValue> value = def_value->Clone(); |
| 141 | value_out->insert(std::make_pair(pair.first, std::move(value))); |
| 142 | } else { |
| 143 | return ErrorMissingProperty(error, pair.first.c_str()); |
| 144 | } |
| 145 | keys_processed.insert(pair.first); |
| 146 | } |
| 147 | |
| 148 | // Just for sanity, make sure that we processed all the necessary properties |
| 149 | // and there weren't any extra (unknown) ones specified. If so, ignore |
| 150 | // them, but log as warnings... |
| 151 | base::DictionaryValue::Iterator iter(*dict); |
| 152 | while (!iter.IsAtEnd()) { |
| 153 | std::string key = iter.key(); |
| 154 | if (keys_processed.find(key) == keys_processed.end() && |
| 155 | !object_schema->GetExtraPropertiesAllowed()) { |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 156 | chromeos::Error::AddToPrintf(error, errors::commands::kDomain, |
| 157 | errors::commands::kUnknownProperty, |
| 158 | "Unrecognized parameter '%s'", key.c_str()); |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 159 | return false; |
| 160 | } |
| 161 | iter.Advance(); |
| 162 | } |
| 163 | |
| 164 | // Now go over all property values and validate them. |
| 165 | for (const auto& pair : *value_out) { |
| 166 | const PropType* prop_type = pair.second->GetPropType(); |
| 167 | CHECK(prop_type) << "Value property type must be available"; |
| 168 | if (!prop_type->ValidateConstraints(*pair.second, error)) { |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 169 | chromeos::Error::AddToPrintf(error, errors::commands::kDomain, |
| 170 | errors::commands::kInvalidPropValue, |
Alex Vakulenko | a32d83a | 2014-09-19 15:05:24 -0700 | [diff] [blame] | 171 | "Invalid value for property '%s'", |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 172 | pair.first.c_str()); |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 173 | return false; |
| 174 | } |
| 175 | } |
| 176 | return true; |
| 177 | } |
| 178 | |
| 179 | // Compares two sets of key-value pairs from two Objects. |
| 180 | static bool obj_cmp(const native_types::Object::value_type& v1, |
| 181 | const native_types::Object::value_type& v2) { |
| 182 | return (v1.first == v2.first) && v1.second->IsEqual(v2.second.get()); |
| 183 | } |
| 184 | |
| 185 | bool operator==(const native_types::Object& obj1, |
| 186 | const native_types::Object& obj2) { |
| 187 | if (obj1.size() != obj2.size()) |
| 188 | return false; |
| 189 | |
| 190 | auto pair = std::mismatch(obj1.begin(), obj1.end(), obj2.begin(), obj_cmp); |
| 191 | return pair == std::make_pair(obj1.end(), obj2.end()); |
| 192 | } |
| 193 | |
| 194 | std::string ToString(const native_types::Object& obj) { |
| 195 | auto val = TypedValueToJson(obj, nullptr); |
| 196 | std::string str; |
| 197 | base::JSONWriter::Write(val.get(), &str); |
| 198 | return str; |
| 199 | } |
| 200 | |
Alex Vakulenko | a32d83a | 2014-09-19 15:05:24 -0700 | [diff] [blame] | 201 | chromeos::Any PropValueToDBusVariant(const PropValue* value) { |
| 202 | if (value->GetType() != ValueType::Object) |
| 203 | return value->GetValueAsAny(); |
| 204 | // Special case for object types. |
Alex Vakulenko | 576c979 | 2014-09-22 16:49:45 -0700 | [diff] [blame] | 205 | // Convert native_types::Object to chromeos::VariantDictionary |
| 206 | chromeos::VariantDictionary dict; |
Alex Vakulenko | a32d83a | 2014-09-19 15:05:24 -0700 | [diff] [blame] | 207 | for (const auto& pair : value->GetObject()->GetValue()) { |
| 208 | // Since we are inserting the elements from native_types::Object which is |
| 209 | // a map, the keys are already sorted. So use the "end()" position as a hint |
| 210 | // for dict.insert() so the destination map can optimize its insertion |
| 211 | // time. |
| 212 | chromeos::Any prop = PropValueToDBusVariant(pair.second.get()); |
| 213 | dict.emplace_hint(dict.end(), pair.first, std::move(prop)); |
| 214 | } |
| 215 | return chromeos::Any(std::move(dict)); |
| 216 | } |
| 217 | |
| 218 | std::shared_ptr<const PropValue> PropValueFromDBusVariant( |
| 219 | const PropType* type, |
| 220 | const chromeos::Any& value, |
| 221 | chromeos::ErrorPtr* error) { |
| 222 | std::shared_ptr<const PropValue> result; |
| 223 | if (type->GetType() != ValueType::Object) { |
| 224 | result = type->CreateValue(value, error); |
| 225 | if (result && !type->ValidateConstraints(*result, error)) |
| 226 | result.reset(); |
| 227 | return result; |
| 228 | } |
| 229 | |
| 230 | // Special case for object types. |
Alex Vakulenko | 576c979 | 2014-09-22 16:49:45 -0700 | [diff] [blame] | 231 | // We expect the |value| to contain chromeos::VariantDictionary, while |
Alex Vakulenko | a32d83a | 2014-09-19 15:05:24 -0700 | [diff] [blame] | 232 | // PropValue must use native_types::Object instead. Do the conversion. |
Alex Vakulenko | 576c979 | 2014-09-22 16:49:45 -0700 | [diff] [blame] | 233 | if (!value.IsTypeCompatible<chromeos::VariantDictionary>()) { |
Alex Vakulenko | a32d83a | 2014-09-19 15:05:24 -0700 | [diff] [blame] | 234 | type->GenerateErrorValueTypeMismatch(error); |
| 235 | return result; |
| 236 | } |
Alex Vakulenko | 576c979 | 2014-09-22 16:49:45 -0700 | [diff] [blame] | 237 | const auto& dict = value.Get<chromeos::VariantDictionary>(); |
Alex Vakulenko | a32d83a | 2014-09-19 15:05:24 -0700 | [diff] [blame] | 238 | native_types::Object obj; |
| 239 | CHECK(nullptr != type->GetObjectSchemaPtr()) |
| 240 | << "An object type must have a schema defined for it"; |
| 241 | std::set<std::string> keys_processed; |
| 242 | // First go over all object parameters defined by type's object schema and |
| 243 | // extract the corresponding parameters from the source dictionary. |
| 244 | for (const auto& pair : type->GetObjectSchemaPtr()->GetProps()) { |
| 245 | const PropValue* def_value = pair.second->GetDefaultValue(); |
| 246 | auto it = dict.find(pair.first); |
| 247 | if (it != dict.end()) { |
| 248 | const PropType* prop_type = pair.second.get(); |
| 249 | CHECK(prop_type) << "Value property type must be available"; |
| 250 | auto prop_value = PropValueFromDBusVariant(prop_type, it->second, error); |
| 251 | if (!prop_value) { |
| 252 | chromeos::Error::AddToPrintf(error, errors::commands::kDomain, |
| 253 | errors::commands::kInvalidPropValue, |
| 254 | "Invalid value for property '%s'", |
| 255 | pair.first.c_str()); |
| 256 | return result; |
| 257 | } |
| 258 | obj.emplace_hint(obj.end(), pair.first, std::move(prop_value)); |
| 259 | } else if (def_value) { |
| 260 | std::shared_ptr<const PropValue> prop_value = def_value->Clone(); |
| 261 | obj.emplace_hint(obj.end(), pair.first, std::move(prop_value)); |
| 262 | } else { |
| 263 | ErrorMissingProperty(error, pair.first.c_str()); |
| 264 | return result; |
| 265 | } |
| 266 | keys_processed.insert(pair.first); |
| 267 | } |
| 268 | |
| 269 | // Make sure that we processed all the necessary properties and there weren't |
| 270 | // any extra (unknown) ones specified, unless the schema allows them. |
| 271 | if (!type->GetObjectSchemaPtr()->GetExtraPropertiesAllowed()) { |
| 272 | for (const auto& pair : dict) { |
| 273 | if (keys_processed.find(pair.first) == keys_processed.end()) { |
| 274 | chromeos::Error::AddToPrintf(error, errors::commands::kDomain, |
| 275 | errors::commands::kUnknownProperty, |
| 276 | "Unrecognized property '%s'", |
| 277 | pair.first.c_str()); |
| 278 | return result; |
| 279 | } |
| 280 | } |
| 281 | } |
| 282 | |
| 283 | result = type->CreateValue(std::move(obj), error); |
| 284 | if (result && !type->ValidateConstraints(*result, error)) |
| 285 | result.reset(); |
| 286 | return result; |
| 287 | } |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 288 | |
| 289 | } // namespace buffet |