| // Copyright 2015 The Weave 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 "src/commands/schema_utils.h" | 
 |  | 
 | #include <algorithm> | 
 | #include <set> | 
 | #include <string> | 
 |  | 
 | #include <base/json/json_writer.h> | 
 | #include <base/logging.h> | 
 |  | 
 | #include "src/commands/object_schema.h" | 
 | #include "src/commands/prop_types.h" | 
 | #include "src/commands/prop_values.h" | 
 |  | 
 | namespace weave { | 
 | namespace { | 
 | // Helper function to report "type mismatch" errors when parsing JSON. | 
 | void ReportJsonTypeMismatch(const tracked_objects::Location& location, | 
 |                             const base::Value* value_in, | 
 |                             const std::string& expected_type, | 
 |                             ErrorPtr* error) { | 
 |   std::string value_as_string; | 
 |   base::JSONWriter::Write(*value_in, &value_as_string); | 
 |   Error::AddToPrintf(error, location, errors::commands::kDomain, | 
 |                      errors::commands::kTypeMismatch, | 
 |                      "Unable to convert value %s into %s", | 
 |                      value_as_string.c_str(), expected_type.c_str()); | 
 | } | 
 |  | 
 | // Template version of ReportJsonTypeMismatch that deduces the type of expected | 
 | // data from the value_out parameter passed to particular overload of | 
 | // TypedValueFromJson() function. Always returns false. | 
 | template <typename T> | 
 | bool ReportUnexpectedJson(const tracked_objects::Location& location, | 
 |                           const base::Value* value_in, | 
 |                           T*, | 
 |                           ErrorPtr* error) { | 
 |   ReportJsonTypeMismatch(location, value_in, | 
 |                          PropType::GetTypeStringFromType(GetValueType<T>()), | 
 |                          error); | 
 |   return false; | 
 | } | 
 |  | 
 | bool ErrorMissingProperty(ErrorPtr* error, | 
 |                           const tracked_objects::Location& location, | 
 |                           const char* param_name) { | 
 |   Error::AddToPrintf(error, location, errors::commands::kDomain, | 
 |                      errors::commands::kPropertyMissing, | 
 |                      "Required parameter missing: %s", param_name); | 
 |   return false; | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | // Specializations of TypedValueToJson<T>() for supported C++ types. | 
 | std::unique_ptr<base::FundamentalValue> TypedValueToJson(bool value) { | 
 |   return std::unique_ptr<base::FundamentalValue>( | 
 |       new base::FundamentalValue(value)); | 
 | } | 
 |  | 
 | std::unique_ptr<base::FundamentalValue> TypedValueToJson(int value) { | 
 |   return std::unique_ptr<base::FundamentalValue>( | 
 |       new base::FundamentalValue(value)); | 
 | } | 
 |  | 
 | std::unique_ptr<base::FundamentalValue> TypedValueToJson(double value) { | 
 |   return std::unique_ptr<base::FundamentalValue>( | 
 |       new base::FundamentalValue(value)); | 
 | } | 
 |  | 
 | std::unique_ptr<base::StringValue> TypedValueToJson(const std::string& value) { | 
 |   return std::unique_ptr<base::StringValue>(new base::StringValue(value)); | 
 | } | 
 |  | 
 | std::unique_ptr<base::DictionaryValue> TypedValueToJson(const ValueMap& value) { | 
 |   std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue); | 
 |   for (const auto& pair : value) { | 
 |     auto prop_value = pair.second->ToJson(); | 
 |     CHECK(prop_value); | 
 |     dict->SetWithoutPathExpansion(pair.first, prop_value.release()); | 
 |   } | 
 |   return dict; | 
 | } | 
 |  | 
 | std::unique_ptr<base::ListValue> TypedValueToJson(const ValueVector& value) { | 
 |   std::unique_ptr<base::ListValue> list(new base::ListValue); | 
 |   for (const auto& item : value) { | 
 |     auto json = item->ToJson(); | 
 |     CHECK(json); | 
 |     list->Append(json.release()); | 
 |   } | 
 |   return list; | 
 | } | 
 |  | 
 | bool TypedValueFromJson(const base::Value* value_in, | 
 |                         const PropType* type, | 
 |                         bool* value_out, | 
 |                         ErrorPtr* error) { | 
 |   return value_in->GetAsBoolean(value_out) || | 
 |          ReportUnexpectedJson(FROM_HERE, value_in, value_out, error); | 
 | } | 
 |  | 
 | bool TypedValueFromJson(const base::Value* value_in, | 
 |                         const PropType* type, | 
 |                         int* value_out, | 
 |                         ErrorPtr* error) { | 
 |   return value_in->GetAsInteger(value_out) || | 
 |          ReportUnexpectedJson(FROM_HERE, value_in, value_out, error); | 
 | } | 
 |  | 
 | bool TypedValueFromJson(const base::Value* value_in, | 
 |                         const PropType* type, | 
 |                         double* value_out, | 
 |                         ErrorPtr* error) { | 
 |   return value_in->GetAsDouble(value_out) || | 
 |          ReportUnexpectedJson(FROM_HERE, value_in, value_out, error); | 
 | } | 
 |  | 
 | bool TypedValueFromJson(const base::Value* value_in, | 
 |                         const PropType* type, | 
 |                         std::string* value_out, | 
 |                         ErrorPtr* error) { | 
 |   return value_in->GetAsString(value_out) || | 
 |          ReportUnexpectedJson(FROM_HERE, value_in, value_out, error); | 
 | } | 
 |  | 
 | bool TypedValueFromJson(const base::Value* value_in, | 
 |                         const PropType* type, | 
 |                         ValueMap* value_out, | 
 |                         ErrorPtr* error) { | 
 |   const base::DictionaryValue* dict = nullptr; | 
 |   if (!value_in->GetAsDictionary(&dict)) | 
 |     return ReportUnexpectedJson(FROM_HERE, value_in, value_out, error); | 
 |  | 
 |   CHECK(type) << "Object definition must be provided"; | 
 |   CHECK(ValueType::Object == type->GetType()) << "Type must be Object"; | 
 |  | 
 |   const ObjectSchema* object_schema = type->GetObject()->GetObjectSchemaPtr(); | 
 |   std::set<std::string> keys_processed; | 
 |   value_out->clear();  // Clear possible default values already in |value_out|. | 
 |   for (const auto& pair : object_schema->GetProps()) { | 
 |     const PropValue* def_value = pair.second->GetDefaultValue(); | 
 |     if (dict->HasKey(pair.first)) { | 
 |       const base::Value* param_value = nullptr; | 
 |       CHECK(dict->GetWithoutPathExpansion(pair.first, ¶m_value)) | 
 |           << "Unable to get parameter"; | 
 |       auto value = pair.second->CreatePropValue(*param_value, error); | 
 |       if (!value) { | 
 |         Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain, | 
 |                            errors::commands::kInvalidPropValue, | 
 |                            "Invalid value for property '%s'", | 
 |                            pair.first.c_str()); | 
 |         return false; | 
 |       } | 
 |       value_out->emplace_hint(value_out->end(), pair.first, std::move(value)); | 
 |       keys_processed.insert(pair.first); | 
 |     } else if (def_value) { | 
 |       value_out->emplace_hint(value_out->end(), pair.first, def_value->Clone()); | 
 |       keys_processed.insert(pair.first); | 
 |     } else if (pair.second->IsRequired()) { | 
 |       return ErrorMissingProperty(error, FROM_HERE, pair.first.c_str()); | 
 |     } | 
 |   } | 
 |  | 
 |   // Just for sanity, make sure that we processed all the necessary properties | 
 |   // and there weren't any extra (unknown) ones specified. If so, ignore | 
 |   // them, but log as warnings... | 
 |   base::DictionaryValue::Iterator iter(*dict); | 
 |   while (!iter.IsAtEnd()) { | 
 |     std::string key = iter.key(); | 
 |     if (keys_processed.find(key) == keys_processed.end() && | 
 |         !object_schema->GetExtraPropertiesAllowed()) { | 
 |       Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain, | 
 |                          errors::commands::kUnknownProperty, | 
 |                          "Unrecognized parameter '%s'", key.c_str()); | 
 |       return false; | 
 |     } | 
 |     iter.Advance(); | 
 |   } | 
 |  | 
 |   // Now go over all property values and validate them. | 
 |   for (const auto& pair : *value_out) { | 
 |     const PropType* prop_type = pair.second->GetPropType(); | 
 |     CHECK(prop_type) << "Value property type must be available"; | 
 |     if (!prop_type->ValidateConstraints(*pair.second, error)) { | 
 |       Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain, | 
 |                          errors::commands::kInvalidPropValue, | 
 |                          "Invalid value for property '%s'", pair.first.c_str()); | 
 |       return false; | 
 |     } | 
 |   } | 
 |   return true; | 
 | } | 
 |  | 
 | bool TypedValueFromJson(const base::Value* value_in, | 
 |                         const PropType* type, | 
 |                         ValueVector* value_out, | 
 |                         ErrorPtr* error) { | 
 |   const base::ListValue* list = nullptr; | 
 |   if (!value_in->GetAsList(&list)) | 
 |     return ReportUnexpectedJson(FROM_HERE, value_in, value_out, error); | 
 |  | 
 |   CHECK(type) << "Array type definition must be provided"; | 
 |   CHECK(ValueType::Array == type->GetType()) << "Type must be Array"; | 
 |   const PropType* item_type = type->GetArray()->GetItemTypePtr(); | 
 |   CHECK(item_type) << "Incomplete array type definition"; | 
 |  | 
 |   // This value might already contain values from the type defaults. | 
 |   // Clear them first before proceeding. | 
 |   value_out->clear(); | 
 |   value_out->reserve(list->GetSize()); | 
 |   for (const base::Value* item : *list) { | 
 |     std::unique_ptr<PropValue> prop_value = | 
 |         item_type->CreatePropValue(*item, error); | 
 |     if (!prop_value) | 
 |       return false; | 
 |     value_out->push_back(std::move(prop_value)); | 
 |   } | 
 |   return true; | 
 | } | 
 |  | 
 | // Compares two sets of key-value pairs from two Objects. | 
 | static bool obj_cmp(const ValueMap::value_type& v1, | 
 |                     const ValueMap::value_type& v2) { | 
 |   return (v1.first == v2.first) && v1.second->IsEqual(v2.second.get()); | 
 | } | 
 |  | 
 | bool operator==(const ValueMap& obj1, const ValueMap& obj2) { | 
 |   if (obj1.size() != obj2.size()) | 
 |     return false; | 
 |  | 
 |   auto pair = std::mismatch(obj1.begin(), obj1.end(), obj2.begin(), obj_cmp); | 
 |   return pair == std::make_pair(obj1.end(), obj2.end()); | 
 | } | 
 |  | 
 | bool operator==(const ValueVector& arr1, const ValueVector& arr2) { | 
 |   if (arr1.size() != arr2.size()) | 
 |     return false; | 
 |  | 
 |   using Type = const ValueVector::value_type; | 
 |   // Compare two array items. | 
 |   auto arr_cmp = [](const Type& v1, const Type& v2) { | 
 |     return v1->IsEqual(v2.get()); | 
 |   }; | 
 |   auto pair = std::mismatch(arr1.begin(), arr1.end(), arr2.begin(), arr_cmp); | 
 |   return pair == std::make_pair(arr1.end(), arr2.end()); | 
 | } | 
 |  | 
 | std::string ToString(const ValueMap& obj) { | 
 |   auto val = TypedValueToJson(obj); | 
 |   std::string str; | 
 |   base::JSONWriter::Write(*val, &str); | 
 |   return str; | 
 | } | 
 |  | 
 | std::string ToString(const ValueVector& arr) { | 
 |   auto val = TypedValueToJson(arr); | 
 |   std::string str; | 
 |   base::JSONWriter::Write(*val, &str); | 
 |   return str; | 
 | } | 
 |  | 
 | }  // namespace weave |