blob: b70beffac7978136bd2df2160e65c584830c8930 [file] [log] [blame]
Vitaly Buka4615e0d2015-10-14 15:35:12 -07001// Copyright 2015 The Weave Authors. All rights reserved.
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -07002// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Stefan Sauer2d16dfa2015-09-25 17:08:35 +02005#include "src/commands/object_schema.h"
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -07006
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>
Vitaly Bukae49d7712015-08-06 01:35:11 -070012#include <weave/enum_to_string.h>
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -070013
Stefan Sauer2d16dfa2015-09-25 17:08:35 +020014#include "src/commands/prop_types.h"
15#include "src/commands/prop_values.h"
16#include "src/commands/schema_constants.h"
17#include "src/string_utils.h"
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -070018
Vitaly Bukab6f015a2015-07-09 14:59:23 -070019namespace weave {
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -070020
Alex Vakulenkod94656e2015-03-18 09:54:37 -070021namespace {
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -070022
Alex Vakulenkod94656e2015-03-18 09:54:37 -070023// Helper function for to create a PropType based on type string.
24// Generates an error if the string identifies an unknown type.
25std::unique_ptr<PropType> CreatePropType(const std::string& type_name,
Vitaly Buka0801a1f2015-08-14 10:03:46 -070026 ErrorPtr* error) {
Vitaly Buka24d6fd52015-08-13 23:22:48 -070027 auto parts = SplitAtFirst(type_name, ".", false);
28 const std::string& primary_type = parts.first;
29 const std::string& array_type = parts.second;
30
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -070031 std::unique_ptr<PropType> prop;
32 ValueType type;
Alex Vakulenko29e64442015-03-20 13:59:19 -070033 if (PropType::GetTypeFromTypeString(primary_type, &type)) {
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -070034 prop = PropType::Create(type);
Alex Vakulenko29e64442015-03-20 13:59:19 -070035 if (prop && type == ValueType::Array && !array_type.empty()) {
36 auto items_type = CreatePropType(array_type, error);
37 if (items_type) {
38 prop->GetArray()->SetItemType(std::move(items_type));
39 } else {
40 prop.reset();
41 return prop;
42 }
43 }
44 }
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -070045 if (!prop) {
Vitaly Buka0801a1f2015-08-14 10:03:46 -070046 Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
47 errors::commands::kUnknownType, "Unknown type %s",
48 type_name.c_str());
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -070049 }
50 return prop;
51}
52
Alex Vakulenkod94656e2015-03-18 09:54:37 -070053// Generates "no_type_info" error.
Vitaly Buka0801a1f2015-08-14 10:03:46 -070054void ErrorInvalidTypeInfo(ErrorPtr* error) {
55 Error::AddTo(error, FROM_HERE, errors::commands::kDomain,
56 errors::commands::kNoTypeInfo,
57 "Unable to determine parameter type");
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -070058}
59
Alex Vakulenkod94656e2015-03-18 09:54:37 -070060// Helper function for PropFromJson to handle the case of parameter being
61// defined as a JSON string like this:
62// "prop":"..."
Vitaly Bukaa647c852015-07-06 14:51:01 -070063std::unique_ptr<PropType> PropFromJsonString(const base::Value& value,
64 const PropType* base_schema,
Vitaly Buka0801a1f2015-08-14 10:03:46 -070065 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()) {
Vitaly Bukaa647c852015-07-06 14:51:01 -070089 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;
101 case base::Value::TYPE_DICTIONARY:
102 type_name = PropType::GetTypeStringFromType(ValueType::Object);
103 break;
104 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 =
111 DetectArrayType(first_element_list, nullptr, false);
112 if (child_type.empty()) {
113 type_name.clear();
114 } else {
115 type_name += '.' + child_type;
116 }
Alex Vakulenko29e64442015-03-20 13:59:19 -0700117 }
118 }
Vitaly Bukaa647c852015-07-06 14:51:01 -0700119 break;
Alex Vakulenko29e64442015-03-20 13:59:19 -0700120 }
Vitaly Bukaa647c852015-07-06 14:51:01 -0700121 default:
122 // The rest are unsupported.
123 break;
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700124 }
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":[...]
Vitaly Bukaa647c852015-07-06 14:51:01 -0700133std::unique_ptr<PropType> PropFromJsonArray(const base::Value& value,
134 const PropType* base_schema,
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700135 ErrorPtr* error) {
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700136 std::unique_ptr<PropType> prop;
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700137 const base::ListValue* list = nullptr;
138 CHECK(value.GetAsList(&list)) << "Unable to get array value";
Alex Vakulenko29e64442015-03-20 13:59:19 -0700139 std::string type_name = DetectArrayType(list, base_schema, true);
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700140 if (type_name.empty()) {
141 ErrorInvalidTypeInfo(error);
142 return prop;
143 }
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700144 base::DictionaryValue array_object;
145 array_object.SetWithoutPathExpansion(commands::attributes::kOneOf_Enum,
146 list->DeepCopy());
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700147 prop = CreatePropType(type_name, error);
148 if (prop && !prop->FromJson(&array_object, base_schema, error))
149 prop.reset();
150
151 return prop;
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700152}
153
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700154// Detects a type based on JSON object definition of type. Looks at various
155// members such as minimum/maximum constraints, default and enum values to
156// try to deduce the underlying type of the element. Returns the string name of
157// the type detected or empty string is type detection failed.
158std::string DetectObjectType(const base::DictionaryValue* dict,
159 const PropType* base_schema) {
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700160 bool has_min_max = dict->HasKey(commands::attributes::kNumeric_Min) ||
161 dict->HasKey(commands::attributes::kNumeric_Max);
162
163 // Here we are trying to "detect the type and read in the object based on
164 // the deduced type". Later, we'll verify that this detected type matches
165 // the expectation of the base schema, if applicable, to make sure we are not
166 // changing the expected type. This makes the vendor-side (re)definition of
167 // standard and custom commands behave exactly the same.
168 // The only problem with this approach was the double-vs-int types.
169 // If the type is meant to be a double we want to allow its definition as
170 // "min:0, max:0" instead of just forcing it to be only "min:0.0, max:0.0".
171 // If we have "minimum" or "maximum", and we have a Double schema object,
172 // treat this object as a Double (even if both min and max are integers).
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700173 if (has_min_max && base_schema && base_schema->GetType() == ValueType::Double)
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700174 return PropType::GetTypeStringFromType(ValueType::Double);
175
176 // If we have at least one "minimum" or "maximum" that is Double,
177 // it's a Double.
178 const base::Value* value = nullptr;
179 if (dict->Get(commands::attributes::kNumeric_Min, &value) &&
180 value->IsType(base::Value::TYPE_DOUBLE))
181 return PropType::GetTypeStringFromType(ValueType::Double);
182 if (dict->Get(commands::attributes::kNumeric_Max, &value) &&
183 value->IsType(base::Value::TYPE_DOUBLE))
184 return PropType::GetTypeStringFromType(ValueType::Double);
185
186 // If we have "minimum" or "maximum", it's an Integer.
187 if (has_min_max)
188 return PropType::GetTypeStringFromType(ValueType::Int);
189
190 // If we have "minLength" or "maxLength", it's a String.
191 if (dict->HasKey(commands::attributes::kString_MinLength) ||
192 dict->HasKey(commands::attributes::kString_MaxLength))
193 return PropType::GetTypeStringFromType(ValueType::String);
194
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700195 // If we have "properties", it's an object.
196 if (dict->HasKey(commands::attributes::kObject_Properties))
197 return PropType::GetTypeStringFromType(ValueType::Object);
198
Alex Vakulenko29e64442015-03-20 13:59:19 -0700199 // If we have "items", it's an array.
200 if (dict->HasKey(commands::attributes::kItems))
201 return PropType::GetTypeStringFromType(ValueType::Array);
202
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700203 // If we have "enum", it's an array. Detect type from array elements.
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700204 const base::ListValue* list = nullptr;
Vitaly Bukaa647c852015-07-06 14:51:01 -0700205 if (dict->GetListWithoutPathExpansion(commands::attributes::kOneOf_Enum,
206 &list))
Alex Vakulenko29e64442015-03-20 13:59:19 -0700207 return DetectArrayType(list, base_schema, true);
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700208
Alex Vakulenko2a17a532015-02-24 14:51:13 -0800209 // If we have "default", try to use it for type detection.
210 if (dict->Get(commands::attributes::kDefault, &value)) {
211 if (value->IsType(base::Value::TYPE_DOUBLE))
212 return PropType::GetTypeStringFromType(ValueType::Double);
213 if (value->IsType(base::Value::TYPE_INTEGER))
214 return PropType::GetTypeStringFromType(ValueType::Int);
215 if (value->IsType(base::Value::TYPE_BOOLEAN))
216 return PropType::GetTypeStringFromType(ValueType::Boolean);
217 if (value->IsType(base::Value::TYPE_STRING))
218 return PropType::GetTypeStringFromType(ValueType::String);
Alex Vakulenko29e64442015-03-20 13:59:19 -0700219 if (value->IsType(base::Value::TYPE_LIST)) {
220 CHECK(value->GetAsList(&list)) << "List value expected";
221 std::string child_type = DetectArrayType(list, base_schema, false);
222 if (!child_type.empty()) {
223 return PropType::GetTypeStringFromType(ValueType::Array) + '.' +
224 child_type;
225 }
226 }
Alex Vakulenko2a17a532015-02-24 14:51:13 -0800227 }
228
Alex Vakulenko29e64442015-03-20 13:59:19 -0700229 return std::string{};
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700230}
231
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700232// Helper function for PropFromJson to handle the case of parameter being
233// defined as a JSON object like this:
234// "prop":{...}
Vitaly Bukaa647c852015-07-06 14:51:01 -0700235std::unique_ptr<PropType> PropFromJsonObject(const base::Value& value,
236 const PropType* base_schema,
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700237 ErrorPtr* error) {
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700238 std::unique_ptr<PropType> prop;
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700239 const base::DictionaryValue* dict = nullptr;
240 CHECK(value.GetAsDictionary(&dict)) << "Unable to get dictionary value";
241 std::string type_name;
242 if (dict->HasKey(commands::attributes::kType)) {
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700243 if (!dict->GetString(commands::attributes::kType, &type_name)) {
244 ErrorInvalidTypeInfo(error);
245 return prop;
246 }
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700247 } else {
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700248 type_name = DetectObjectType(dict, base_schema);
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700249 }
250 if (type_name.empty()) {
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700251 if (!base_schema) {
252 ErrorInvalidTypeInfo(error);
253 return prop;
254 }
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700255 type_name = base_schema->GetTypeAsString();
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700256 }
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700257 prop = CreatePropType(type_name, error);
258 if (prop && !prop->FromJson(dict, base_schema, error))
259 prop.reset();
260
261 return prop;
262}
263
Vitaly Bukae49d7712015-08-06 01:35:11 -0700264const EnumToStringMap<base::Value::Type>::Map kMap[] = {
265 {base::Value::TYPE_NULL, "Null"},
266 {base::Value::TYPE_BOOLEAN, "Boolean"},
267 {base::Value::TYPE_INTEGER, "Integer"},
268 {base::Value::TYPE_DOUBLE, "Double"},
269 {base::Value::TYPE_STRING, "String"},
270 {base::Value::TYPE_BINARY, "Binary"},
271 {base::Value::TYPE_DICTIONARY, "Object"},
272 {base::Value::TYPE_LIST, "Array"},
273};
274
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700275} // anonymous namespace
276
Vitaly Bukae49d7712015-08-06 01:35:11 -0700277template <>
Vitaly Buka4ebd3292015-09-23 18:04:17 -0700278EnumToStringMap<base::Value::Type>::EnumToStringMap() : EnumToStringMap(kMap) {}
Vitaly Bukae49d7712015-08-06 01:35:11 -0700279
Vitaly Buka4ebd3292015-09-23 18:04:17 -0700280ObjectSchema::ObjectSchema() {}
281ObjectSchema::~ObjectSchema() {}
Alex Vakulenko5ef75792015-03-19 15:50:44 -0700282
283std::unique_ptr<ObjectSchema> ObjectSchema::Clone() const {
284 std::unique_ptr<ObjectSchema> cloned{new ObjectSchema};
285 for (const auto& pair : properties_) {
286 cloned->properties_.emplace(pair.first, pair.second->Clone());
287 }
288 cloned->extra_properties_allowed_ = extra_properties_allowed_;
289 return cloned;
290}
291
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700292void ObjectSchema::AddProp(const std::string& name,
Alex Vakulenko5ef75792015-03-19 15:50:44 -0700293 std::unique_ptr<PropType> prop) {
294 // Not using emplace() here to make sure we override existing properties.
295 properties_[name] = std::move(prop);
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700296}
297
298const PropType* ObjectSchema::GetProp(const std::string& name) const {
299 auto p = properties_.find(name);
300 return p != properties_.end() ? p->second.get() : nullptr;
301}
302
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700303bool ObjectSchema::MarkPropRequired(const std::string& name, ErrorPtr* error) {
Alex Vakulenko7e8df462015-07-07 10:59:20 -0700304 auto p = properties_.find(name);
305 if (p == properties_.end()) {
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700306 Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
307 errors::commands::kUnknownProperty,
308 "Unknown property '%s'", name.c_str());
Alex Vakulenko7e8df462015-07-07 10:59:20 -0700309 return false;
310 }
311 p->second->MakeRequired(true);
312 return true;
313}
Vitaly Buka6942e1f2015-07-28 15:33:55 -0700314
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700315std::unique_ptr<base::DictionaryValue> ObjectSchema::ToJson(
Vitaly Bukaa647c852015-07-06 14:51:01 -0700316 bool full_schema,
Vitaly Buka6942e1f2015-07-28 15:33:55 -0700317 bool in_command_def) const {
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700318 std::unique_ptr<base::DictionaryValue> value(new base::DictionaryValue);
319 for (const auto& pair : properties_) {
Vitaly Buka6942e1f2015-07-28 15:33:55 -0700320 auto prop_def = pair.second->ToJson(full_schema, in_command_def);
321 CHECK(prop_def);
322 value->SetWithoutPathExpansion(pair.first, prop_def.release());
Alex Vakulenko685312f2014-07-22 07:40:07 -0700323 }
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700324 return value;
325}
326
327bool ObjectSchema::FromJson(const base::DictionaryValue* value,
328 const ObjectSchema* object_schema,
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700329 ErrorPtr* error) {
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700330 Properties properties;
331 base::DictionaryValue::Iterator iter(*value);
332 while (!iter.IsAtEnd()) {
333 std::string name = iter.key();
334 const PropType* base_schema =
335 object_schema ? object_schema->GetProp(iter.key()) : nullptr;
336 auto prop_type = PropFromJson(iter.value(), base_schema, error);
337 if (prop_type) {
Alex Vakulenko5ef75792015-03-19 15:50:44 -0700338 properties.emplace(iter.key(), std::move(prop_type));
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700339 } else {
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700340 Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
341 errors::commands::kInvalidPropDef,
342 "Error in definition of property '%s'",
343 iter.key().c_str());
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700344 return false;
345 }
346 iter.Advance();
347 }
348 properties_ = std::move(properties);
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700349 return true;
350}
351
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700352std::unique_ptr<PropType> ObjectSchema::PropFromJson(
353 const base::Value& value,
354 const PropType* base_schema,
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700355 ErrorPtr* error) {
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700356 if (value.IsType(base::Value::TYPE_STRING)) {
357 // A string value is a short-hand object specification and provides
358 // the parameter type.
359 return PropFromJsonString(value, base_schema, error);
360 } else if (value.IsType(base::Value::TYPE_LIST)) {
361 // One of the enumerated types.
362 return PropFromJsonArray(value, base_schema, error);
363 } else if (value.IsType(base::Value::TYPE_DICTIONARY)) {
364 // Full parameter definition.
365 return PropFromJsonObject(value, base_schema, error);
366 }
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700367 Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
368 errors::commands::kUnknownType,
369 "Unexpected JSON value type: %s",
370 EnumToString(value.GetType()).c_str());
Vitaly Bukae527a642015-07-28 21:39:45 -0700371 return nullptr;
Alex Vakulenkod94656e2015-03-18 09:54:37 -0700372}
373
Alex Vakulenko5ef75792015-03-19 15:50:44 -0700374std::unique_ptr<ObjectSchema> ObjectSchema::Create() {
375 return std::unique_ptr<ObjectSchema>{new ObjectSchema};
376}
377
Vitaly Bukab6f015a2015-07-09 14:59:23 -0700378} // namespace weave