blob: b71b46788eb504d9889c24629b6f996e5240c4e3 [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>
12
13#include "buffet/commands/prop_types.h"
14#include "buffet/commands/prop_values.h"
15#include "buffet/commands/schema_constants.h"
16
17namespace buffet {
18
19void ObjectSchema::AddProp(const std::string& name,
20 std::shared_ptr<PropType> prop) {
21 properties_[name] = prop;
22}
23
24const PropType* ObjectSchema::GetProp(const std::string& name) const {
25 auto p = properties_.find(name);
26 return p != properties_.end() ? p->second.get() : nullptr;
27}
28
29std::unique_ptr<base::DictionaryValue> ObjectSchema::ToJson(
Alex Vakulenko5f472062014-08-14 17:54:04 -070030 bool full_schema, chromeos::ErrorPtr* error) const {
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -070031 std::unique_ptr<base::DictionaryValue> value(new base::DictionaryValue);
32 for (const auto& pair : properties_) {
33 auto PropDef = pair.second->ToJson(full_schema, error);
34 if (!PropDef)
35 return std::unique_ptr<base::DictionaryValue>();
36 value->SetWithoutPathExpansion(pair.first, PropDef.release());
37 }
38 return value;
39}
40
41bool ObjectSchema::FromJson(const base::DictionaryValue* value,
Alex Vakulenko66ec2922014-06-17 15:30:22 -070042 const ObjectSchema* object_schema,
Alex Vakulenko5f472062014-08-14 17:54:04 -070043 chromeos::ErrorPtr* error) {
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -070044 Properties properties;
45 base::DictionaryValue::Iterator iter(*value);
46 while (!iter.IsAtEnd()) {
47 std::string name = iter.key();
Alex Vakulenko66ec2922014-06-17 15:30:22 -070048 const PropType* base_schema =
49 object_schema ? object_schema->GetProp(iter.key()) : nullptr;
50 if (!PropFromJson(iter.key(), iter.value(), base_schema, &properties,
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -070051 error))
52 return false;
53 iter.Advance();
54 }
55 properties_ = std::move(properties);
56 return true;
57}
58
59static std::unique_ptr<PropType> CreatePropType(const std::string& type_name,
60 const std::string& prop_name,
Alex Vakulenko5f472062014-08-14 17:54:04 -070061 chromeos::ErrorPtr* error) {
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -070062 std::unique_ptr<PropType> prop;
63 ValueType type;
64 if (PropType::GetTypeFromTypeString(type_name, &type))
65 prop = PropType::Create(type);
66 if (!prop) {
Alex Vakulenko5f472062014-08-14 17:54:04 -070067 chromeos::Error::AddToPrintf(error, errors::commands::kDomain,
68 errors::commands::kUnknownType,
69 "Unknown type %s for parameter %s",
70 type_name.c_str(), prop_name.c_str());
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -070071 }
72 return prop;
73}
74
75static bool ErrorInvalidTypeInfo(const std::string& prop_name,
Alex Vakulenko5f472062014-08-14 17:54:04 -070076 chromeos::ErrorPtr* error) {
77 chromeos::Error::AddToPrintf(error, errors::commands::kDomain,
78 errors::commands::kNoTypeInfo,
79 "Unable to determine parameter type for %s",
80 prop_name.c_str());
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -070081 return false;
82}
83
84bool ObjectSchema::PropFromJson(const std::string& prop_name,
85 const base::Value& value,
Alex Vakulenko66ec2922014-06-17 15:30:22 -070086 const PropType* base_schema,
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -070087 Properties* properties,
Alex Vakulenko5f472062014-08-14 17:54:04 -070088 chromeos::ErrorPtr* error) const {
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -070089 if (value.IsType(base::Value::TYPE_STRING)) {
90 // A string value is a short-hand object specification and provides
91 // the parameter type.
Alex Vakulenko66ec2922014-06-17 15:30:22 -070092 return PropFromJsonString(prop_name, value, base_schema, properties,
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -070093 error);
94 } else if (value.IsType(base::Value::TYPE_LIST)) {
95 // One of the enumerated types.
Alex Vakulenko66ec2922014-06-17 15:30:22 -070096 return PropFromJsonArray(prop_name, value, base_schema, properties,
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -070097 error);
98 } else if (value.IsType(base::Value::TYPE_DICTIONARY)) {
99 // Full parameter definition.
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700100 return PropFromJsonObject(prop_name, value, base_schema, properties,
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700101 error);
102 }
Alex Vakulenko5f472062014-08-14 17:54:04 -0700103 chromeos::Error::AddToPrintf(error, errors::commands::kDomain,
104 errors::commands::kInvalidPropDef,
105 "Invalid parameter definition for %s",
106 prop_name.c_str());
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700107 return false;
108}
109
110bool ObjectSchema::PropFromJsonString(const std::string& prop_name,
111 const base::Value& value,
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700112 const PropType* base_schema,
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700113 Properties* properties,
Alex Vakulenko5f472062014-08-14 17:54:04 -0700114 chromeos::ErrorPtr* error) const {
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700115 std::string type_name;
116 CHECK(value.GetAsString(&type_name)) << "Unable to get string value";
117 std::unique_ptr<PropType> prop = CreatePropType(type_name, prop_name, error);
118 if (!prop)
119 return false;
120 base::DictionaryValue empty;
Alex Vakulenko685312f2014-07-22 07:40:07 -0700121 if (!prop->FromJson(&empty, base_schema, error)) {
Alex Vakulenko5f472062014-08-14 17:54:04 -0700122 chromeos::Error::AddToPrintf(error, errors::commands::kDomain,
123 errors::commands::kInvalidPropDef,
124 "Error in definition of property '%s'",
125 prop_name.c_str());
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700126 return false;
Alex Vakulenko685312f2014-07-22 07:40:07 -0700127 }
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700128 properties->insert(std::make_pair(prop_name, std::move(prop)));
129 return true;
130}
131
132static std::string DetectArrayType(const base::ListValue* list,
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700133 const PropType* base_schema) {
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700134 std::string type_name;
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700135 if (base_schema) {
136 type_name = base_schema->GetTypeAsString();
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700137 } else if (list->GetSize() > 0) {
138 const base::Value* first_element = nullptr;
139 if (list->Get(0, &first_element)) {
140 switch (first_element->GetType()) {
141 case base::Value::TYPE_BOOLEAN:
142 type_name = PropType::GetTypeStringFromType(ValueType::Boolean);
143 break;
144 case base::Value::TYPE_INTEGER:
145 type_name = PropType::GetTypeStringFromType(ValueType::Int);
146 break;
147 case base::Value::TYPE_DOUBLE:
148 type_name = PropType::GetTypeStringFromType(ValueType::Double);
149 break;
150 case base::Value::TYPE_STRING:
151 type_name = PropType::GetTypeStringFromType(ValueType::String);
152 break;
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700153 case base::Value::TYPE_DICTIONARY:
154 type_name = PropType::GetTypeStringFromType(ValueType::Object);
155 break;
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700156 default:
157 // The rest are unsupported.
158 break;
159 }
160 }
161 }
162 return type_name;
163}
164
165bool ObjectSchema::PropFromJsonArray(const std::string& prop_name,
166 const base::Value& value,
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700167 const PropType* base_schema,
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700168 Properties* properties,
Alex Vakulenko5f472062014-08-14 17:54:04 -0700169 chromeos::ErrorPtr* error) const {
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700170 const base::ListValue* list = nullptr;
171 CHECK(value.GetAsList(&list)) << "Unable to get array value";
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700172 std::string type_name = DetectArrayType(list, base_schema);
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700173 if (type_name.empty())
174 return ErrorInvalidTypeInfo(prop_name, error);
175 std::unique_ptr<PropType> prop = CreatePropType(type_name, prop_name, error);
176 if (!prop)
177 return false;
178 base::DictionaryValue array_object;
179 array_object.SetWithoutPathExpansion(commands::attributes::kOneOf_Enum,
180 list->DeepCopy());
Alex Vakulenko685312f2014-07-22 07:40:07 -0700181 if (!prop->FromJson(&array_object, base_schema, error)) {
Alex Vakulenko5f472062014-08-14 17:54:04 -0700182 chromeos::Error::AddToPrintf(error, errors::commands::kDomain,
183 errors::commands::kInvalidPropDef,
184 "Error in definition of property '%s'",
185 prop_name.c_str());
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700186 return false;
Alex Vakulenko685312f2014-07-22 07:40:07 -0700187 }
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700188 properties->insert(std::make_pair(prop_name, std::move(prop)));
189 return true;
190}
191
192static std::string DetectObjectType(const base::DictionaryValue* dict,
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700193 const PropType* base_schema) {
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700194 bool has_min_max = dict->HasKey(commands::attributes::kNumeric_Min) ||
195 dict->HasKey(commands::attributes::kNumeric_Max);
196
197 // Here we are trying to "detect the type and read in the object based on
198 // the deduced type". Later, we'll verify that this detected type matches
199 // the expectation of the base schema, if applicable, to make sure we are not
200 // changing the expected type. This makes the vendor-side (re)definition of
201 // standard and custom commands behave exactly the same.
202 // The only problem with this approach was the double-vs-int types.
203 // If the type is meant to be a double we want to allow its definition as
204 // "min:0, max:0" instead of just forcing it to be only "min:0.0, max:0.0".
205 // If we have "minimum" or "maximum", and we have a Double schema object,
206 // treat this object as a Double (even if both min and max are integers).
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700207 if (has_min_max && base_schema && base_schema->GetType() == ValueType::Double)
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700208 return PropType::GetTypeStringFromType(ValueType::Double);
209
210 // If we have at least one "minimum" or "maximum" that is Double,
211 // it's a Double.
212 const base::Value* value = nullptr;
213 if (dict->Get(commands::attributes::kNumeric_Min, &value) &&
214 value->IsType(base::Value::TYPE_DOUBLE))
215 return PropType::GetTypeStringFromType(ValueType::Double);
216 if (dict->Get(commands::attributes::kNumeric_Max, &value) &&
217 value->IsType(base::Value::TYPE_DOUBLE))
218 return PropType::GetTypeStringFromType(ValueType::Double);
219
220 // If we have "minimum" or "maximum", it's an Integer.
221 if (has_min_max)
222 return PropType::GetTypeStringFromType(ValueType::Int);
223
224 // If we have "minLength" or "maxLength", it's a String.
225 if (dict->HasKey(commands::attributes::kString_MinLength) ||
226 dict->HasKey(commands::attributes::kString_MaxLength))
227 return PropType::GetTypeStringFromType(ValueType::String);
228
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700229 // If we have "properties", it's an object.
230 if (dict->HasKey(commands::attributes::kObject_Properties))
231 return PropType::GetTypeStringFromType(ValueType::Object);
232
233 // If we have "enum", it's an array. Detect type from array elements.
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700234 const base::ListValue* list = nullptr;
235 if (dict->GetListWithoutPathExpansion(
236 commands::attributes::kOneOf_Enum, &list))
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700237 return DetectArrayType(list, base_schema);
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700238
239 return std::string();
240}
241
242bool ObjectSchema::PropFromJsonObject(const std::string& prop_name,
243 const base::Value& value,
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700244 const PropType* base_schema,
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700245 Properties* properties,
Alex Vakulenko5f472062014-08-14 17:54:04 -0700246 chromeos::ErrorPtr* error) const {
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700247 const base::DictionaryValue* dict = nullptr;
248 CHECK(value.GetAsDictionary(&dict)) << "Unable to get dictionary value";
249 std::string type_name;
250 if (dict->HasKey(commands::attributes::kType)) {
251 if (!dict->GetString(commands::attributes::kType, &type_name))
252 return ErrorInvalidTypeInfo(prop_name, error);
253 } else {
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700254 type_name = DetectObjectType(dict, base_schema);
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700255 }
256 if (type_name.empty()) {
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700257 if (!base_schema)
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700258 return ErrorInvalidTypeInfo(prop_name, error);
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700259 type_name = base_schema->GetTypeAsString();
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700260 }
261 std::unique_ptr<PropType> prop = CreatePropType(type_name, prop_name, error);
Alex Vakulenko685312f2014-07-22 07:40:07 -0700262 if (!prop)
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700263 return false;
Alex Vakulenko685312f2014-07-22 07:40:07 -0700264 if (!prop->FromJson(dict, base_schema, error)) {
Alex Vakulenko5f472062014-08-14 17:54:04 -0700265 chromeos::Error::AddToPrintf(error, errors::commands::kDomain,
266 errors::commands::kInvalidPropDef,
267 "Error in definition of property '%s'",
268 prop_name.c_str());
Alex Vakulenko685312f2014-07-22 07:40:07 -0700269 return false;
270 }
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700271 properties->insert(std::make_pair(prop_name, std::move(prop)));
272 return true;
273}
274
275} // namespace buffet