blob: 8f64eecb8cc32622251ff4621f8d7dd114887511 [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>
Alex Vakulenko576c9792014-09-22 16:49:45 -070012#include <chromeos/variant_dictionary.h>
Alex Vakulenko66ec2922014-06-17 15:30:22 -070013
14#include "buffet/commands/object_schema.h"
15#include "buffet/commands/prop_types.h"
16#include "buffet/commands/prop_values.h"
17
18namespace buffet {
19namespace {
20// Helper function to report "type mismatch" errors when parsing JSON.
21void ReportJsonTypeMismatch(const base::Value* value_in,
22 const std::string& expected_type,
Alex Vakulenko5f472062014-08-14 17:54:04 -070023 chromeos::ErrorPtr* error) {
Alex Vakulenko66ec2922014-06-17 15:30:22 -070024 std::string value_as_string;
25 base::JSONWriter::Write(value_in, &value_as_string);
Alex Vakulenko5f472062014-08-14 17:54:04 -070026 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 Vakulenko66ec2922014-06-17 15:30:22 -070030}
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.
35template<typename T>
Alex Vakulenko5f472062014-08-14 17:54:04 -070036bool ReportUnexpectedJson(const base::Value* value_in, T*,
37 chromeos::ErrorPtr* error) {
Alex Vakulenko66ec2922014-06-17 15:30:22 -070038 ReportJsonTypeMismatch(value_in,
39 PropType::GetTypeStringFromType(GetValueType<T>()),
40 error);
41 return false;
42}
43
Alex Vakulenko5f472062014-08-14 17:54:04 -070044bool 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 Vakulenko66ec2922014-06-17 15:30:22 -070048 return false;
49}
50} // namespace
51
52// Specializations of TypedValueToJson<T>() for supported C++ types.
Alex Vakulenko5f472062014-08-14 17:54:04 -070053std::unique_ptr<base::Value> TypedValueToJson(bool value,
54 chromeos::ErrorPtr* error) {
Ben Chand0ce0d72014-09-04 21:59:24 -070055 return std::unique_ptr<base::Value>(new base::FundamentalValue(value));
Alex Vakulenko66ec2922014-06-17 15:30:22 -070056}
57
Alex Vakulenko5f472062014-08-14 17:54:04 -070058std::unique_ptr<base::Value> TypedValueToJson(int value,
59 chromeos::ErrorPtr* error) {
Ben Chand0ce0d72014-09-04 21:59:24 -070060 return std::unique_ptr<base::Value>(new base::FundamentalValue(value));
Alex Vakulenko66ec2922014-06-17 15:30:22 -070061}
62
Alex Vakulenko5f472062014-08-14 17:54:04 -070063std::unique_ptr<base::Value> TypedValueToJson(double value,
64 chromeos::ErrorPtr* error) {
Ben Chand0ce0d72014-09-04 21:59:24 -070065 return std::unique_ptr<base::Value>(new base::FundamentalValue(value));
Alex Vakulenko66ec2922014-06-17 15:30:22 -070066}
67
68std::unique_ptr<base::Value> TypedValueToJson(const std::string& value,
Alex Vakulenko5f472062014-08-14 17:54:04 -070069 chromeos::ErrorPtr* error) {
Ben Chand0ce0d72014-09-04 21:59:24 -070070 return std::unique_ptr<base::Value>(new base::StringValue(value));
Alex Vakulenko66ec2922014-06-17 15:30:22 -070071}
72
73std::unique_ptr<base::Value> TypedValueToJson(const native_types::Object& value,
Alex Vakulenko5f472062014-08-14 17:54:04 -070074 chromeos::ErrorPtr* error) {
Alex Vakulenko66ec2922014-06-17 15:30:22 -070075 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
85bool TypedValueFromJson(const base::Value* value_in,
86 const ObjectSchema* object_schema,
Alex Vakulenko5f472062014-08-14 17:54:04 -070087 bool* value_out, chromeos::ErrorPtr* error) {
Alex Vakulenko66ec2922014-06-17 15:30:22 -070088 return value_in->GetAsBoolean(value_out) ||
89 ReportUnexpectedJson(value_in, value_out, error);
90}
91
92bool TypedValueFromJson(const base::Value* value_in,
93 const ObjectSchema* object_schema,
Alex Vakulenko5f472062014-08-14 17:54:04 -070094 int* value_out, chromeos::ErrorPtr* error) {
Alex Vakulenko66ec2922014-06-17 15:30:22 -070095 return value_in->GetAsInteger(value_out) ||
96 ReportUnexpectedJson(value_in, value_out, error);
97}
98
99bool TypedValueFromJson(const base::Value* value_in,
100 const ObjectSchema* object_schema,
Alex Vakulenko5f472062014-08-14 17:54:04 -0700101 double* value_out, chromeos::ErrorPtr* error) {
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700102 return value_in->GetAsDouble(value_out) ||
103 ReportUnexpectedJson(value_in, value_out, error);
104}
105
106bool TypedValueFromJson(const base::Value* value_in,
107 const ObjectSchema* object_schema,
Alex Vakulenko5f472062014-08-14 17:54:04 -0700108 std::string* value_out, chromeos::ErrorPtr* error) {
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700109 return value_in->GetAsString(value_out) ||
110 ReportUnexpectedJson(value_in, value_out, error);
111}
112
113bool TypedValueFromJson(const base::Value* value_in,
114 const ObjectSchema* object_schema,
Alex Vakulenko5f472062014-08-14 17:54:04 -0700115 native_types::Object* value_out,
116 chromeos::ErrorPtr* error) {
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700117 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, &param_value))
130 << "Unable to get parameter";
Alex Vakulenkoa32d83a2014-09-19 15:05:24 -0700131 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 Vakulenko66ec2922014-06-17 15:30:22 -0700136 return false;
Alex Vakulenkoa32d83a2014-09-19 15:05:24 -0700137 }
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700138 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 Vakulenko5f472062014-08-14 17:54:04 -0700156 chromeos::Error::AddToPrintf(error, errors::commands::kDomain,
157 errors::commands::kUnknownProperty,
158 "Unrecognized parameter '%s'", key.c_str());
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700159 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 Vakulenko5f472062014-08-14 17:54:04 -0700169 chromeos::Error::AddToPrintf(error, errors::commands::kDomain,
170 errors::commands::kInvalidPropValue,
Alex Vakulenkoa32d83a2014-09-19 15:05:24 -0700171 "Invalid value for property '%s'",
Alex Vakulenko5f472062014-08-14 17:54:04 -0700172 pair.first.c_str());
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700173 return false;
174 }
175 }
176 return true;
177}
178
179// Compares two sets of key-value pairs from two Objects.
180static 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
185bool 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
194std::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 Vakulenkoa32d83a2014-09-19 15:05:24 -0700201chromeos::Any PropValueToDBusVariant(const PropValue* value) {
202 if (value->GetType() != ValueType::Object)
203 return value->GetValueAsAny();
204 // Special case for object types.
Alex Vakulenko576c9792014-09-22 16:49:45 -0700205 // Convert native_types::Object to chromeos::VariantDictionary
206 chromeos::VariantDictionary dict;
Alex Vakulenkoa32d83a2014-09-19 15:05:24 -0700207 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
218std::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 Vakulenko576c9792014-09-22 16:49:45 -0700231 // We expect the |value| to contain chromeos::VariantDictionary, while
Alex Vakulenkoa32d83a2014-09-19 15:05:24 -0700232 // PropValue must use native_types::Object instead. Do the conversion.
Alex Vakulenko576c9792014-09-22 16:49:45 -0700233 if (!value.IsTypeCompatible<chromeos::VariantDictionary>()) {
Alex Vakulenkoa32d83a2014-09-19 15:05:24 -0700234 type->GenerateErrorValueTypeMismatch(error);
235 return result;
236 }
Alex Vakulenko576c9792014-09-22 16:49:45 -0700237 const auto& dict = value.Get<chromeos::VariantDictionary>();
Alex Vakulenkoa32d83a2014-09-19 15:05:24 -0700238 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 Vakulenko66ec2922014-06-17 15:30:22 -0700288
289} // namespace buffet