blob: 96fe9526d39bdedd430662fb5851657cbb44b208 [file] [log] [blame]
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -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/object_schema.h"
6
7#include <algorithm>
8#include <limits>
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -07009
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -070010#include <base/logging.h>
11#include <base/values.h>
Alex Vakulenkod94656e2015-03-18 09:54:37 -070012#include <chromeos/map_utils.h>
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -070013
14#include "buffet/commands/prop_types.h"
15#include "buffet/commands/prop_values.h"
16#include "buffet/commands/schema_constants.h"
17
18namespace buffet {
19
Alex Vakulenkod94656e2015-03-18 09:54:37 -070020namespace {
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -070021
Alex Vakulenkod94656e2015-03-18 09:54:37 -070022// Helper function for to create a PropType based on type string.
23// Generates an error if the string identifies an unknown type.
24std::unique_ptr<PropType> CreatePropType(const std::string& type_name,
25 chromeos::ErrorPtr* error) {
Alex Vakulenko29e64442015-03-20 13:59:19 -070026 std::string primary_type;
27 std::string array_type;
28 chromeos::string_utils::SplitAtFirst(type_name, ".", &primary_type,
29 &array_type, false);
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -070030 std::unique_ptr<PropType> prop;
31 ValueType type;
Alex Vakulenko29e64442015-03-20 13:59:19 -070032 if (PropType::GetTypeFromTypeString(primary_type, &type)) {
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -070033 prop = PropType::Create(type);
Alex Vakulenko29e64442015-03-20 13:59:19 -070034 if (prop && type == ValueType::Array && !array_type.empty()) {
35 auto items_type = CreatePropType(array_type, error);
36 if (items_type) {
37 prop->GetArray()->SetItemType(std::move(items_type));
38 } else {
39 prop.reset();
40 return prop;
41 }
42 }
43 }
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -070044 if (!prop) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -080045 chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
Alex Vakulenko5f472062014-08-14 17:54:04 -070046 errors::commands::kUnknownType,
Alex Vakulenkod94656e2015-03-18 09:54:37 -070047 "Unknown type %s", type_name.c_str());
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -070048 }
49 return prop;
50}
51
Alex Vakulenkod94656e2015-03-18 09:54:37 -070052// Generates "no_type_info" error.
53void ErrorInvalidTypeInfo(chromeos::ErrorPtr* error) {
54 chromeos::Error::AddTo(error, FROM_HERE, errors::commands::kDomain,
55 errors::commands::kNoTypeInfo,
56 "Unable to determine parameter type");
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -070057}
58
Alex Vakulenkod94656e2015-03-18 09:54:37 -070059// Helper function for PropFromJson to handle the case of parameter being
60// defined as a JSON string like this:
61// "prop":"..."
62std::unique_ptr<PropType> PropFromJsonString(
63 const base::Value& value,
64 const PropType* base_schema,
65 chromeos::ErrorPtr* error) {
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -070066 std::string type_name;
67 CHECK(value.GetAsString(&type_name)) << "Unable to get string value";
Alex Vakulenkod94656e2015-03-18 09:54:37 -070068 std::unique_ptr<PropType> prop = CreatePropType(type_name, error);
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -070069 base::DictionaryValue empty;
Alex Vakulenkod94656e2015-03-18 09:54:37 -070070 if (prop && !prop->FromJson(&empty, base_schema, error))
71 prop.reset();
72
73 return prop;
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -070074}
75
Alex Vakulenkod94656e2015-03-18 09:54:37 -070076// Detects a type based on JSON array. Inspects the first element of the array
77// to deduce the PropType from. Returns the string name of the type detected
78// or empty string is type detection failed.
79std::string DetectArrayType(const base::ListValue* list,
Alex Vakulenko29e64442015-03-20 13:59:19 -070080 const PropType* base_schema,
81 bool allow_arrays) {
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -070082 std::string type_name;
Alex Vakulenko66ec2922014-06-17 15:30:22 -070083 if (base_schema) {
84 type_name = base_schema->GetTypeAsString();
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -070085 } else if (list->GetSize() > 0) {
86 const base::Value* first_element = nullptr;
87 if (list->Get(0, &first_element)) {
88 switch (first_element->GetType()) {
89 case base::Value::TYPE_BOOLEAN:
90 type_name = PropType::GetTypeStringFromType(ValueType::Boolean);
91 break;
92 case base::Value::TYPE_INTEGER:
93 type_name = PropType::GetTypeStringFromType(ValueType::Int);
94 break;
95 case base::Value::TYPE_DOUBLE:
96 type_name = PropType::GetTypeStringFromType(ValueType::Double);
97 break;
98 case base::Value::TYPE_STRING:
99 type_name = PropType::GetTypeStringFromType(ValueType::String);
100 break;
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700101 case base::Value::TYPE_DICTIONARY:
102 type_name = PropType::GetTypeStringFromType(ValueType::Object);
103 break;
Alex Vakulenko29e64442015-03-20 13:59:19 -0700104 case base::Value::TYPE_LIST: {
105 if (allow_arrays) {
106 type_name = PropType::GetTypeStringFromType(ValueType::Array);
107 const base::ListValue* first_element_list = nullptr;
108 if (first_element->GetAsList(&first_element_list)) {
109 // We do not allow arrays of arrays.
110 auto child_type = DetectArrayType(first_element_list, nullptr,
111 false);
112 if (child_type.empty()) {
113 type_name.clear();
114 } else {
115 type_name += '.' + child_type;
116 }
117 }
118 }
119 break;
120 }
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700121 default:
122 // The rest are unsupported.
123 break;
124 }
125 }
126 }
127 return type_name;
128}
129
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700130// Helper function for PropFromJson to handle the case of parameter being
131// defined as a JSON array like this:
132// "prop":[...]
133std::unique_ptr<PropType> PropFromJsonArray(
134 const base::Value& value,
135 const PropType* base_schema,
136 chromeos::ErrorPtr* error) {
137 std::unique_ptr<PropType> prop;
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700138 const base::ListValue* list = nullptr;
139 CHECK(value.GetAsList(&list)) << "Unable to get array value";
Alex Vakulenko29e64442015-03-20 13:59:19 -0700140 std::string type_name = DetectArrayType(list, base_schema, true);
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700141 if (type_name.empty()) {
142 ErrorInvalidTypeInfo(error);
143 return prop;
144 }
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700145 base::DictionaryValue array_object;
146 array_object.SetWithoutPathExpansion(commands::attributes::kOneOf_Enum,
147 list->DeepCopy());
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700148 prop = CreatePropType(type_name, error);
149 if (prop && !prop->FromJson(&array_object, base_schema, error))
150 prop.reset();
151
152 return prop;
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700153}
154
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700155// Detects a type based on JSON object definition of type. Looks at various
156// members such as minimum/maximum constraints, default and enum values to
157// try to deduce the underlying type of the element. Returns the string name of
158// the type detected or empty string is type detection failed.
159std::string DetectObjectType(const base::DictionaryValue* dict,
160 const PropType* base_schema) {
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700161 bool has_min_max = dict->HasKey(commands::attributes::kNumeric_Min) ||
162 dict->HasKey(commands::attributes::kNumeric_Max);
163
164 // Here we are trying to "detect the type and read in the object based on
165 // the deduced type". Later, we'll verify that this detected type matches
166 // the expectation of the base schema, if applicable, to make sure we are not
167 // changing the expected type. This makes the vendor-side (re)definition of
168 // standard and custom commands behave exactly the same.
169 // The only problem with this approach was the double-vs-int types.
170 // If the type is meant to be a double we want to allow its definition as
171 // "min:0, max:0" instead of just forcing it to be only "min:0.0, max:0.0".
172 // If we have "minimum" or "maximum", and we have a Double schema object,
173 // treat this object as a Double (even if both min and max are integers).
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700174 if (has_min_max && base_schema && base_schema->GetType() == ValueType::Double)
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700175 return PropType::GetTypeStringFromType(ValueType::Double);
176
177 // If we have at least one "minimum" or "maximum" that is Double,
178 // it's a Double.
179 const base::Value* value = nullptr;
180 if (dict->Get(commands::attributes::kNumeric_Min, &value) &&
181 value->IsType(base::Value::TYPE_DOUBLE))
182 return PropType::GetTypeStringFromType(ValueType::Double);
183 if (dict->Get(commands::attributes::kNumeric_Max, &value) &&
184 value->IsType(base::Value::TYPE_DOUBLE))
185 return PropType::GetTypeStringFromType(ValueType::Double);
186
187 // If we have "minimum" or "maximum", it's an Integer.
188 if (has_min_max)
189 return PropType::GetTypeStringFromType(ValueType::Int);
190
191 // If we have "minLength" or "maxLength", it's a String.
192 if (dict->HasKey(commands::attributes::kString_MinLength) ||
193 dict->HasKey(commands::attributes::kString_MaxLength))
194 return PropType::GetTypeStringFromType(ValueType::String);
195
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700196 // If we have "properties", it's an object.
197 if (dict->HasKey(commands::attributes::kObject_Properties))
198 return PropType::GetTypeStringFromType(ValueType::Object);
199
Alex Vakulenko29e64442015-03-20 13:59:19 -0700200 // If we have "items", it's an array.
201 if (dict->HasKey(commands::attributes::kItems))
202 return PropType::GetTypeStringFromType(ValueType::Array);
203
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700204 // If we have "enum", it's an array. Detect type from array elements.
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700205 const base::ListValue* list = nullptr;
206 if (dict->GetListWithoutPathExpansion(
207 commands::attributes::kOneOf_Enum, &list))
Alex Vakulenko29e64442015-03-20 13:59:19 -0700208 return DetectArrayType(list, base_schema, true);
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700209
Alex Vakulenko2a17a532015-02-24 14:51:13 -0800210 // If we have "default", try to use it for type detection.
211 if (dict->Get(commands::attributes::kDefault, &value)) {
212 if (value->IsType(base::Value::TYPE_DOUBLE))
213 return PropType::GetTypeStringFromType(ValueType::Double);
214 if (value->IsType(base::Value::TYPE_INTEGER))
215 return PropType::GetTypeStringFromType(ValueType::Int);
216 if (value->IsType(base::Value::TYPE_BOOLEAN))
217 return PropType::GetTypeStringFromType(ValueType::Boolean);
218 if (value->IsType(base::Value::TYPE_STRING))
219 return PropType::GetTypeStringFromType(ValueType::String);
Alex Vakulenko29e64442015-03-20 13:59:19 -0700220 if (value->IsType(base::Value::TYPE_LIST)) {
221 CHECK(value->GetAsList(&list)) << "List value expected";
222 std::string child_type = DetectArrayType(list, base_schema, false);
223 if (!child_type.empty()) {
224 return PropType::GetTypeStringFromType(ValueType::Array) + '.' +
225 child_type;
226 }
227 }
Alex Vakulenko2a17a532015-02-24 14:51:13 -0800228 }
229
Alex Vakulenko29e64442015-03-20 13:59:19 -0700230 return std::string{};
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700231}
232
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700233// Helper function for PropFromJson to handle the case of parameter being
234// defined as a JSON object like this:
235// "prop":{...}
236std::unique_ptr<PropType> PropFromJsonObject(
237 const base::Value& value,
238 const PropType* base_schema,
239 chromeos::ErrorPtr* error) {
240 std::unique_ptr<PropType> prop;
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700241 const base::DictionaryValue* dict = nullptr;
242 CHECK(value.GetAsDictionary(&dict)) << "Unable to get dictionary value";
243 std::string type_name;
244 if (dict->HasKey(commands::attributes::kType)) {
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700245 if (!dict->GetString(commands::attributes::kType, &type_name)) {
246 ErrorInvalidTypeInfo(error);
247 return prop;
248 }
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700249 } else {
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700250 type_name = DetectObjectType(dict, base_schema);
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700251 }
252 if (type_name.empty()) {
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700253 if (!base_schema) {
254 ErrorInvalidTypeInfo(error);
255 return prop;
256 }
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700257 type_name = base_schema->GetTypeAsString();
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700258 }
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700259 prop = CreatePropType(type_name, error);
260 if (prop && !prop->FromJson(dict, base_schema, error))
261 prop.reset();
262
263 return prop;
264}
265
266} // anonymous namespace
267
Alex Vakulenko5ef75792015-03-19 15:50:44 -0700268ObjectSchema::ObjectSchema() {}
269ObjectSchema::~ObjectSchema() {}
270
271std::unique_ptr<ObjectSchema> ObjectSchema::Clone() const {
272 std::unique_ptr<ObjectSchema> cloned{new ObjectSchema};
273 for (const auto& pair : properties_) {
274 cloned->properties_.emplace(pair.first, pair.second->Clone());
275 }
276 cloned->extra_properties_allowed_ = extra_properties_allowed_;
277 return cloned;
278}
279
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700280void ObjectSchema::AddProp(const std::string& name,
Alex Vakulenko5ef75792015-03-19 15:50:44 -0700281 std::unique_ptr<PropType> prop) {
282 // Not using emplace() here to make sure we override existing properties.
283 properties_[name] = std::move(prop);
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700284}
285
286const PropType* ObjectSchema::GetProp(const std::string& name) const {
287 auto p = properties_.find(name);
288 return p != properties_.end() ? p->second.get() : nullptr;
289}
290
291std::unique_ptr<base::DictionaryValue> ObjectSchema::ToJson(
292 bool full_schema, chromeos::ErrorPtr* error) const {
293 std::unique_ptr<base::DictionaryValue> value(new base::DictionaryValue);
294 for (const auto& pair : properties_) {
295 auto PropDef = pair.second->ToJson(full_schema, error);
296 if (!PropDef)
297 return std::unique_ptr<base::DictionaryValue>();
298 value->SetWithoutPathExpansion(pair.first, PropDef.release());
Alex Vakulenko685312f2014-07-22 07:40:07 -0700299 }
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700300 return value;
301}
302
303bool ObjectSchema::FromJson(const base::DictionaryValue* value,
304 const ObjectSchema* object_schema,
305 chromeos::ErrorPtr* error) {
306 Properties properties;
307 base::DictionaryValue::Iterator iter(*value);
308 while (!iter.IsAtEnd()) {
309 std::string name = iter.key();
310 const PropType* base_schema =
311 object_schema ? object_schema->GetProp(iter.key()) : nullptr;
312 auto prop_type = PropFromJson(iter.value(), base_schema, error);
313 if (prop_type) {
Alex Vakulenko5ef75792015-03-19 15:50:44 -0700314 properties.emplace(iter.key(), std::move(prop_type));
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700315 } else {
316 chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
317 errors::commands::kInvalidPropDef,
318 "Error in definition of property '%s'",
319 iter.key().c_str());
320 return false;
321 }
322 iter.Advance();
323 }
324 properties_ = std::move(properties);
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700325 return true;
326}
327
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700328std::unique_ptr<PropType> ObjectSchema::PropFromJson(
329 const base::Value& value,
330 const PropType* base_schema,
331 chromeos::ErrorPtr* error) {
332 if (value.IsType(base::Value::TYPE_STRING)) {
333 // A string value is a short-hand object specification and provides
334 // the parameter type.
335 return PropFromJsonString(value, base_schema, error);
336 } else if (value.IsType(base::Value::TYPE_LIST)) {
337 // One of the enumerated types.
338 return PropFromJsonArray(value, base_schema, error);
339 } else if (value.IsType(base::Value::TYPE_DICTIONARY)) {
340 // Full parameter definition.
341 return PropFromJsonObject(value, base_schema, error);
342 }
343 static const std::map<base::Value::Type, const char*> type_names = {
344 {base::Value::TYPE_NULL, "Null"},
345 {base::Value::TYPE_BOOLEAN, "Boolean"},
346 {base::Value::TYPE_INTEGER, "Integer"},
347 {base::Value::TYPE_DOUBLE, "Double"},
348 {base::Value::TYPE_STRING, "String"},
349 {base::Value::TYPE_BINARY, "Binary"},
350 {base::Value::TYPE_DICTIONARY, "Object"},
351 {base::Value::TYPE_LIST, "Array"},
352 };
353 const char* type_name = chromeos::GetOrDefault(type_names, value.GetType(),
354 "<unknown>");
355 chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
356 errors::commands::kUnknownType,
357 "Unexpected JSON value type: %s", type_name);
358 return {};
359}
360
Alex Vakulenko5ef75792015-03-19 15:50:44 -0700361std::unique_ptr<ObjectSchema> ObjectSchema::Create() {
362 return std::unique_ptr<ObjectSchema>{new ObjectSchema};
363}
364
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700365} // namespace buffet