blob: b978ec9fb030bae474fe8a66b01113e09c010bac [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
10#include <base/json/json_writer.h>
11#include <base/logging.h>
12#include <base/values.h>
13
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
20void ObjectSchema::AddProp(const std::string& name,
21 std::shared_ptr<PropType> prop) {
22 properties_[name] = prop;
23}
24
25const PropType* ObjectSchema::GetProp(const std::string& name) const {
26 auto p = properties_.find(name);
27 return p != properties_.end() ? p->second.get() : nullptr;
28}
29
30std::unique_ptr<base::DictionaryValue> ObjectSchema::ToJson(
31 bool full_schema, ErrorPtr* error) const {
32 std::unique_ptr<base::DictionaryValue> value(new base::DictionaryValue);
33 for (const auto& pair : properties_) {
34 auto PropDef = pair.second->ToJson(full_schema, error);
35 if (!PropDef)
36 return std::unique_ptr<base::DictionaryValue>();
37 value->SetWithoutPathExpansion(pair.first, PropDef.release());
38 }
39 return value;
40}
41
42bool ObjectSchema::FromJson(const base::DictionaryValue* value,
Alex Vakulenko66ec2922014-06-17 15:30:22 -070043 const ObjectSchema* object_schema,
44 ErrorPtr* error) {
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -070045 Properties properties;
46 base::DictionaryValue::Iterator iter(*value);
47 while (!iter.IsAtEnd()) {
48 std::string name = iter.key();
Alex Vakulenko66ec2922014-06-17 15:30:22 -070049 const PropType* base_schema =
50 object_schema ? object_schema->GetProp(iter.key()) : nullptr;
51 if (!PropFromJson(iter.key(), iter.value(), base_schema, &properties,
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -070052 error))
53 return false;
54 iter.Advance();
55 }
56 properties_ = std::move(properties);
57 return true;
58}
59
60static std::unique_ptr<PropType> CreatePropType(const std::string& type_name,
61 const std::string& prop_name,
62 ErrorPtr* error) {
63 std::unique_ptr<PropType> prop;
64 ValueType type;
65 if (PropType::GetTypeFromTypeString(type_name, &type))
66 prop = PropType::Create(type);
67 if (!prop) {
68 Error::AddToPrintf(error, commands::errors::kDomain,
69 commands::errors::kUnknownType,
70 "Unknown type %s for parameter %s",
71 type_name.c_str(), prop_name.c_str());
72 }
73 return prop;
74}
75
76static bool ErrorInvalidTypeInfo(const std::string& prop_name,
77 ErrorPtr* error) {
78 Error::AddToPrintf(error, commands::errors::kDomain,
79 commands::errors::kNoTypeInfo,
80 "Unable to determine parameter type for %s",
81 prop_name.c_str());
82 return false;
83}
84
85bool ObjectSchema::PropFromJson(const std::string& prop_name,
86 const base::Value& value,
Alex Vakulenko66ec2922014-06-17 15:30:22 -070087 const PropType* base_schema,
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -070088 Properties* properties,
89 ErrorPtr* error) const {
90 if (value.IsType(base::Value::TYPE_STRING)) {
91 // A string value is a short-hand object specification and provides
92 // the parameter type.
Alex Vakulenko66ec2922014-06-17 15:30:22 -070093 return PropFromJsonString(prop_name, value, base_schema, properties,
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -070094 error);
95 } else if (value.IsType(base::Value::TYPE_LIST)) {
96 // One of the enumerated types.
Alex Vakulenko66ec2922014-06-17 15:30:22 -070097 return PropFromJsonArray(prop_name, value, base_schema, properties,
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -070098 error);
99 } else if (value.IsType(base::Value::TYPE_DICTIONARY)) {
100 // Full parameter definition.
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700101 return PropFromJsonObject(prop_name, value, base_schema, properties,
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700102 error);
103 }
104 Error::AddToPrintf(error, commands::errors::kDomain,
105 commands::errors::kInvalidPropDef,
106 "Invalid parameter definition for %s", prop_name.c_str());
107 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,
114 ErrorPtr* error) const {
115 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 Vakulenko66ec2922014-06-17 15:30:22 -0700121 if (!prop->FromJson(&empty, base_schema, error))
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700122 return false;
123 properties->insert(std::make_pair(prop_name, std::move(prop)));
124 return true;
125}
126
127static std::string DetectArrayType(const base::ListValue* list,
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700128 const PropType* base_schema) {
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700129 std::string type_name;
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700130 if (base_schema) {
131 type_name = base_schema->GetTypeAsString();
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700132 } else if (list->GetSize() > 0) {
133 const base::Value* first_element = nullptr;
134 if (list->Get(0, &first_element)) {
135 switch (first_element->GetType()) {
136 case base::Value::TYPE_BOOLEAN:
137 type_name = PropType::GetTypeStringFromType(ValueType::Boolean);
138 break;
139 case base::Value::TYPE_INTEGER:
140 type_name = PropType::GetTypeStringFromType(ValueType::Int);
141 break;
142 case base::Value::TYPE_DOUBLE:
143 type_name = PropType::GetTypeStringFromType(ValueType::Double);
144 break;
145 case base::Value::TYPE_STRING:
146 type_name = PropType::GetTypeStringFromType(ValueType::String);
147 break;
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700148 case base::Value::TYPE_DICTIONARY:
149 type_name = PropType::GetTypeStringFromType(ValueType::Object);
150 break;
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700151 default:
152 // The rest are unsupported.
153 break;
154 }
155 }
156 }
157 return type_name;
158}
159
160bool ObjectSchema::PropFromJsonArray(const std::string& prop_name,
161 const base::Value& value,
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700162 const PropType* base_schema,
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700163 Properties* properties,
164 ErrorPtr* error) const {
165 const base::ListValue* list = nullptr;
166 CHECK(value.GetAsList(&list)) << "Unable to get array value";
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700167 std::string type_name = DetectArrayType(list, base_schema);
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700168 if (type_name.empty())
169 return ErrorInvalidTypeInfo(prop_name, error);
170 std::unique_ptr<PropType> prop = CreatePropType(type_name, prop_name, error);
171 if (!prop)
172 return false;
173 base::DictionaryValue array_object;
174 array_object.SetWithoutPathExpansion(commands::attributes::kOneOf_Enum,
175 list->DeepCopy());
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700176 if (!prop->FromJson(&array_object, base_schema, error))
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700177 return false;
178 properties->insert(std::make_pair(prop_name, std::move(prop)));
179 return true;
180}
181
182static std::string DetectObjectType(const base::DictionaryValue* dict,
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700183 const PropType* base_schema) {
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700184 bool has_min_max = dict->HasKey(commands::attributes::kNumeric_Min) ||
185 dict->HasKey(commands::attributes::kNumeric_Max);
186
187 // Here we are trying to "detect the type and read in the object based on
188 // the deduced type". Later, we'll verify that this detected type matches
189 // the expectation of the base schema, if applicable, to make sure we are not
190 // changing the expected type. This makes the vendor-side (re)definition of
191 // standard and custom commands behave exactly the same.
192 // The only problem with this approach was the double-vs-int types.
193 // If the type is meant to be a double we want to allow its definition as
194 // "min:0, max:0" instead of just forcing it to be only "min:0.0, max:0.0".
195 // If we have "minimum" or "maximum", and we have a Double schema object,
196 // treat this object as a Double (even if both min and max are integers).
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700197 if (has_min_max && base_schema && base_schema->GetType() == ValueType::Double)
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700198 return PropType::GetTypeStringFromType(ValueType::Double);
199
200 // If we have at least one "minimum" or "maximum" that is Double,
201 // it's a Double.
202 const base::Value* value = nullptr;
203 if (dict->Get(commands::attributes::kNumeric_Min, &value) &&
204 value->IsType(base::Value::TYPE_DOUBLE))
205 return PropType::GetTypeStringFromType(ValueType::Double);
206 if (dict->Get(commands::attributes::kNumeric_Max, &value) &&
207 value->IsType(base::Value::TYPE_DOUBLE))
208 return PropType::GetTypeStringFromType(ValueType::Double);
209
210 // If we have "minimum" or "maximum", it's an Integer.
211 if (has_min_max)
212 return PropType::GetTypeStringFromType(ValueType::Int);
213
214 // If we have "minLength" or "maxLength", it's a String.
215 if (dict->HasKey(commands::attributes::kString_MinLength) ||
216 dict->HasKey(commands::attributes::kString_MaxLength))
217 return PropType::GetTypeStringFromType(ValueType::String);
218
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700219 // If we have "properties", it's an object.
220 if (dict->HasKey(commands::attributes::kObject_Properties))
221 return PropType::GetTypeStringFromType(ValueType::Object);
222
223 // If we have "enum", it's an array. Detect type from array elements.
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700224 const base::ListValue* list = nullptr;
225 if (dict->GetListWithoutPathExpansion(
226 commands::attributes::kOneOf_Enum, &list))
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700227 return DetectArrayType(list, base_schema);
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700228
229 return std::string();
230}
231
232bool ObjectSchema::PropFromJsonObject(const std::string& prop_name,
233 const base::Value& value,
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700234 const PropType* base_schema,
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700235 Properties* properties,
236 ErrorPtr* error) const {
237 const base::DictionaryValue* dict = nullptr;
238 CHECK(value.GetAsDictionary(&dict)) << "Unable to get dictionary value";
239 std::string type_name;
240 if (dict->HasKey(commands::attributes::kType)) {
241 if (!dict->GetString(commands::attributes::kType, &type_name))
242 return ErrorInvalidTypeInfo(prop_name, error);
243 } else {
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700244 type_name = DetectObjectType(dict, base_schema);
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700245 }
246 if (type_name.empty()) {
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700247 if (!base_schema)
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700248 return ErrorInvalidTypeInfo(prop_name, error);
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700249 type_name = base_schema->GetTypeAsString();
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700250 }
251 std::unique_ptr<PropType> prop = CreatePropType(type_name, prop_name, error);
Alex Vakulenko66ec2922014-06-17 15:30:22 -0700252 if (!prop || !prop->FromJson(dict, base_schema, error))
Alex Vakulenkoe439a0f2014-05-21 12:26:47 -0700253 return false;
254 properties->insert(std::make_pair(prop_name, std::move(prop)));
255 return true;
256}
257
258} // namespace buffet