blob: 47b757f9a16265f1f838249df348b2bf39591c4a [file] [log] [blame]
Alex Vakulenko66ec2922014-06-17 15:30:22 -07001// 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
17namespace buffet {
18namespace {
19// Helper function to report "type mismatch" errors when parsing JSON.
20void ReportJsonTypeMismatch(const base::Value* value_in,
21 const std::string& expected_type,
Alex Vakulenko5f472062014-08-14 17:54:04 -070022 chromeos::ErrorPtr* error) {
Alex Vakulenko66ec2922014-06-17 15:30:22 -070023 std::string value_as_string;
24 base::JSONWriter::Write(value_in, &value_as_string);
Alex Vakulenkoac8037d2014-11-11 11:42:05 -080025 chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
Alex Vakulenko5f472062014-08-14 17:54:04 -070026 errors::commands::kTypeMismatch,
27 "Unable to convert value %s into %s",
28 value_as_string.c_str(), expected_type.c_str());
Alex Vakulenko66ec2922014-06-17 15:30:22 -070029}
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.
34template<typename T>
Alex Vakulenko5f472062014-08-14 17:54:04 -070035bool ReportUnexpectedJson(const base::Value* value_in, T*,
36 chromeos::ErrorPtr* error) {
Alex Vakulenko66ec2922014-06-17 15:30:22 -070037 ReportJsonTypeMismatch(value_in,
38 PropType::GetTypeStringFromType(GetValueType<T>()),
39 error);
40 return false;
41}
42
Alex Vakulenko5f472062014-08-14 17:54:04 -070043bool ErrorMissingProperty(chromeos::ErrorPtr* error, const char* param_name) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -080044 chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
Alex Vakulenko5f472062014-08-14 17:54:04 -070045 errors::commands::kPropertyMissing,
46 "Required parameter missing: %s", param_name);
Alex Vakulenko66ec2922014-06-17 15:30:22 -070047 return false;
48}
49} // namespace
50
51// Specializations of TypedValueToJson<T>() for supported C++ types.
Alex Vakulenko5f472062014-08-14 17:54:04 -070052std::unique_ptr<base::Value> TypedValueToJson(bool value,
53 chromeos::ErrorPtr* error) {
Ben Chand0ce0d72014-09-04 21:59:24 -070054 return std::unique_ptr<base::Value>(new base::FundamentalValue(value));
Alex Vakulenko66ec2922014-06-17 15:30:22 -070055}
56
Alex Vakulenko5f472062014-08-14 17:54:04 -070057std::unique_ptr<base::Value> TypedValueToJson(int value,
58 chromeos::ErrorPtr* error) {
Ben Chand0ce0d72014-09-04 21:59:24 -070059 return std::unique_ptr<base::Value>(new base::FundamentalValue(value));
Alex Vakulenko66ec2922014-06-17 15:30:22 -070060}
61
Alex Vakulenko5f472062014-08-14 17:54:04 -070062std::unique_ptr<base::Value> TypedValueToJson(double value,
63 chromeos::ErrorPtr* error) {
Ben Chand0ce0d72014-09-04 21:59:24 -070064 return std::unique_ptr<base::Value>(new base::FundamentalValue(value));
Alex Vakulenko66ec2922014-06-17 15:30:22 -070065}
66
67std::unique_ptr<base::Value> TypedValueToJson(const std::string& value,
Alex Vakulenko5f472062014-08-14 17:54:04 -070068 chromeos::ErrorPtr* error) {
Ben Chand0ce0d72014-09-04 21:59:24 -070069 return std::unique_ptr<base::Value>(new base::StringValue(value));
Alex Vakulenko66ec2922014-06-17 15:30:22 -070070}
71
72std::unique_ptr<base::Value> TypedValueToJson(const native_types::Object& value,
Alex Vakulenko5f472062014-08-14 17:54:04 -070073 chromeos::ErrorPtr* error) {
Alex Vakulenko66ec2922014-06-17 15:30:22 -070074 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
Alex Vakulenko29e64442015-03-20 13:59:19 -070084std::unique_ptr<base::Value> TypedValueToJson(const native_types::Array& value,
85 chromeos::ErrorPtr* error) {
86 std::unique_ptr<base::ListValue> list(new base::ListValue);
87 for (const auto& item : value) {
88 auto json = item->ToJson(error);
89 if (!json)
90 return std::unique_ptr<base::Value>();
91 list->Append(json.release());
92 }
93 return std::move(list);
94}
95
Alex Vakulenko66ec2922014-06-17 15:30:22 -070096bool TypedValueFromJson(const base::Value* value_in,
Alex Vakulenkod94656e2015-03-18 09:54:37 -070097 const PropType* type,
98 bool* value_out,
99 chromeos::ErrorPtr* error) {
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700100 return value_in->GetAsBoolean(value_out) ||
101 ReportUnexpectedJson(value_in, value_out, error);
102}
103
104bool TypedValueFromJson(const base::Value* value_in,
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700105 const PropType* type,
106 int* value_out,
107 chromeos::ErrorPtr* error) {
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700108 return value_in->GetAsInteger(value_out) ||
109 ReportUnexpectedJson(value_in, value_out, error);
110}
111
112bool TypedValueFromJson(const base::Value* value_in,
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700113 const PropType* type,
114 double* value_out,
115 chromeos::ErrorPtr* error) {
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700116 return value_in->GetAsDouble(value_out) ||
117 ReportUnexpectedJson(value_in, value_out, error);
118}
119
120bool TypedValueFromJson(const base::Value* value_in,
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700121 const PropType* type,
122 std::string* value_out,
123 chromeos::ErrorPtr* error) {
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700124 return value_in->GetAsString(value_out) ||
125 ReportUnexpectedJson(value_in, value_out, error);
126}
127
128bool TypedValueFromJson(const base::Value* value_in,
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700129 const PropType* type,
Alex Vakulenko5f472062014-08-14 17:54:04 -0700130 native_types::Object* value_out,
131 chromeos::ErrorPtr* error) {
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700132 const base::DictionaryValue* dict = nullptr;
133 if (!value_in->GetAsDictionary(&dict))
134 return ReportUnexpectedJson(value_in, value_out, error);
135
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700136 CHECK(type) << "Object definition must be provided";
137 CHECK(ValueType::Object == type->GetType()) << "Type must be Object";
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700138
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700139 const ObjectSchema* object_schema = type->GetObject()->GetObjectSchemaPtr();
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700140 std::set<std::string> keys_processed;
Alex Vakulenko3e864c02015-03-24 10:19:32 -0700141 value_out->clear(); // Clear possible default values already in |value_out|.
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700142 for (const auto& pair : object_schema->GetProps()) {
143 const PropValue* def_value = pair.second->GetDefaultValue();
144 if (dict->HasKey(pair.first)) {
Alex Vakulenko5ef75792015-03-19 15:50:44 -0700145 auto value = pair.second->CreateValue();
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700146 const base::Value* param_value = nullptr;
147 CHECK(dict->GetWithoutPathExpansion(pair.first, &param_value))
148 << "Unable to get parameter";
Alex Vakulenko29e64442015-03-20 13:59:19 -0700149 if (!value->FromJson(param_value, error) ||
150 !pair.second->ValidateValue(value->GetValueAsAny(), error)) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800151 chromeos::Error::AddToPrintf(error, FROM_HERE,
152 errors::commands::kDomain,
Alex Vakulenkoa32d83a2014-09-19 15:05:24 -0700153 errors::commands::kInvalidPropValue,
154 "Invalid value for property '%s'",
155 pair.first.c_str());
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700156 return false;
Alex Vakulenkoa32d83a2014-09-19 15:05:24 -0700157 }
Alex Vakulenko3e864c02015-03-24 10:19:32 -0700158 value_out->emplace_hint(value_out->end(), pair.first, std::move(value));
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700159 } else if (def_value) {
Alex Vakulenko3e864c02015-03-24 10:19:32 -0700160 value_out->emplace_hint(value_out->end(), pair.first, def_value->Clone());
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700161 } else {
162 return ErrorMissingProperty(error, pair.first.c_str());
163 }
164 keys_processed.insert(pair.first);
165 }
166
167 // Just for sanity, make sure that we processed all the necessary properties
168 // and there weren't any extra (unknown) ones specified. If so, ignore
169 // them, but log as warnings...
170 base::DictionaryValue::Iterator iter(*dict);
171 while (!iter.IsAtEnd()) {
172 std::string key = iter.key();
173 if (keys_processed.find(key) == keys_processed.end() &&
174 !object_schema->GetExtraPropertiesAllowed()) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800175 chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
Alex Vakulenko5f472062014-08-14 17:54:04 -0700176 errors::commands::kUnknownProperty,
177 "Unrecognized parameter '%s'", key.c_str());
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700178 return false;
179 }
180 iter.Advance();
181 }
182
183 // Now go over all property values and validate them.
184 for (const auto& pair : *value_out) {
185 const PropType* prop_type = pair.second->GetPropType();
186 CHECK(prop_type) << "Value property type must be available";
187 if (!prop_type->ValidateConstraints(*pair.second, error)) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800188 chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
Alex Vakulenko5f472062014-08-14 17:54:04 -0700189 errors::commands::kInvalidPropValue,
Alex Vakulenkoa32d83a2014-09-19 15:05:24 -0700190 "Invalid value for property '%s'",
Alex Vakulenko5f472062014-08-14 17:54:04 -0700191 pair.first.c_str());
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700192 return false;
193 }
194 }
195 return true;
196}
197
Alex Vakulenko29e64442015-03-20 13:59:19 -0700198bool TypedValueFromJson(const base::Value* value_in,
199 const PropType* type,
200 native_types::Array* value_out,
201 chromeos::ErrorPtr* error) {
202 const base::ListValue* list = nullptr;
203 if (!value_in->GetAsList(&list))
204 return ReportUnexpectedJson(value_in, value_out, error);
205
206 CHECK(type) << "Array type definition must be provided";
207 CHECK(ValueType::Array == type->GetType()) << "Type must be Array";
208 const PropType* item_type = type->GetArray()->GetItemTypePtr();
209 CHECK(item_type) << "Incomplete array type definition";
210
Alex Vakulenko3e864c02015-03-24 10:19:32 -0700211 // This value might already contain values from the type defaults.
212 // Clear them first before proceeding.
213 value_out->clear();
Alex Vakulenko29e64442015-03-20 13:59:19 -0700214 value_out->reserve(list->GetSize());
215 for (const base::Value* item : *list) {
216 std::unique_ptr<PropValue> prop_value = item_type->CreateValue();
217 if (!prop_value->FromJson(item, error) ||
218 !item_type->ValidateValue(prop_value->GetValueAsAny(), error)) {
219 return false;
220 }
221 value_out->push_back(std::move(prop_value));
222 }
223 return true;
224}
225
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700226// Compares two sets of key-value pairs from two Objects.
227static bool obj_cmp(const native_types::Object::value_type& v1,
228 const native_types::Object::value_type& v2) {
229 return (v1.first == v2.first) && v1.second->IsEqual(v2.second.get());
230}
231
232bool operator==(const native_types::Object& obj1,
233 const native_types::Object& obj2) {
234 if (obj1.size() != obj2.size())
235 return false;
236
237 auto pair = std::mismatch(obj1.begin(), obj1.end(), obj2.begin(), obj_cmp);
238 return pair == std::make_pair(obj1.end(), obj2.end());
239}
240
Alex Vakulenko29e64442015-03-20 13:59:19 -0700241bool operator==(const native_types::Array& arr1,
242 const native_types::Array& arr2) {
243 if (arr1.size() != arr2.size())
244 return false;
245
246 using Type = const native_types::Array::value_type;
247 // Compare two array items.
248 auto arr_cmp = [](const Type& v1, const Type& v2) {
249 return v1->IsEqual(v2.get());
250 };
251 auto pair = std::mismatch(arr1.begin(), arr1.end(), arr2.begin(), arr_cmp);
252 return pair == std::make_pair(arr1.end(), arr2.end());
253}
254
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700255std::string ToString(const native_types::Object& obj) {
256 auto val = TypedValueToJson(obj, nullptr);
257 std::string str;
258 base::JSONWriter::Write(val.get(), &str);
259 return str;
260}
261
Alex Vakulenko29e64442015-03-20 13:59:19 -0700262std::string ToString(const native_types::Array& arr) {
263 auto val = TypedValueToJson(arr, nullptr);
264 std::string str;
265 base::JSONWriter::Write(val.get(), &str);
266 return str;
267}
268
Alex Vakulenkoa32d83a2014-09-19 15:05:24 -0700269chromeos::Any PropValueToDBusVariant(const PropValue* value) {
Alex Vakulenko72452612015-03-24 09:58:04 -0700270 if (value->GetType() == ValueType::Object)
271 return ObjectToDBusVariant(value->GetObject()->GetValue());
272
273 if (value->GetType() == ValueType::Array) {
274 const PropType* item_type =
275 value->GetPropType()->GetArray()->GetItemTypePtr();
276 return item_type->ConvertArrayToDBusVariant(value->GetArray()->GetValue());
277 }
278 return value->GetValueAsAny();
Anton Muhincfde8692014-11-25 03:36:59 +0400279}
280
281chromeos::VariantDictionary
282ObjectToDBusVariant(const native_types::Object& object) {
Alex Vakulenko576c9792014-09-22 16:49:45 -0700283 chromeos::VariantDictionary dict;
Anton Muhincfde8692014-11-25 03:36:59 +0400284 for (const auto& pair : object) {
Alex Vakulenkoa32d83a2014-09-19 15:05:24 -0700285 // Since we are inserting the elements from native_types::Object which is
286 // a map, the keys are already sorted. So use the "end()" position as a hint
287 // for dict.insert() so the destination map can optimize its insertion
288 // time.
289 chromeos::Any prop = PropValueToDBusVariant(pair.second.get());
290 dict.emplace_hint(dict.end(), pair.first, std::move(prop));
291 }
Anton Muhincfde8692014-11-25 03:36:59 +0400292 return dict;
Alex Vakulenkoa32d83a2014-09-19 15:05:24 -0700293}
294
Alex Vakulenko5ef75792015-03-19 15:50:44 -0700295std::unique_ptr<const PropValue> PropValueFromDBusVariant(
Alex Vakulenkoa32d83a2014-09-19 15:05:24 -0700296 const PropType* type,
297 const chromeos::Any& value,
298 chromeos::ErrorPtr* error) {
Alex Vakulenko5ef75792015-03-19 15:50:44 -0700299 std::unique_ptr<const PropValue> result;
Alex Vakulenko72452612015-03-24 09:58:04 -0700300 if (type->GetType() == ValueType::Array) {
301 // Special case for array types.
302 // We expect the |value| to contain std::vector<T>, while PropValue must use
303 // native_types::Array instead. Do the conversion.
304 native_types::Array arr;
305 const PropType* item_type = type->GetArray()->GetItemTypePtr();
306 if (item_type->ConvertDBusVariantToArray(value, &arr, error))
307 result = type->CreateValue(arr, error);
308 } else if (type->GetType() == ValueType::Object) {
Anton Muhincfde8692014-11-25 03:36:59 +0400309 // Special case for object types.
310 // We expect the |value| to contain chromeos::VariantDictionary, while
311 // PropValue must use native_types::Object instead. Do the conversion.
312 if (!value.IsTypeCompatible<chromeos::VariantDictionary>()) {
313 type->GenerateErrorValueTypeMismatch(error);
Alex Vakulenko72452612015-03-24 09:58:04 -0700314 return result;
Anton Muhincfde8692014-11-25 03:36:59 +0400315 }
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700316 CHECK(nullptr != type->GetObject()->GetObjectSchemaPtr())
Anton Muhincfde8692014-11-25 03:36:59 +0400317 << "An object type must have a schema defined for it";
318 native_types::Object obj;
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700319 if (!ObjectFromDBusVariant(type->GetObject()->GetObjectSchemaPtr(),
Anton Muhincfde8692014-11-25 03:36:59 +0400320 value.Get<chromeos::VariantDictionary>(),
321 &obj,
Alex Vakulenko72452612015-03-24 09:58:04 -0700322 error)) {
323 return result;
324 }
Anton Muhincfde8692014-11-25 03:36:59 +0400325
326 result = type->CreateValue(std::move(obj), error);
327 } else {
Alex Vakulenkoa32d83a2014-09-19 15:05:24 -0700328 result = type->CreateValue(value, error);
Alex Vakulenkoa32d83a2014-09-19 15:05:24 -0700329 }
330
Anton Muhincfde8692014-11-25 03:36:59 +0400331 return result;
332}
333
334bool ObjectFromDBusVariant(const ObjectSchema* object_schema,
335 const chromeos::VariantDictionary& dict,
336 native_types::Object* obj,
337 chromeos::ErrorPtr* error) {
Alex Vakulenkoa32d83a2014-09-19 15:05:24 -0700338 std::set<std::string> keys_processed;
Alex Vakulenko3e864c02015-03-24 10:19:32 -0700339 obj->clear();
Alex Vakulenkoa32d83a2014-09-19 15:05:24 -0700340 // First go over all object parameters defined by type's object schema and
341 // extract the corresponding parameters from the source dictionary.
Anton Muhincfde8692014-11-25 03:36:59 +0400342 for (const auto& pair : object_schema->GetProps()) {
Alex Vakulenkoa32d83a2014-09-19 15:05:24 -0700343 const PropValue* def_value = pair.second->GetDefaultValue();
344 auto it = dict.find(pair.first);
345 if (it != dict.end()) {
346 const PropType* prop_type = pair.second.get();
347 CHECK(prop_type) << "Value property type must be available";
348 auto prop_value = PropValueFromDBusVariant(prop_type, it->second, error);
349 if (!prop_value) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800350 chromeos::Error::AddToPrintf(error, FROM_HERE,
351 errors::commands::kDomain,
Alex Vakulenkoa32d83a2014-09-19 15:05:24 -0700352 errors::commands::kInvalidPropValue,
353 "Invalid value for property '%s'",
354 pair.first.c_str());
Anton Muhincfde8692014-11-25 03:36:59 +0400355 return false;
Alex Vakulenkoa32d83a2014-09-19 15:05:24 -0700356 }
Anton Muhincfde8692014-11-25 03:36:59 +0400357 obj->emplace_hint(obj->end(), pair.first, std::move(prop_value));
Alex Vakulenkoa32d83a2014-09-19 15:05:24 -0700358 } else if (def_value) {
Alex Vakulenko5ef75792015-03-19 15:50:44 -0700359 obj->emplace_hint(obj->end(), pair.first, def_value->Clone());
Alex Vakulenkoa32d83a2014-09-19 15:05:24 -0700360 } else {
361 ErrorMissingProperty(error, pair.first.c_str());
Anton Muhincfde8692014-11-25 03:36:59 +0400362 return false;
Alex Vakulenkoa32d83a2014-09-19 15:05:24 -0700363 }
364 keys_processed.insert(pair.first);
365 }
366
367 // Make sure that we processed all the necessary properties and there weren't
368 // any extra (unknown) ones specified, unless the schema allows them.
Anton Muhincfde8692014-11-25 03:36:59 +0400369 if (!object_schema->GetExtraPropertiesAllowed()) {
Alex Vakulenkoa32d83a2014-09-19 15:05:24 -0700370 for (const auto& pair : dict) {
371 if (keys_processed.find(pair.first) == keys_processed.end()) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800372 chromeos::Error::AddToPrintf(error, FROM_HERE,
373 errors::commands::kDomain,
Alex Vakulenkoa32d83a2014-09-19 15:05:24 -0700374 errors::commands::kUnknownProperty,
375 "Unrecognized property '%s'",
376 pair.first.c_str());
Anton Muhincfde8692014-11-25 03:36:59 +0400377 return false;
Alex Vakulenkoa32d83a2014-09-19 15:05:24 -0700378 }
379 }
380 }
381
Anton Muhincfde8692014-11-25 03:36:59 +0400382 return true;
Alex Vakulenkoa32d83a2014-09-19 15:05:24 -0700383}
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700384
385} // namespace buffet