| // Copyright 2014 The Chromium OS Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "buffet/commands/object_schema.h" | 
 |  | 
 | #include <algorithm> | 
 | #include <limits> | 
 |  | 
 | #include <base/logging.h> | 
 | #include <base/values.h> | 
 |  | 
 | #include "buffet/commands/prop_types.h" | 
 | #include "buffet/commands/prop_values.h" | 
 | #include "buffet/commands/schema_constants.h" | 
 |  | 
 | namespace buffet { | 
 |  | 
 | void ObjectSchema::AddProp(const std::string& name, | 
 |                            std::shared_ptr<PropType> prop) { | 
 |   properties_[name] = prop; | 
 | } | 
 |  | 
 | const PropType* ObjectSchema::GetProp(const std::string& name) const { | 
 |   auto p = properties_.find(name); | 
 |   return p != properties_.end() ? p->second.get() : nullptr; | 
 | } | 
 |  | 
 | std::unique_ptr<base::DictionaryValue> ObjectSchema::ToJson( | 
 |     bool full_schema, chromeos::ErrorPtr* error) const { | 
 |   std::unique_ptr<base::DictionaryValue> value(new base::DictionaryValue); | 
 |   for (const auto& pair : properties_) { | 
 |     auto PropDef = pair.second->ToJson(full_schema, error); | 
 |     if (!PropDef) | 
 |       return std::unique_ptr<base::DictionaryValue>(); | 
 |     value->SetWithoutPathExpansion(pair.first, PropDef.release()); | 
 |   } | 
 |   return value; | 
 | } | 
 |  | 
 | bool ObjectSchema::FromJson(const base::DictionaryValue* value, | 
 |                             const ObjectSchema* object_schema, | 
 |                             chromeos::ErrorPtr* error) { | 
 |   Properties properties; | 
 |   base::DictionaryValue::Iterator iter(*value); | 
 |   while (!iter.IsAtEnd()) { | 
 |     std::string name = iter.key(); | 
 |     const PropType* base_schema = | 
 |         object_schema ? object_schema->GetProp(iter.key()) : nullptr; | 
 |     if (!PropFromJson(iter.key(), iter.value(), base_schema, &properties, | 
 |                       error)) | 
 |       return false; | 
 |     iter.Advance(); | 
 |   } | 
 |   properties_ = std::move(properties); | 
 |   return true; | 
 | } | 
 |  | 
 | static std::unique_ptr<PropType> CreatePropType(const std::string& type_name, | 
 |                                                 const std::string& prop_name, | 
 |                                                 chromeos::ErrorPtr* error) { | 
 |   std::unique_ptr<PropType> prop; | 
 |   ValueType type; | 
 |   if (PropType::GetTypeFromTypeString(type_name, &type)) | 
 |     prop = PropType::Create(type); | 
 |   if (!prop) { | 
 |     chromeos::Error::AddToPrintf(error, errors::commands::kDomain, | 
 |                                  errors::commands::kUnknownType, | 
 |                                  "Unknown type %s for parameter %s", | 
 |                                  type_name.c_str(), prop_name.c_str()); | 
 |   } | 
 |   return prop; | 
 | } | 
 |  | 
 | static bool ErrorInvalidTypeInfo(const std::string& prop_name, | 
 |                                  chromeos::ErrorPtr* error) { | 
 |   chromeos::Error::AddToPrintf(error, errors::commands::kDomain, | 
 |                                errors::commands::kNoTypeInfo, | 
 |                                "Unable to determine parameter type for %s", | 
 |                                prop_name.c_str()); | 
 |   return false; | 
 | } | 
 |  | 
 | bool ObjectSchema::PropFromJson(const std::string& prop_name, | 
 |                                 const base::Value& value, | 
 |                                 const PropType* base_schema, | 
 |                                 Properties* properties, | 
 |                                 chromeos::ErrorPtr* error) const { | 
 |   if (value.IsType(base::Value::TYPE_STRING)) { | 
 |     // A string value is a short-hand object specification and provides | 
 |     // the parameter type. | 
 |     return PropFromJsonString(prop_name, value, base_schema, properties, | 
 |                               error); | 
 |   } else if (value.IsType(base::Value::TYPE_LIST)) { | 
 |     // One of the enumerated types. | 
 |     return PropFromJsonArray(prop_name, value, base_schema, properties, | 
 |                              error); | 
 |   } else if (value.IsType(base::Value::TYPE_DICTIONARY)) { | 
 |     // Full parameter definition. | 
 |     return PropFromJsonObject(prop_name, value, base_schema, properties, | 
 |                               error); | 
 |   } | 
 |   chromeos::Error::AddToPrintf(error, errors::commands::kDomain, | 
 |                                errors::commands::kInvalidPropDef, | 
 |                                "Invalid parameter definition for %s", | 
 |                                prop_name.c_str()); | 
 |   return false; | 
 | } | 
 |  | 
 | bool ObjectSchema::PropFromJsonString(const std::string& prop_name, | 
 |                                       const base::Value& value, | 
 |                                       const PropType* base_schema, | 
 |                                       Properties* properties, | 
 |                                       chromeos::ErrorPtr* error) const { | 
 |   std::string type_name; | 
 |   CHECK(value.GetAsString(&type_name)) << "Unable to get string value"; | 
 |   std::unique_ptr<PropType> prop = CreatePropType(type_name, prop_name, error); | 
 |   if (!prop) | 
 |     return false; | 
 |   base::DictionaryValue empty; | 
 |   if (!prop->FromJson(&empty, base_schema, error)) { | 
 |     chromeos::Error::AddToPrintf(error, errors::commands::kDomain, | 
 |                                  errors::commands::kInvalidPropDef, | 
 |                                  "Error in definition of property '%s'", | 
 |                                  prop_name.c_str()); | 
 |     return false; | 
 |   } | 
 |   properties->insert(std::make_pair(prop_name, std::move(prop))); | 
 |   return true; | 
 | } | 
 |  | 
 | static std::string DetectArrayType(const base::ListValue* list, | 
 |                                    const PropType* base_schema) { | 
 |   std::string type_name; | 
 |   if (base_schema) { | 
 |     type_name = base_schema->GetTypeAsString(); | 
 |   } else if (list->GetSize() > 0) { | 
 |     const base::Value* first_element = nullptr; | 
 |     if (list->Get(0, &first_element)) { | 
 |       switch (first_element->GetType()) { | 
 |       case base::Value::TYPE_BOOLEAN: | 
 |         type_name = PropType::GetTypeStringFromType(ValueType::Boolean); | 
 |         break; | 
 |       case base::Value::TYPE_INTEGER: | 
 |         type_name = PropType::GetTypeStringFromType(ValueType::Int); | 
 |         break; | 
 |       case base::Value::TYPE_DOUBLE: | 
 |         type_name = PropType::GetTypeStringFromType(ValueType::Double); | 
 |         break; | 
 |       case base::Value::TYPE_STRING: | 
 |         type_name = PropType::GetTypeStringFromType(ValueType::String); | 
 |         break; | 
 |       case base::Value::TYPE_DICTIONARY: | 
 |         type_name = PropType::GetTypeStringFromType(ValueType::Object); | 
 |         break; | 
 |       default: | 
 |         // The rest are unsupported. | 
 |         break; | 
 |       } | 
 |     } | 
 |   } | 
 |   return type_name; | 
 | } | 
 |  | 
 | bool ObjectSchema::PropFromJsonArray(const std::string& prop_name, | 
 |                                      const base::Value& value, | 
 |                                      const PropType* base_schema, | 
 |                                      Properties* properties, | 
 |                                      chromeos::ErrorPtr* error) const { | 
 |   const base::ListValue* list = nullptr; | 
 |   CHECK(value.GetAsList(&list)) << "Unable to get array value"; | 
 |   std::string type_name = DetectArrayType(list, base_schema); | 
 |   if (type_name.empty()) | 
 |     return ErrorInvalidTypeInfo(prop_name, error); | 
 |   std::unique_ptr<PropType> prop = CreatePropType(type_name, prop_name, error); | 
 |   if (!prop) | 
 |     return false; | 
 |   base::DictionaryValue array_object; | 
 |   array_object.SetWithoutPathExpansion(commands::attributes::kOneOf_Enum, | 
 |                                        list->DeepCopy()); | 
 |   if (!prop->FromJson(&array_object, base_schema, error)) { | 
 |     chromeos::Error::AddToPrintf(error, errors::commands::kDomain, | 
 |                                  errors::commands::kInvalidPropDef, | 
 |                                  "Error in definition of property '%s'", | 
 |                                  prop_name.c_str()); | 
 |     return false; | 
 |   } | 
 |   properties->insert(std::make_pair(prop_name, std::move(prop))); | 
 |   return true; | 
 | } | 
 |  | 
 | static std::string DetectObjectType(const base::DictionaryValue* dict, | 
 |                                     const PropType* base_schema) { | 
 |   bool has_min_max = dict->HasKey(commands::attributes::kNumeric_Min) || | 
 |                      dict->HasKey(commands::attributes::kNumeric_Max); | 
 |  | 
 |   // Here we are trying to "detect the type and read in the object based on | 
 |   // the deduced type". Later, we'll verify that this detected type matches | 
 |   // the expectation of the base schema, if applicable, to make sure we are not | 
 |   // changing the expected type. This makes the vendor-side (re)definition of | 
 |   // standard and custom commands behave exactly the same. | 
 |   // The only problem with this approach was the double-vs-int types. | 
 |   // If the type is meant to be a double we want to allow its definition as | 
 |   // "min:0, max:0" instead of just forcing it to be only "min:0.0, max:0.0". | 
 |   // If we have "minimum" or "maximum", and we have a Double schema object, | 
 |   // treat this object as a Double (even if both min and max are integers). | 
 |   if (has_min_max && base_schema && base_schema->GetType() == ValueType::Double) | 
 |     return PropType::GetTypeStringFromType(ValueType::Double); | 
 |  | 
 |   // If we have at least one "minimum" or "maximum" that is Double, | 
 |   // it's a Double. | 
 |   const base::Value* value = nullptr; | 
 |   if (dict->Get(commands::attributes::kNumeric_Min, &value) && | 
 |       value->IsType(base::Value::TYPE_DOUBLE)) | 
 |     return PropType::GetTypeStringFromType(ValueType::Double); | 
 |   if (dict->Get(commands::attributes::kNumeric_Max, &value) && | 
 |       value->IsType(base::Value::TYPE_DOUBLE)) | 
 |     return PropType::GetTypeStringFromType(ValueType::Double); | 
 |  | 
 |   // If we have "minimum" or "maximum", it's an Integer. | 
 |   if (has_min_max) | 
 |     return PropType::GetTypeStringFromType(ValueType::Int); | 
 |  | 
 |   // If we have "minLength" or "maxLength", it's a String. | 
 |   if (dict->HasKey(commands::attributes::kString_MinLength) || | 
 |       dict->HasKey(commands::attributes::kString_MaxLength)) | 
 |     return PropType::GetTypeStringFromType(ValueType::String); | 
 |  | 
 |   // If we have "properties", it's an object. | 
 |   if (dict->HasKey(commands::attributes::kObject_Properties)) | 
 |     return PropType::GetTypeStringFromType(ValueType::Object); | 
 |  | 
 |   // If we have "enum", it's an array. Detect type from array elements. | 
 |   const base::ListValue* list = nullptr; | 
 |   if (dict->GetListWithoutPathExpansion( | 
 |       commands::attributes::kOneOf_Enum, &list)) | 
 |     return DetectArrayType(list, base_schema); | 
 |  | 
 |   return std::string(); | 
 | } | 
 |  | 
 | bool ObjectSchema::PropFromJsonObject(const std::string& prop_name, | 
 |                                       const base::Value& value, | 
 |                                       const PropType* base_schema, | 
 |                                       Properties* properties, | 
 |                                       chromeos::ErrorPtr* error) const { | 
 |   const base::DictionaryValue* dict = nullptr; | 
 |   CHECK(value.GetAsDictionary(&dict)) << "Unable to get dictionary value"; | 
 |   std::string type_name; | 
 |   if (dict->HasKey(commands::attributes::kType)) { | 
 |     if (!dict->GetString(commands::attributes::kType, &type_name)) | 
 |       return ErrorInvalidTypeInfo(prop_name, error); | 
 |   } else { | 
 |     type_name = DetectObjectType(dict, base_schema); | 
 |   } | 
 |   if (type_name.empty()) { | 
 |     if (!base_schema) | 
 |       return ErrorInvalidTypeInfo(prop_name, error); | 
 |     type_name = base_schema->GetTypeAsString(); | 
 |   } | 
 |   std::unique_ptr<PropType> prop = CreatePropType(type_name, prop_name, error); | 
 |   if (!prop) | 
 |     return false; | 
 |   if (!prop->FromJson(dict, base_schema, error)) { | 
 |     chromeos::Error::AddToPrintf(error, errors::commands::kDomain, | 
 |                                  errors::commands::kInvalidPropDef, | 
 |                                  "Error in definition of property '%s'", | 
 |                                  prop_name.c_str()); | 
 |     return false; | 
 |   } | 
 |   properties->insert(std::make_pair(prop_name, std::move(prop))); | 
 |   return true; | 
 | } | 
 |  | 
 | }  // namespace buffet |