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> |
| 12 | |
| 13 | #include "buffet/commands/object_schema.h" |
| 14 | #include "buffet/commands/prop_types.h" |
| 15 | #include "buffet/commands/prop_values.h" |
| 16 | |
| 17 | namespace buffet { |
| 18 | namespace { |
| 19 | // Helper function to report "type mismatch" errors when parsing JSON. |
| 20 | void ReportJsonTypeMismatch(const base::Value* value_in, |
| 21 | const std::string& expected_type, |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 22 | chromeos::ErrorPtr* error) { |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 23 | std::string value_as_string; |
| 24 | base::JSONWriter::Write(value_in, &value_as_string); |
Alex Vakulenko | ac8037d | 2014-11-11 11:42:05 -0800 | [diff] [blame] | 25 | chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain, |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 26 | errors::commands::kTypeMismatch, |
| 27 | "Unable to convert value %s into %s", |
| 28 | value_as_string.c_str(), expected_type.c_str()); |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 29 | } |
| 30 | |
| 31 | // Template version of ReportJsonTypeMismatch that deduces the type of expected |
| 32 | // data from the value_out parameter passed to particular overload of |
| 33 | // TypedValueFromJson() function. Always returns false. |
| 34 | template<typename T> |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 35 | bool ReportUnexpectedJson(const base::Value* value_in, T*, |
| 36 | chromeos::ErrorPtr* error) { |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 37 | ReportJsonTypeMismatch(value_in, |
| 38 | PropType::GetTypeStringFromType(GetValueType<T>()), |
| 39 | error); |
| 40 | return false; |
| 41 | } |
| 42 | |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 43 | bool ErrorMissingProperty(chromeos::ErrorPtr* error, const char* param_name) { |
Alex Vakulenko | ac8037d | 2014-11-11 11:42:05 -0800 | [diff] [blame] | 44 | chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain, |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 45 | errors::commands::kPropertyMissing, |
| 46 | "Required parameter missing: %s", param_name); |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 47 | return false; |
| 48 | } |
| 49 | } // namespace |
| 50 | |
| 51 | // Specializations of TypedValueToJson<T>() for supported C++ types. |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 52 | std::unique_ptr<base::Value> TypedValueToJson(bool value, |
| 53 | chromeos::ErrorPtr* error) { |
Ben Chan | d0ce0d7 | 2014-09-04 21:59:24 -0700 | [diff] [blame] | 54 | return std::unique_ptr<base::Value>(new base::FundamentalValue(value)); |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 55 | } |
| 56 | |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 57 | std::unique_ptr<base::Value> TypedValueToJson(int value, |
| 58 | chromeos::ErrorPtr* error) { |
Ben Chan | d0ce0d7 | 2014-09-04 21:59:24 -0700 | [diff] [blame] | 59 | return std::unique_ptr<base::Value>(new base::FundamentalValue(value)); |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 60 | } |
| 61 | |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 62 | std::unique_ptr<base::Value> TypedValueToJson(double value, |
| 63 | chromeos::ErrorPtr* error) { |
Ben Chan | d0ce0d7 | 2014-09-04 21:59:24 -0700 | [diff] [blame] | 64 | return std::unique_ptr<base::Value>(new base::FundamentalValue(value)); |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 65 | } |
| 66 | |
| 67 | std::unique_ptr<base::Value> TypedValueToJson(const std::string& value, |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 68 | chromeos::ErrorPtr* error) { |
Ben Chan | d0ce0d7 | 2014-09-04 21:59:24 -0700 | [diff] [blame] | 69 | return std::unique_ptr<base::Value>(new base::StringValue(value)); |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 70 | } |
| 71 | |
| 72 | std::unique_ptr<base::Value> TypedValueToJson(const native_types::Object& value, |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 73 | chromeos::ErrorPtr* error) { |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 74 | std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue); |
| 75 | for (const auto& pair : value) { |
| 76 | auto prop_value = pair.second->ToJson(error); |
| 77 | if (!prop_value) |
| 78 | return prop_value; |
| 79 | dict->SetWithoutPathExpansion(pair.first, prop_value.release()); |
| 80 | } |
| 81 | return std::move(dict); |
| 82 | } |
| 83 | |
| 84 | bool TypedValueFromJson(const base::Value* value_in, |
| 85 | const ObjectSchema* object_schema, |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 86 | bool* value_out, chromeos::ErrorPtr* error) { |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 87 | return value_in->GetAsBoolean(value_out) || |
| 88 | ReportUnexpectedJson(value_in, value_out, error); |
| 89 | } |
| 90 | |
| 91 | bool TypedValueFromJson(const base::Value* value_in, |
| 92 | const ObjectSchema* object_schema, |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 93 | int* value_out, chromeos::ErrorPtr* error) { |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 94 | return value_in->GetAsInteger(value_out) || |
| 95 | ReportUnexpectedJson(value_in, value_out, error); |
| 96 | } |
| 97 | |
| 98 | bool TypedValueFromJson(const base::Value* value_in, |
| 99 | const ObjectSchema* object_schema, |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 100 | double* value_out, chromeos::ErrorPtr* error) { |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 101 | return value_in->GetAsDouble(value_out) || |
| 102 | ReportUnexpectedJson(value_in, value_out, error); |
| 103 | } |
| 104 | |
| 105 | bool TypedValueFromJson(const base::Value* value_in, |
| 106 | const ObjectSchema* object_schema, |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 107 | std::string* value_out, chromeos::ErrorPtr* error) { |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 108 | return value_in->GetAsString(value_out) || |
| 109 | ReportUnexpectedJson(value_in, value_out, error); |
| 110 | } |
| 111 | |
| 112 | bool TypedValueFromJson(const base::Value* value_in, |
| 113 | const ObjectSchema* object_schema, |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 114 | native_types::Object* value_out, |
| 115 | chromeos::ErrorPtr* error) { |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 116 | const base::DictionaryValue* dict = nullptr; |
| 117 | if (!value_in->GetAsDictionary(&dict)) |
| 118 | return ReportUnexpectedJson(value_in, value_out, error); |
| 119 | |
| 120 | CHECK(object_schema) << "Object schema must be provided"; |
| 121 | |
| 122 | std::set<std::string> keys_processed; |
| 123 | for (const auto& pair : object_schema->GetProps()) { |
| 124 | const PropValue* def_value = pair.second->GetDefaultValue(); |
| 125 | if (dict->HasKey(pair.first)) { |
| 126 | std::shared_ptr<PropValue> value = pair.second->CreateValue(); |
| 127 | const base::Value* param_value = nullptr; |
| 128 | CHECK(dict->GetWithoutPathExpansion(pair.first, ¶m_value)) |
| 129 | << "Unable to get parameter"; |
Alex Vakulenko | a32d83a | 2014-09-19 15:05:24 -0700 | [diff] [blame] | 130 | if (!value->FromJson(param_value, error)) { |
Alex Vakulenko | ac8037d | 2014-11-11 11:42:05 -0800 | [diff] [blame] | 131 | chromeos::Error::AddToPrintf(error, FROM_HERE, |
| 132 | errors::commands::kDomain, |
Alex Vakulenko | a32d83a | 2014-09-19 15:05:24 -0700 | [diff] [blame] | 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 | ac8037d | 2014-11-11 11:42:05 -0800 | [diff] [blame] | 156 | chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain, |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 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 | ac8037d | 2014-11-11 11:42:05 -0800 | [diff] [blame] | 169 | chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain, |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 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(); |
Anton Muhin | cfde869 | 2014-11-25 03:36:59 +0400 | [diff] [blame] | 204 | return ObjectToDBusVariant(value->GetObject()->GetValue()); |
| 205 | } |
| 206 | |
| 207 | chromeos::VariantDictionary |
| 208 | ObjectToDBusVariant(const native_types::Object& object) { |
Alex Vakulenko | 576c979 | 2014-09-22 16:49:45 -0700 | [diff] [blame] | 209 | chromeos::VariantDictionary dict; |
Anton Muhin | cfde869 | 2014-11-25 03:36:59 +0400 | [diff] [blame] | 210 | for (const auto& pair : object) { |
Alex Vakulenko | a32d83a | 2014-09-19 15:05:24 -0700 | [diff] [blame] | 211 | // Since we are inserting the elements from native_types::Object which is |
| 212 | // a map, the keys are already sorted. So use the "end()" position as a hint |
| 213 | // for dict.insert() so the destination map can optimize its insertion |
| 214 | // time. |
| 215 | chromeos::Any prop = PropValueToDBusVariant(pair.second.get()); |
| 216 | dict.emplace_hint(dict.end(), pair.first, std::move(prop)); |
| 217 | } |
Anton Muhin | cfde869 | 2014-11-25 03:36:59 +0400 | [diff] [blame] | 218 | return dict; |
Alex Vakulenko | a32d83a | 2014-09-19 15:05:24 -0700 | [diff] [blame] | 219 | } |
| 220 | |
| 221 | std::shared_ptr<const PropValue> PropValueFromDBusVariant( |
| 222 | const PropType* type, |
| 223 | const chromeos::Any& value, |
| 224 | chromeos::ErrorPtr* error) { |
| 225 | std::shared_ptr<const PropValue> result; |
Anton Muhin | cfde869 | 2014-11-25 03:36:59 +0400 | [diff] [blame] | 226 | if (type->GetType() == ValueType::Object) { |
| 227 | // Special case for object types. |
| 228 | // We expect the |value| to contain chromeos::VariantDictionary, while |
| 229 | // PropValue must use native_types::Object instead. Do the conversion. |
| 230 | if (!value.IsTypeCompatible<chromeos::VariantDictionary>()) { |
| 231 | type->GenerateErrorValueTypeMismatch(error); |
| 232 | return {}; |
| 233 | } |
| 234 | CHECK(nullptr != type->GetObjectSchemaPtr()) |
| 235 | << "An object type must have a schema defined for it"; |
| 236 | native_types::Object obj; |
| 237 | if (!ObjectFromDBusVariant(type->GetObjectSchemaPtr(), |
| 238 | value.Get<chromeos::VariantDictionary>(), |
| 239 | &obj, |
| 240 | error)) |
| 241 | return {}; |
| 242 | |
| 243 | result = type->CreateValue(std::move(obj), error); |
| 244 | } else { |
Alex Vakulenko | a32d83a | 2014-09-19 15:05:24 -0700 | [diff] [blame] | 245 | result = type->CreateValue(value, error); |
Alex Vakulenko | a32d83a | 2014-09-19 15:05:24 -0700 | [diff] [blame] | 246 | } |
| 247 | |
Anton Muhin | cfde869 | 2014-11-25 03:36:59 +0400 | [diff] [blame] | 248 | if (result && !type->ValidateConstraints(*result, error)) |
| 249 | result.reset(); |
| 250 | return result; |
| 251 | } |
| 252 | |
| 253 | bool ObjectFromDBusVariant(const ObjectSchema* object_schema, |
| 254 | const chromeos::VariantDictionary& dict, |
| 255 | native_types::Object* obj, |
| 256 | chromeos::ErrorPtr* error) { |
Alex Vakulenko | a32d83a | 2014-09-19 15:05:24 -0700 | [diff] [blame] | 257 | std::set<std::string> keys_processed; |
| 258 | // First go over all object parameters defined by type's object schema and |
| 259 | // extract the corresponding parameters from the source dictionary. |
Anton Muhin | cfde869 | 2014-11-25 03:36:59 +0400 | [diff] [blame] | 260 | for (const auto& pair : object_schema->GetProps()) { |
Alex Vakulenko | a32d83a | 2014-09-19 15:05:24 -0700 | [diff] [blame] | 261 | const PropValue* def_value = pair.second->GetDefaultValue(); |
| 262 | auto it = dict.find(pair.first); |
| 263 | if (it != dict.end()) { |
| 264 | const PropType* prop_type = pair.second.get(); |
| 265 | CHECK(prop_type) << "Value property type must be available"; |
| 266 | auto prop_value = PropValueFromDBusVariant(prop_type, it->second, error); |
| 267 | if (!prop_value) { |
Alex Vakulenko | ac8037d | 2014-11-11 11:42:05 -0800 | [diff] [blame] | 268 | chromeos::Error::AddToPrintf(error, FROM_HERE, |
| 269 | errors::commands::kDomain, |
Alex Vakulenko | a32d83a | 2014-09-19 15:05:24 -0700 | [diff] [blame] | 270 | errors::commands::kInvalidPropValue, |
| 271 | "Invalid value for property '%s'", |
| 272 | pair.first.c_str()); |
Anton Muhin | cfde869 | 2014-11-25 03:36:59 +0400 | [diff] [blame] | 273 | return false; |
Alex Vakulenko | a32d83a | 2014-09-19 15:05:24 -0700 | [diff] [blame] | 274 | } |
Anton Muhin | cfde869 | 2014-11-25 03:36:59 +0400 | [diff] [blame] | 275 | obj->emplace_hint(obj->end(), pair.first, std::move(prop_value)); |
Alex Vakulenko | a32d83a | 2014-09-19 15:05:24 -0700 | [diff] [blame] | 276 | } else if (def_value) { |
| 277 | std::shared_ptr<const PropValue> prop_value = def_value->Clone(); |
Anton Muhin | cfde869 | 2014-11-25 03:36:59 +0400 | [diff] [blame] | 278 | obj->emplace_hint(obj->end(), pair.first, std::move(prop_value)); |
Alex Vakulenko | a32d83a | 2014-09-19 15:05:24 -0700 | [diff] [blame] | 279 | } else { |
| 280 | ErrorMissingProperty(error, pair.first.c_str()); |
Anton Muhin | cfde869 | 2014-11-25 03:36:59 +0400 | [diff] [blame] | 281 | return false; |
Alex Vakulenko | a32d83a | 2014-09-19 15:05:24 -0700 | [diff] [blame] | 282 | } |
| 283 | keys_processed.insert(pair.first); |
| 284 | } |
| 285 | |
| 286 | // Make sure that we processed all the necessary properties and there weren't |
| 287 | // any extra (unknown) ones specified, unless the schema allows them. |
Anton Muhin | cfde869 | 2014-11-25 03:36:59 +0400 | [diff] [blame] | 288 | if (!object_schema->GetExtraPropertiesAllowed()) { |
Alex Vakulenko | a32d83a | 2014-09-19 15:05:24 -0700 | [diff] [blame] | 289 | for (const auto& pair : dict) { |
| 290 | if (keys_processed.find(pair.first) == keys_processed.end()) { |
Alex Vakulenko | ac8037d | 2014-11-11 11:42:05 -0800 | [diff] [blame] | 291 | chromeos::Error::AddToPrintf(error, FROM_HERE, |
| 292 | errors::commands::kDomain, |
Alex Vakulenko | a32d83a | 2014-09-19 15:05:24 -0700 | [diff] [blame] | 293 | errors::commands::kUnknownProperty, |
| 294 | "Unrecognized property '%s'", |
| 295 | pair.first.c_str()); |
Anton Muhin | cfde869 | 2014-11-25 03:36:59 +0400 | [diff] [blame] | 296 | return false; |
Alex Vakulenko | a32d83a | 2014-09-19 15:05:24 -0700 | [diff] [blame] | 297 | } |
| 298 | } |
| 299 | } |
| 300 | |
Anton Muhin | cfde869 | 2014-11-25 03:36:59 +0400 | [diff] [blame] | 301 | return true; |
Alex Vakulenko | a32d83a | 2014-09-19 15:05:24 -0700 | [diff] [blame] | 302 | } |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 303 | |
| 304 | } // namespace buffet |