Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 1 | // 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/schema_utils.h" |
| 6 | |
| 7 | #include <algorithm> |
| 8 | #include <set> |
| 9 | #include <string> |
| 10 | |
| 11 | #include <base/json/json_writer.h> |
| 12 | |
| 13 | #include "buffet/commands/object_schema.h" |
| 14 | #include "buffet/commands/prop_types.h" |
| 15 | #include "buffet/commands/prop_values.h" |
| 16 | |
| 17 | namespace buffet { |
| 18 | namespace { |
| 19 | // Helper function to report "type mismatch" errors when parsing JSON. |
| 20 | void ReportJsonTypeMismatch(const base::Value* value_in, |
| 21 | const std::string& expected_type, |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 22 | chromeos::ErrorPtr* error) { |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 23 | std::string value_as_string; |
| 24 | base::JSONWriter::Write(value_in, &value_as_string); |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 25 | chromeos::Error::AddToPrintf(error, errors::commands::kDomain, |
| 26 | errors::commands::kTypeMismatch, |
| 27 | "Unable to convert value %s into %s", |
| 28 | value_as_string.c_str(), expected_type.c_str()); |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 29 | } |
| 30 | |
| 31 | // Template version of ReportJsonTypeMismatch that deduces the type of expected |
| 32 | // data from the value_out parameter passed to particular overload of |
| 33 | // TypedValueFromJson() function. Always returns false. |
| 34 | template<typename T> |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 35 | bool ReportUnexpectedJson(const base::Value* value_in, T*, |
| 36 | chromeos::ErrorPtr* error) { |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 37 | ReportJsonTypeMismatch(value_in, |
| 38 | PropType::GetTypeStringFromType(GetValueType<T>()), |
| 39 | error); |
| 40 | return false; |
| 41 | } |
| 42 | |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 43 | bool ErrorMissingProperty(chromeos::ErrorPtr* error, const char* param_name) { |
| 44 | chromeos::Error::AddToPrintf(error, errors::commands::kDomain, |
| 45 | errors::commands::kPropertyMissing, |
| 46 | "Required parameter missing: %s", param_name); |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 47 | return false; |
| 48 | } |
| 49 | } // namespace |
| 50 | |
| 51 | // Specializations of TypedValueToJson<T>() for supported C++ types. |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 52 | std::unique_ptr<base::Value> TypedValueToJson(bool value, |
| 53 | chromeos::ErrorPtr* error) { |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 54 | return std::unique_ptr<base::Value>(base::Value::CreateBooleanValue(value)); |
| 55 | } |
| 56 | |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 57 | std::unique_ptr<base::Value> TypedValueToJson(int value, |
| 58 | chromeos::ErrorPtr* error) { |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 59 | return std::unique_ptr<base::Value>(base::Value::CreateIntegerValue(value)); |
| 60 | } |
| 61 | |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 62 | std::unique_ptr<base::Value> TypedValueToJson(double value, |
| 63 | chromeos::ErrorPtr* error) { |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 64 | return std::unique_ptr<base::Value>(base::Value::CreateDoubleValue(value)); |
| 65 | } |
| 66 | |
| 67 | std::unique_ptr<base::Value> TypedValueToJson(const std::string& value, |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 68 | chromeos::ErrorPtr* error) { |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 69 | return std::unique_ptr<base::Value>(base::Value::CreateStringValue(value)); |
| 70 | } |
| 71 | |
| 72 | std::unique_ptr<base::Value> TypedValueToJson(const native_types::Object& value, |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 73 | chromeos::ErrorPtr* error) { |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 74 | std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue); |
| 75 | for (const auto& pair : value) { |
| 76 | auto prop_value = pair.second->ToJson(error); |
| 77 | if (!prop_value) |
| 78 | return prop_value; |
| 79 | dict->SetWithoutPathExpansion(pair.first, prop_value.release()); |
| 80 | } |
| 81 | return std::move(dict); |
| 82 | } |
| 83 | |
| 84 | bool TypedValueFromJson(const base::Value* value_in, |
| 85 | const ObjectSchema* object_schema, |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 86 | bool* value_out, chromeos::ErrorPtr* error) { |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 87 | return value_in->GetAsBoolean(value_out) || |
| 88 | ReportUnexpectedJson(value_in, value_out, error); |
| 89 | } |
| 90 | |
| 91 | bool TypedValueFromJson(const base::Value* value_in, |
| 92 | const ObjectSchema* object_schema, |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 93 | int* value_out, chromeos::ErrorPtr* error) { |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 94 | return value_in->GetAsInteger(value_out) || |
| 95 | ReportUnexpectedJson(value_in, value_out, error); |
| 96 | } |
| 97 | |
| 98 | bool TypedValueFromJson(const base::Value* value_in, |
| 99 | const ObjectSchema* object_schema, |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 100 | double* value_out, chromeos::ErrorPtr* error) { |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 101 | return value_in->GetAsDouble(value_out) || |
| 102 | ReportUnexpectedJson(value_in, value_out, error); |
| 103 | } |
| 104 | |
| 105 | bool TypedValueFromJson(const base::Value* value_in, |
| 106 | const ObjectSchema* object_schema, |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 107 | std::string* value_out, chromeos::ErrorPtr* error) { |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 108 | return value_in->GetAsString(value_out) || |
| 109 | ReportUnexpectedJson(value_in, value_out, error); |
| 110 | } |
| 111 | |
| 112 | bool TypedValueFromJson(const base::Value* value_in, |
| 113 | const ObjectSchema* object_schema, |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 114 | native_types::Object* value_out, |
| 115 | chromeos::ErrorPtr* error) { |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 116 | const base::DictionaryValue* dict = nullptr; |
| 117 | if (!value_in->GetAsDictionary(&dict)) |
| 118 | return ReportUnexpectedJson(value_in, value_out, error); |
| 119 | |
| 120 | CHECK(object_schema) << "Object schema must be provided"; |
| 121 | |
| 122 | std::set<std::string> keys_processed; |
| 123 | for (const auto& pair : object_schema->GetProps()) { |
| 124 | const PropValue* def_value = pair.second->GetDefaultValue(); |
| 125 | if (dict->HasKey(pair.first)) { |
| 126 | std::shared_ptr<PropValue> value = pair.second->CreateValue(); |
| 127 | const base::Value* param_value = nullptr; |
| 128 | CHECK(dict->GetWithoutPathExpansion(pair.first, ¶m_value)) |
| 129 | << "Unable to get parameter"; |
| 130 | if (!value->FromJson(param_value, error)) |
| 131 | return false; |
| 132 | value_out->insert(std::make_pair(pair.first, std::move(value))); |
| 133 | } else if (def_value) { |
| 134 | std::shared_ptr<PropValue> value = def_value->Clone(); |
| 135 | value_out->insert(std::make_pair(pair.first, std::move(value))); |
| 136 | } else { |
| 137 | return ErrorMissingProperty(error, pair.first.c_str()); |
| 138 | } |
| 139 | keys_processed.insert(pair.first); |
| 140 | } |
| 141 | |
| 142 | // Just for sanity, make sure that we processed all the necessary properties |
| 143 | // and there weren't any extra (unknown) ones specified. If so, ignore |
| 144 | // them, but log as warnings... |
| 145 | base::DictionaryValue::Iterator iter(*dict); |
| 146 | while (!iter.IsAtEnd()) { |
| 147 | std::string key = iter.key(); |
| 148 | if (keys_processed.find(key) == keys_processed.end() && |
| 149 | !object_schema->GetExtraPropertiesAllowed()) { |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 150 | chromeos::Error::AddToPrintf(error, errors::commands::kDomain, |
| 151 | errors::commands::kUnknownProperty, |
| 152 | "Unrecognized parameter '%s'", key.c_str()); |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 153 | return false; |
| 154 | } |
| 155 | iter.Advance(); |
| 156 | } |
| 157 | |
| 158 | // Now go over all property values and validate them. |
| 159 | for (const auto& pair : *value_out) { |
| 160 | const PropType* prop_type = pair.second->GetPropType(); |
| 161 | CHECK(prop_type) << "Value property type must be available"; |
| 162 | if (!prop_type->ValidateConstraints(*pair.second, error)) { |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 163 | chromeos::Error::AddToPrintf(error, errors::commands::kDomain, |
| 164 | errors::commands::kInvalidPropValue, |
| 165 | "Invalid parameter value for property '%s'", |
| 166 | pair.first.c_str()); |
Alex Vakulenko | 66ec292 | 2014-06-17 15:30:22 -0700 | [diff] [blame] | 167 | return false; |
| 168 | } |
| 169 | } |
| 170 | return true; |
| 171 | } |
| 172 | |
| 173 | // Compares two sets of key-value pairs from two Objects. |
| 174 | static bool obj_cmp(const native_types::Object::value_type& v1, |
| 175 | const native_types::Object::value_type& v2) { |
| 176 | return (v1.first == v2.first) && v1.second->IsEqual(v2.second.get()); |
| 177 | } |
| 178 | |
| 179 | bool operator==(const native_types::Object& obj1, |
| 180 | const native_types::Object& obj2) { |
| 181 | if (obj1.size() != obj2.size()) |
| 182 | return false; |
| 183 | |
| 184 | auto pair = std::mismatch(obj1.begin(), obj1.end(), obj2.begin(), obj_cmp); |
| 185 | return pair == std::make_pair(obj1.end(), obj2.end()); |
| 186 | } |
| 187 | |
| 188 | std::string ToString(const native_types::Object& obj) { |
| 189 | auto val = TypedValueToJson(obj, nullptr); |
| 190 | std::string str; |
| 191 | base::JSONWriter::Write(val.get(), &str); |
| 192 | return str; |
| 193 | } |
| 194 | |
| 195 | |
| 196 | } // namespace buffet |