buffet: GCD command defintion. Compound object type support. Added support for "object" type. Refactored parameter validation to make sure we have object schema context when we validate a value of parameter. Parameter |schema| was used in two different contexts, as both a base parameter definition and as a custom object definition. Renamed the former to be 'base_schema' and latter as 'object_schema' to remove the confusion. Extracted common data type manipulation functions into schema_utils.cc/.h files. BUG=chromium:374860 TEST=All unit tests pass. Change-Id: I6c3549849a258bcc94b3d754acd14e072438d140 Reviewed-on: https://chromium-review.googlesource.com/204793 Tested-by: Alex Vakulenko <avakulenko@chromium.org> Reviewed-by: Christopher Wiley <wiley@chromium.org> Commit-Queue: Alex Vakulenko <avakulenko@chromium.org>
diff --git a/buffet/buffet.gyp b/buffet/buffet.gyp index c16d741..8f7ba6e 100644 --- a/buffet/buffet.gyp +++ b/buffet/buffet.gyp
@@ -32,6 +32,7 @@ 'commands/prop_types.cc', 'commands/prop_values.cc', 'commands/schema_constants.cc', + 'commands/schema_utils.cc', 'data_encoding.cc', 'dbus_constants.cc', 'dbus_utils.cc', @@ -84,6 +85,7 @@ 'async_event_sequencer_unittest.cc', 'buffet_testrunner.cc', 'commands/object_schema_unittest.cc', + 'commands/schema_utils_unittest.cc', 'data_encoding_unittest.cc', 'device_registration_info_unittest.cc', 'error_unittest.cc',
diff --git a/buffet/commands/object_schema.cc b/buffet/commands/object_schema.cc index 19610be..0312735 100644 --- a/buffet/commands/object_schema.cc +++ b/buffet/commands/object_schema.cc
@@ -41,14 +41,15 @@ } bool ObjectSchema::FromJson(const base::DictionaryValue* value, - const ObjectSchema* schema, ErrorPtr* error) { + const ObjectSchema* object_schema, + ErrorPtr* error) { Properties properties; base::DictionaryValue::Iterator iter(*value); while (!iter.IsAtEnd()) { std::string name = iter.key(); - const PropType* schemaProp = schema ? schema->GetProp(iter.key()) : - nullptr; - if (!PropFromJson(iter.key(), iter.value(), schemaProp, &properties, + 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(); @@ -84,21 +85,21 @@ bool ObjectSchema::PropFromJson(const std::string& prop_name, const base::Value& value, - const PropType* schemaProp, + const PropType* base_schema, Properties* properties, 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, schemaProp, properties, + 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, schemaProp, properties, + 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, schemaProp, properties, + return PropFromJsonObject(prop_name, value, base_schema, properties, error); } Error::AddToPrintf(error, commands::errors::kDomain, @@ -109,7 +110,7 @@ bool ObjectSchema::PropFromJsonString(const std::string& prop_name, const base::Value& value, - const PropType* schemaProp, + const PropType* base_schema, Properties* properties, ErrorPtr* error) const { std::string type_name; @@ -118,17 +119,17 @@ if (!prop) return false; base::DictionaryValue empty; - if (!prop->FromJson(&empty, schemaProp, error)) + if (!prop->FromJson(&empty, base_schema, error)) return false; properties->insert(std::make_pair(prop_name, std::move(prop))); return true; } static std::string DetectArrayType(const base::ListValue* list, - const PropType* schemaProp) { + const PropType* base_schema) { std::string type_name; - if (schemaProp) { - type_name = schemaProp->GetTypeAsString(); + 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)) { @@ -145,6 +146,9 @@ 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; @@ -156,12 +160,12 @@ bool ObjectSchema::PropFromJsonArray(const std::string& prop_name, const base::Value& value, - const PropType* schemaProp, + const PropType* base_schema, Properties* properties, ErrorPtr* error) const { const base::ListValue* list = nullptr; CHECK(value.GetAsList(&list)) << "Unable to get array value"; - std::string type_name = DetectArrayType(list, schemaProp); + 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); @@ -170,14 +174,14 @@ base::DictionaryValue array_object; array_object.SetWithoutPathExpansion(commands::attributes::kOneOf_Enum, list->DeepCopy()); - if (!prop->FromJson(&array_object, schemaProp, error)) + if (!prop->FromJson(&array_object, base_schema, error)) return false; properties->insert(std::make_pair(prop_name, std::move(prop))); return true; } static std::string DetectObjectType(const base::DictionaryValue* dict, - const PropType* schemaProp) { + const PropType* base_schema) { bool has_min_max = dict->HasKey(commands::attributes::kNumeric_Min) || dict->HasKey(commands::attributes::kNumeric_Max); @@ -191,7 +195,7 @@ // "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 && schemaProp && schemaProp->GetType() == ValueType::Double) + 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, @@ -213,18 +217,22 @@ dict->HasKey(commands::attributes::kString_MaxLength)) return PropType::GetTypeStringFromType(ValueType::String); - // If we have "values", it's an array. + // 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, schemaProp); + return DetectArrayType(list, base_schema); return std::string(); } bool ObjectSchema::PropFromJsonObject(const std::string& prop_name, const base::Value& value, - const PropType* schemaProp, + const PropType* base_schema, Properties* properties, ErrorPtr* error) const { const base::DictionaryValue* dict = nullptr; @@ -234,15 +242,15 @@ if (!dict->GetString(commands::attributes::kType, &type_name)) return ErrorInvalidTypeInfo(prop_name, error); } else { - type_name = DetectObjectType(dict, schemaProp); + type_name = DetectObjectType(dict, base_schema); } if (type_name.empty()) { - if (!schemaProp) + if (!base_schema) return ErrorInvalidTypeInfo(prop_name, error); - type_name = schemaProp->GetTypeAsString(); + type_name = base_schema->GetTypeAsString(); } std::unique_ptr<PropType> prop = CreatePropType(type_name, prop_name, error); - if (!prop || !prop->FromJson(dict, schemaProp, error)) + if (!prop || !prop->FromJson(dict, base_schema, error)) return false; properties->insert(std::make_pair(prop_name, std::move(prop))); return true;
diff --git a/buffet/commands/object_schema.h b/buffet/commands/object_schema.h index 0e48129..d6f5069 100644 --- a/buffet/commands/object_schema.h +++ b/buffet/commands/object_schema.h
@@ -9,8 +9,6 @@ #include <memory> #include <string> -#include <base/basictypes.h> - #include "buffet/error.h" namespace base { @@ -34,7 +32,11 @@ // name and the value is the parameter type definition object. using Properties = std::map<std::string, std::shared_ptr<PropType>>; + // Declaring default and copy constructors to document the copyable + // nature of this class. Using the default implementation for them though. ObjectSchema() = default; + ObjectSchema(const ObjectSchema& rhs) = default; + ObjectSchema& operator=(const ObjectSchema& rhs) = default; // Add a new parameter definition. void AddProp(const std::string& name, std::shared_ptr<PropType> prop); @@ -43,44 +45,54 @@ // Gets the list of all the properties defined. const Properties& GetProps() const { return properties_; } + // Specify whether extra properties are allowed on objects described by + // this schema. When validating a value of an object type, we can + // make sure that the value has only the properties explicitly defined by + // the schema and no other (custom) properties are allowed. + // This is to support JSON Schema's "additionalProperties" specification. + bool GetExtraPropertiesAllowed() const { return extra_properties_allowed_; } + void SetExtraPropertiesAllowed(bool allowed) { + extra_properties_allowed_ = allowed; + } + // Saves the object schema to JSON. When |full_schema| is set to true, // then all properties and constraints are saved, otherwise, only // the overridden (not inherited) ones are saved. std::unique_ptr<base::DictionaryValue> ToJson(bool full_schema, ErrorPtr* error) const; - // Loads the object schema from JSON. If |schema| is not nullptr, it is + // Loads the object schema from JSON. If |object_schema| is not nullptr, it is // used as a base schema to inherit omitted properties and constraints from. - bool FromJson(const base::DictionaryValue* value, const ObjectSchema* schema, - ErrorPtr* error); + bool FromJson(const base::DictionaryValue* value, + const ObjectSchema* object_schema, ErrorPtr* error); private: // Internal helper method to load individual parameter type definitions. bool PropFromJson(const std::string& prop_name, const base::Value& value, - const PropType* schemaProp, + const PropType* base_schema, Properties* properties, ErrorPtr* error) const; // Helper function in case the parameter is defined as JSON string like this: // "prop":"..." bool PropFromJsonString(const std::string& prop_name, const base::Value& value, - const PropType* schemaProp, + const PropType* base_schema, Properties* properties, ErrorPtr* error) const; // Helper function in case the parameter is defined as JSON array like this: // "prop":[...] bool PropFromJsonArray(const std::string& prop_name, const base::Value& value, - const PropType* schemaProp, + const PropType* base_schema, Properties* properties, ErrorPtr* error) const; // Helper function in case the parameter is defined as JSON object like this: // "prop":{...} bool PropFromJsonObject(const std::string& prop_name, const base::Value& value, - const PropType* schemaProp, + const PropType* base_schema, Properties* properties, ErrorPtr* error) const; // Internal parameter type definition map. Properties properties_; - DISALLOW_COPY_AND_ASSIGN(ObjectSchema); + bool extra_properties_allowed_{false}; }; } // namespace buffet
diff --git a/buffet/commands/object_schema_unittest.cc b/buffet/commands/object_schema_unittest.cc index fa901ae..be93073 100644 --- a/buffet/commands/object_schema_unittest.cc +++ b/buffet/commands/object_schema_unittest.cc
@@ -60,6 +60,7 @@ EXPECT_EQ(&prop, prop.GetInt()); EXPECT_EQ(nullptr, prop.GetDouble()); EXPECT_EQ(nullptr, prop.GetString()); + EXPECT_EQ(nullptr, prop.GetObject()); } TEST(CommandSchema, IntPropType_ToJson) { @@ -165,6 +166,7 @@ EXPECT_EQ(&prop, prop.GetBoolean()); EXPECT_EQ(nullptr, prop.GetDouble()); EXPECT_EQ(nullptr, prop.GetString()); + EXPECT_EQ(nullptr, prop.GetObject()); } TEST(CommandSchema, BoolPropType_ToJson) { @@ -229,6 +231,7 @@ EXPECT_EQ(nullptr, prop.GetBoolean()); EXPECT_EQ(&prop, prop.GetDouble()); EXPECT_EQ(nullptr, prop.GetString()); + EXPECT_EQ(nullptr, prop.GetObject()); } TEST(CommandSchema, DoublePropType_ToJson) { @@ -325,6 +328,7 @@ EXPECT_EQ(nullptr, prop.GetBoolean()); EXPECT_EQ(nullptr, prop.GetDouble()); EXPECT_EQ(&prop, prop.GetString()); + EXPECT_EQ(nullptr, prop.GetObject()); } TEST(CommandSchema, StringPropType_ToJson) { @@ -411,6 +415,121 @@ error.reset(); } +/////////////////////////////////////////////////////////////////////////////// + +TEST(CommandSchema, ObjectPropType_Empty) { + buffet::ObjectPropType prop; + EXPECT_TRUE(prop.HasOverriddenAttributes()); + EXPECT_FALSE(prop.IsBasedOnSchema()); +} + +TEST(CommandSchema, ObjectPropType_Types) { + buffet::ObjectPropType prop; + EXPECT_EQ(nullptr, prop.GetInt()); + EXPECT_EQ(nullptr, prop.GetBoolean()); + EXPECT_EQ(nullptr, prop.GetDouble()); + EXPECT_EQ(nullptr, prop.GetString()); + EXPECT_EQ(&prop, prop.GetObject()); +} + +TEST(CommandSchema, ObjectPropType_ToJson) { + buffet::ObjectPropType prop; + EXPECT_EQ("{'properties':{}}", + ValueToString(prop.ToJson(false, nullptr).get())); + EXPECT_EQ("{'properties':{},'type':'object'}", + ValueToString(prop.ToJson(true, nullptr).get())); + EXPECT_FALSE(prop.IsBasedOnSchema()); + buffet::ObjectPropType prop2; + prop2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr); + EXPECT_EQ("{}", ValueToString(prop2.ToJson(false, nullptr).get())); + EXPECT_TRUE(prop2.IsBasedOnSchema()); + + auto schema = std::make_shared<buffet::ObjectSchema>(); + schema->AddProp("expires", std::make_shared<buffet::IntPropType>()); + auto pw = std::make_shared<buffet::StringPropType>(); + pw->AddLengthConstraint(6, 100); + schema->AddProp("password", pw); + prop2.SetObjectSchema(schema); + EXPECT_EQ("{'properties':{'expires':'integer'," + "'password':{'maxLength':100,'minLength':6}}}", + ValueToString(prop2.ToJson(false, nullptr).get())); + EXPECT_EQ("{'properties':{'expires':{'type':'integer'}," + "'password':{'maxLength':100,'minLength':6,'type':'string'}}," + "'type':'object'}", + ValueToString(prop2.ToJson(true, nullptr).get())); +} + +TEST(CommandSchema, ObjectPropType_FromJson) { + buffet::ObjectPropType base_prop; + EXPECT_TRUE(base_prop.FromJson(CreateDictionaryValue( + "{'properties':{'name':'string','age':'integer'}}").get(), nullptr, + nullptr)); + auto schema = base_prop.GetObjectSchemaPtr(); + const buffet::PropType* prop = schema->GetProp("name"); + EXPECT_EQ(buffet::ValueType::String, prop->GetType()); + prop = schema->GetProp("age"); + EXPECT_EQ(buffet::ValueType::Int, prop->GetType()); +} + +TEST(CommandSchema, ObjectPropType_Validate) { + buffet::ObjectPropType prop; + prop.FromJson(CreateDictionaryValue( + "{'properties':{'expires':'integer'," + "'password':{'maxLength':100,'minLength':6}}}").get(), nullptr, + nullptr); + buffet::ErrorPtr error; + EXPECT_TRUE(prop.ValidateValue(CreateValue( + "{'expires':10,'password':'abcdef'}").get(), &error)); + error.reset(); + + EXPECT_FALSE(prop.ValidateValue(CreateValue( + "{'expires':10}").get(), &error)); + EXPECT_EQ("parameter_missing", error->GetCode()); + error.reset(); + + EXPECT_FALSE(prop.ValidateValue(CreateValue( + "{'password':'abcdef'}").get(), &error)); + EXPECT_EQ("parameter_missing", error->GetCode()); + error.reset(); + + EXPECT_FALSE(prop.ValidateValue(CreateValue( + "{'expires':10,'password':'abcde'}").get(), &error)); + EXPECT_EQ("out_of_range", error->GetFirstError()->GetCode()); + error.reset(); + + EXPECT_FALSE(prop.ValidateValue(CreateValue("2").get(), &error)); + EXPECT_EQ("type_mismatch", error->GetCode()); + error.reset(); + + EXPECT_FALSE(prop.ValidateValue(CreateValue( + "{'expires':10,'password':'abcdef','retry':true}").get(), &error)); + EXPECT_EQ("unexpected_parameter", error->GetCode()); + error.reset(); +} + +TEST(CommandSchema, ObjectPropType_Validate_Enum) { + buffet::ObjectPropType prop; + EXPECT_TRUE(prop.FromJson(CreateDictionaryValue( + "{'properties':{'width':'integer','height':'integer'}," + "'enum':[{'width':10,'height':20},{'width':100,'height':200}]}").get(), + nullptr, nullptr)); + buffet::ErrorPtr error; + EXPECT_TRUE(prop.ValidateValue(CreateValue( + "{'height':20,'width':10}").get(), &error)); + error.reset(); + + EXPECT_TRUE(prop.ValidateValue(CreateValue( + "{'height':200,'width':100}").get(), &error)); + error.reset(); + + EXPECT_FALSE(prop.ValidateValue(CreateValue( + "{'height':12,'width':10}").get(), &error)); + EXPECT_EQ("out_of_range", error->GetCode()); + error.reset(); +} + +/////////////////////////////////////////////////////////////////////////////// + TEST(CommandSchema, ObjectSchema_FromJson_Shorthand_TypeName) { buffet::ObjectSchema schema; const char* schema_str = "{"
diff --git a/buffet/commands/prop_constraints.cc b/buffet/commands/prop_constraints.cc index 2376588..5ea9314 100644 --- a/buffet/commands/prop_constraints.cc +++ b/buffet/commands/prop_constraints.cc
@@ -3,25 +3,12 @@ // found in the LICENSE file. #include "buffet/commands/prop_constraints.h" +#include "buffet/commands/prop_values.h" #include "buffet/commands/schema_constants.h" #include "buffet/string_utils.h" namespace buffet { -// Specializations of TypedValueToJson<T>() for supported C++ types. -std::unique_ptr<base::Value> TypedValueToJson(bool value) { - return std::unique_ptr<base::Value>(base::Value::CreateBooleanValue(value)); -} -std::unique_ptr<base::Value> TypedValueToJson(int value) { - return std::unique_ptr<base::Value>(base::Value::CreateIntegerValue(value)); -} -std::unique_ptr<base::Value> TypedValueToJson(double value) { - return std::unique_ptr<base::Value>(base::Value::CreateDoubleValue(value)); -} -std::unique_ptr<base::Value> TypedValueToJson(const std::string& value) { - return std::unique_ptr<base::Value>(base::Value::CreateStringValue(value)); -} - // Constraint ---------------------------------------------------------------- Constraint::~Constraint() {} @@ -76,7 +63,7 @@ std::unique_ptr<base::Value> ConstraintStringLength::ToJson( ErrorPtr* error) const { - return TypedValueToJson(limit_.value); + return TypedValueToJson(limit_.value, error); } // ConstraintStringLengthMin -------------------------------------------------- @@ -85,9 +72,10 @@ ConstraintStringLengthMin::ConstraintStringLengthMin(int limit) : ConstraintStringLength(limit) {} -bool ConstraintStringLengthMin::Validate(const Any& value, +bool ConstraintStringLengthMin::Validate(const PropValue& value, ErrorPtr* error) const { - std::string str = value.Get<std::string>(); + CHECK(value.GetString()) << "Expecting a string value for this constraint"; + const std::string& str = value.GetString()->GetValue(); int length = static_cast<int>(str.size()); if (length < limit_.value) { if (limit_.value == 1) { @@ -116,9 +104,10 @@ ConstraintStringLengthMax::ConstraintStringLengthMax(int limit) : ConstraintStringLength(limit) {} -bool ConstraintStringLengthMax::Validate(const Any& value, +bool ConstraintStringLengthMax::Validate(const PropValue& value, ErrorPtr* error) const { - std::string str = value.Get<std::string>(); + CHECK(value.GetString()) << "Expecting a string value for this constraint"; + const std::string& str = value.GetString()->GetValue(); int length = static_cast<int>(str.size()); if (length > limit_.value) { Error::AddToPrintf(error, commands::errors::kDomain,
diff --git a/buffet/commands/prop_constraints.h b/buffet/commands/prop_constraints.h index 96bfdfb..da43e86 100644 --- a/buffet/commands/prop_constraints.h +++ b/buffet/commands/prop_constraints.h
@@ -5,7 +5,6 @@ #ifndef BUFFET_COMMANDS_PROP_CONSTRAINTS_H_ #define BUFFET_COMMANDS_PROP_CONSTRAINTS_H_ -#include <limits> #include <string> #include <type_traits> #include <vector> @@ -13,64 +12,14 @@ #include <base/basictypes.h> #include <base/values.h> -#include "buffet/any.h" +#include "buffet/commands/prop_values.h" #include "buffet/commands/schema_constants.h" +#include "buffet/commands/schema_utils.h" #include "buffet/error.h" #include "buffet/string_utils.h" namespace buffet { -// InheritableAttribute class is used for specifying various command parameter -// attributes that can be inherited from a base (parent) schema. -// The |value| still specifies the actual attribute values, whether it -// is inherited or overridden, while |is_inherited| can be used to identify -// if the attribute was inherited (true) or overridden (false). -template<typename T> -class InheritableAttribute { - public: - InheritableAttribute() = default; - explicit InheritableAttribute(T val) - : value(std::move(val)), is_inherited(true) {} - InheritableAttribute(T val, bool inherited) - : value(std::move(val)), is_inherited(inherited) {} - T value{}; - bool is_inherited{true}; -}; - -// A bunch of helper function to create base::Value for specific C++ classes, -// including vectors of types. These are used in template classes below -// to simplify specialization logic. -std::unique_ptr<base::Value> TypedValueToJson(bool value); -std::unique_ptr<base::Value> TypedValueToJson(int value); -std::unique_ptr<base::Value> TypedValueToJson(double value); -std::unique_ptr<base::Value> TypedValueToJson(const std::string& value); -template<typename T> -std::unique_ptr<base::Value> TypedValueToJson(const std::vector<T>& values) { - std::unique_ptr<base::ListValue> list(new base::ListValue); - for (const auto& v : values) - list->Append(TypedValueToJson(v).release()); - return std::unique_ptr<base::Value>(list.release()); -} - -// Similarly to CreateTypedValue() function above, the following overloaded -// helper methods allow to extract specific C++ data types from base::Value. -// Also used in template classes below to simplify specialization logic. -inline bool TypedValueFromJson(const base::Value* value_in, bool* value_out) { - return value_in->GetAsBoolean(value_out); -} -inline bool TypedValueFromJson(const base::Value* value_in, int* value_out) { - return value_in->GetAsInteger(value_out); -} -inline bool TypedValueFromJson(const base::Value* value_in, double* value_out) { - return value_in->GetAsDouble(value_out); -} -inline bool TypedValueFromJson(const base::Value* value_in, - std::string* value_out) { - return value_in->GetAsString(value_out); -} - -////////////////////////////////////////////////////////////////////////////// - enum class ConstraintType { Min, Max, @@ -97,7 +46,7 @@ // Validates a parameter against the constraint. Returns true if parameter // value satisfies the constraint, otherwise fills the optional |error| with // the details for the failure. - virtual bool Validate(const Any& value, ErrorPtr* error) const = 0; + virtual bool Validate(const PropValue& value, ErrorPtr* error) const = 0; // Makes a copy of the constraint object, marking all the attributes // as inherited from the original definition. virtual std::shared_ptr<Constraint> CloneAsInherited() const = 0; @@ -153,7 +102,7 @@ // Implementation of Constraint::ToJson(). virtual std::unique_ptr<base::Value> ToJson(ErrorPtr* error) const override { - return TypedValueToJson(limit_.value); + return TypedValueToJson(limit_.value, error); } // Stores the upper/lower value limit for maximum/minimum constraint. @@ -178,8 +127,9 @@ virtual ConstraintType GetType() const { return ConstraintType::Min; } // Implementation of Constraint::Validate(). - virtual bool Validate(const Any& value, ErrorPtr* error) const override { - const T& v = value.Get<T>(); + virtual bool Validate(const PropValue& value, + ErrorPtr* error) const override { + T v = value.GetValueAsAny().Get<T>(); if (v < this->limit_.value) return this->ReportErrorLessThan( error, string_utils::ToString(v), @@ -214,8 +164,9 @@ virtual ConstraintType GetType() const { return ConstraintType::Max; } // Implementation of Constraint::Validate(). - virtual bool Validate(const Any& value, ErrorPtr* error) const override { - const T& v = value.Get<T>(); + virtual bool Validate(const PropValue& value, + ErrorPtr* error) const override { + T v = value.GetValueAsAny().Get<T>(); if (v > this->limit_.value) return this->ReportErrorGreaterThan( error, string_utils::ToString(v), @@ -268,7 +219,7 @@ return ConstraintType::StringLengthMin; } // Implementation of Constraint::Validate(). - virtual bool Validate(const Any& value, ErrorPtr* error) const override; + virtual bool Validate(const PropValue& value, ErrorPtr* error) const override; // Implementation of Constraint::CloneAsInherited(). virtual std::shared_ptr<Constraint> CloneAsInherited() const override; // Implementation of Constraint::GetDictKey(). @@ -289,7 +240,7 @@ return ConstraintType::StringLengthMax; } // Implementation of Constraint::Validate(). - virtual bool Validate(const Any& value, ErrorPtr* error) const override; + virtual bool Validate(const PropValue& value, ErrorPtr* error) const override; // Implementation of Constraint::CloneAsInherited(). virtual std::shared_ptr<Constraint> CloneAsInherited() const override; // Implementation of Constraint::GetDictKey(). @@ -301,27 +252,6 @@ DISALLOW_COPY_AND_ASSIGN(ConstraintStringLengthMax); }; -// CompareValue is a helper functor to help with implementing EqualsTo operator -// for various data types. For most scalar types it is using operator==(), -// however, for floating point values, rounding errors in binary representation -// of IEEE floats/doubles can cause straight == comparison to fail for seemingly -// equivalent values. For these, use approximate comparison with the error -// margin equal to the epsilon value defined for the corresponding data type. - -// Compare exact types using ==. -template<typename T, bool exact = true> -struct CompareValue { - inline bool operator()(const T& v1, const T& v2) { return v1 == v2; } -}; - -// Compare non-exact types (such as double) using precision margin (epsilon). -template<typename T> -struct CompareValue<T, false> { - inline bool operator()(const T& v1, const T& v2) { - return std::abs(v1 - v2) <= std::numeric_limits<T>::epsilon(); - } -}; - // Implementation of OneOf constraint for different data types. template<typename T> class ConstraintOneOf : public Constraint { @@ -342,20 +272,20 @@ } // Implementation of Constraint::Validate(). - virtual bool Validate(const Any& value, ErrorPtr* error) const override { - const T& v = value.Get<T>(); - constexpr bool exact_type = std::is_same<T, std::string>::value || - std::numeric_limits<T>::is_exact; + virtual bool Validate(const PropValue& value, + ErrorPtr* error) const override { + using string_utils::ToString; + T v = value.GetValueAsAny().Get<T>(); for (const auto& item : set_.value) { - if (CompareValue<T, exact_type>()(v, item)) + if (CompareValue(v, item)) return true; } std::vector<std::string> values; values.reserve(set_.value.size()); for (const auto& item : set_.value) { - values.push_back(string_utils::ToString(item)); + values.push_back(ToString(item)); } - return ReportErrorNotOneOf(error, string_utils::ToString(v), values); + return ReportErrorNotOneOf(error, ToString(v), values); } // Implementation of Constraint::CloneAsInherited(). @@ -365,7 +295,7 @@ // Implementation of Constraint::ToJson(). virtual std::unique_ptr<base::Value> ToJson(ErrorPtr* error) const override { - return TypedValueToJson(set_.value); + return TypedValueToJson(set_.value, error); } // Implementation of Constraint::GetDictKey().
diff --git a/buffet/commands/prop_types.cc b/buffet/commands/prop_types.cc index 6efdfb6..3b71a56 100644 --- a/buffet/commands/prop_types.cc +++ b/buffet/commands/prop_types.cc
@@ -39,11 +39,11 @@ } std::unique_ptr<base::Value> PropType::ToJson(bool full_schema, - ErrorPtr* error) const { + ErrorPtr* error) const { if (!full_schema && !HasOverriddenAttributes()) { if (based_on_schema_) return std::unique_ptr<base::Value>(new base::DictionaryValue); - return TypedValueToJson(GetTypeAsString()); + return TypedValueToJson(GetTypeAsString(), error); } std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue); @@ -82,19 +82,19 @@ } bool PropType::FromJson(const base::DictionaryValue* value, - const PropType* schema, ErrorPtr* error) { - if (schema && schema->GetType() != GetType()) { + const PropType* base_schema, ErrorPtr* error) { + if (base_schema && base_schema->GetType() != GetType()) { Error::AddToPrintf(error, commands::errors::kDomain, commands::errors::kPropTypeChanged, "Redefining a command of type %s as %s", - schema->GetTypeAsString().c_str(), + base_schema->GetTypeAsString().c_str(), GetTypeAsString().c_str()); return false; } - based_on_schema_ = (schema != nullptr); + based_on_schema_ = (base_schema != nullptr); constraints_.clear(); - if (schema) { - for (const auto& pair : schema->GetConstraints()) { + if (base_schema) { + for (const auto& pair : base_schema->GetConstraints()) { std::shared_ptr<Constraint> inherited(pair.second->CloneAsInherited()); constraints_.insert(std::make_pair(pair.first, inherited)); } @@ -121,7 +121,14 @@ return p != constraints_.end() ? p->second.get() : nullptr; } -bool PropType::ValidateConstraints(const Any& value, ErrorPtr* error) const { +bool PropType::ValidateValue(const base::Value* value, ErrorPtr* error) const { + std::shared_ptr<PropValue> val = CreateValue(); + CHECK(val) << "Failed to create value object"; + return val->FromJson(value, error) && ValidateConstraints(*val, error); +} + +bool PropType::ValidateConstraints(const PropValue& value, + ErrorPtr* error) const { for (const auto& pair : constraints_) { if (!pair.second->Validate(value, error)) return false; @@ -135,6 +142,7 @@ {ValueType::Double, "number"}, {ValueType::String, "string"}, {ValueType::Boolean, "boolean"}, + {ValueType::Object, "object"}, }; return map; } @@ -173,28 +181,17 @@ case buffet::ValueType::Boolean: prop = new BooleanPropType; break; + case buffet::ValueType::Object: + prop = new ObjectPropType; + break; } return std::unique_ptr<PropType>(prop); } template<typename T> -bool TypedValueFromJson(const base::Value* value_in, T* value_out, - ErrorPtr* error) { - if (!TypedValueFromJson(value_in, value_out)) { - std::string value_as_string; - base::JSONWriter::Write(value_in, &value_as_string); - Error::AddToPrintf( - error, commands::errors::kDomain, commands::errors::kTypeMismatch, - "Unable to convert %s to %s", value_as_string.c_str(), - PropType::GetTypeStringFromType(GetValueType<T>()).c_str()); - return false; - } - return true; -} - -template<typename T> static std::shared_ptr<Constraint> LoadOneOfConstraint( - const base::DictionaryValue* value, ErrorPtr* error) { + const base::DictionaryValue* value, const ObjectSchema* object_schema, + ErrorPtr* error) { const base::ListValue* list = nullptr; if (!value->GetListWithoutPathExpansion(commands::attributes::kOneOf_Enum, &list)) { @@ -206,7 +203,7 @@ set.reserve(list->GetSize()); for (const base::Value* item : *list) { T val{}; - if (!TypedValueFromJson(item, &val, error)) + if (!TypedValueFromJson(item, object_schema, &val, error)) return std::shared_ptr<Constraint>(); set.push_back(val); } @@ -216,12 +213,13 @@ template<class ConstraintClass, typename T> static std::shared_ptr<Constraint> LoadMinMaxConstraint( - const char* dict_key, const base::DictionaryValue* value, ErrorPtr* error) { + const char* dict_key, const base::DictionaryValue* value, + const ObjectSchema* object_schema, ErrorPtr* error) { InheritableAttribute<T> limit; const base::Value* src_val = nullptr; CHECK(value->Get(dict_key, &src_val)) << "Unable to get min/max constraints"; - if (!TypedValueFromJson(src_val, &limit.value, error)) + if (!TypedValueFromJson(src_val, object_schema, &limit.value, error)) return std::shared_ptr<Constraint>(); limit.is_inherited = false; @@ -230,13 +228,14 @@ template<typename T> bool NumericPropTypeBase::FromJsonHelper(const base::DictionaryValue* value, - const PropType* schema, + const PropType* base_schema, ErrorPtr* error) { - if (!PropType::FromJson(value, schema, error)) + if (!PropType::FromJson(value, base_schema, error)) return false; if (value->HasKey(commands::attributes::kOneOf_Enum)) { - auto constraint = LoadOneOfConstraint<T>(value, error); + auto constraint = LoadOneOfConstraint<T>(value, GetObjectSchemaPtr(), + error); if (!constraint) return false; AddConstraint(constraint); @@ -245,7 +244,8 @@ } else { if (value->HasKey(commands::attributes::kNumeric_Min)) { auto constraint = LoadMinMaxConstraint<ConstraintMin<T>, T>( - commands::attributes::kNumeric_Min, value, error); + commands::attributes::kNumeric_Min, value, GetObjectSchemaPtr(), + error); if (!constraint) return false; AddConstraint(constraint); @@ -253,7 +253,8 @@ } if (value->HasKey(commands::attributes::kNumeric_Max)) { auto constraint = LoadMinMaxConstraint<ConstraintMax<T>, T>( - commands::attributes::kNumeric_Max, value, error); + commands::attributes::kNumeric_Max, value, GetObjectSchemaPtr(), + error); if (!constraint) return false; AddConstraint(constraint); @@ -271,7 +272,13 @@ } std::shared_ptr<PropValue> IntPropType::CreateValue() const { - return std::make_shared<IntValue>(); + return std::make_shared<IntValue>(this); +} + +std::shared_ptr<PropValue> IntPropType::CreateValue(const Any& val) const { + auto v = std::make_shared<IntValue>(this); + v->SetValue(val.Get<int>()); + return std::move(v); } // DoublePropType ------------------------------------------------------------- @@ -281,7 +288,13 @@ } std::shared_ptr<PropValue> DoublePropType::CreateValue() const { - return std::make_shared<DoubleValue>(); + return std::make_shared<DoubleValue>(this); +} + +std::shared_ptr<PropValue> DoublePropType::CreateValue(const Any& val) const { + auto v = std::make_shared<DoubleValue>(this); + v->SetValue(val.Get<double>()); + return std::move(v); } // StringPropType ------------------------------------------------------------- @@ -291,15 +304,23 @@ } std::shared_ptr<PropValue> StringPropType::CreateValue() const { - return std::make_shared<StringValue>(); + return std::make_shared<StringValue>(this); +} + +std::shared_ptr<PropValue> StringPropType::CreateValue(const Any& val) const { + auto v = std::make_shared<StringValue>(this); + v->SetValue(val.Get<std::string>()); + return std::move(v); } bool StringPropType::FromJson(const base::DictionaryValue* value, - const PropType* schema, ErrorPtr* error) { - if (!PropType::FromJson(value, schema, error)) + const PropType* base_schema, ErrorPtr* error) { + if (!PropType::FromJson(value, base_schema, error)) return false; if (value->HasKey(commands::attributes::kOneOf_Enum)) { - auto constraint = LoadOneOfConstraint<std::string>(value, error); + auto constraint = LoadOneOfConstraint<std::string>(value, + GetObjectSchemaPtr(), + error); if (!constraint) return false; AddConstraint(constraint); @@ -308,7 +329,8 @@ } else { if (value->HasKey(commands::attributes::kString_MinLength)) { auto constraint = LoadMinMaxConstraint<ConstraintStringLengthMin, int>( - commands::attributes::kString_MinLength, value, error); + commands::attributes::kString_MinLength, value, GetObjectSchemaPtr(), + error); if (!constraint) return false; AddConstraint(constraint); @@ -316,7 +338,8 @@ } if (value->HasKey(commands::attributes::kString_MaxLength)) { auto constraint = LoadMinMaxConstraint<ConstraintStringLengthMax, int>( - commands::attributes::kString_MaxLength, value, error); + commands::attributes::kString_MaxLength, value, GetObjectSchemaPtr(), + error); if (!constraint) return false; AddConstraint(constraint); @@ -352,16 +375,111 @@ } std::shared_ptr<PropValue> BooleanPropType::CreateValue() const { - return std::make_shared<BooleanValue>(); + return std::make_shared<BooleanValue>(this); +} + +std::shared_ptr<PropValue> BooleanPropType::CreateValue(const Any& val) const { + auto v = std::make_shared<BooleanValue>(this); + v->SetValue(val.Get<bool>()); + return std::move(v); } bool BooleanPropType::FromJson(const base::DictionaryValue* value, - const PropType* schema, ErrorPtr* error) { - if (!PropType::FromJson(value, schema, error)) + const PropType* base_schema, ErrorPtr* error) { + if (!PropType::FromJson(value, base_schema, error)) return false; if (value->HasKey(commands::attributes::kOneOf_Enum)) { - auto constraint = LoadOneOfConstraint<bool>(value, error); + auto constraint = LoadOneOfConstraint<bool>(value, GetObjectSchemaPtr(), + error); + if (!constraint) + return false; + AddConstraint(constraint); + } + + return true; +} + +// ObjectPropType ------------------------------------------------------------- + +ObjectPropType::ObjectPropType() + : object_schema_(std::make_shared<ObjectSchema>(), false) {} + +std::shared_ptr<PropType> ObjectPropType::Clone() const { + return std::make_shared<ObjectPropType>(*this); +} + +std::shared_ptr<PropValue> ObjectPropType::CreateValue() const { + return std::make_shared<ObjectValue>(this); +} + +std::shared_ptr<PropValue> ObjectPropType::CreateValue(const Any& val) const { + auto v = std::make_shared<ObjectValue>(this); + v->SetValue(val.Get<native_types::Object>()); + return std::move(v); +} + +bool ObjectPropType::HasOverriddenAttributes() const { + return PropType::HasOverriddenAttributes() || + !object_schema_.is_inherited; +} + + +std::unique_ptr<base::Value> ObjectPropType::ToJson(bool full_schema, + ErrorPtr* error) const { + std::unique_ptr<base::Value> value = PropType::ToJson(full_schema, error); + if (value) { + base::DictionaryValue* dict = nullptr; + CHECK(value->GetAsDictionary(&dict)) << "Expecting a JSON object"; + if (!object_schema_.is_inherited || full_schema) { + auto object_schema = object_schema_.value->ToJson(full_schema, error); + if (!object_schema) { + value.reset(); + return value; + } + dict->SetWithoutPathExpansion(commands::attributes::kObject_Properties, + object_schema.release()); + } + } + return value; +} + +bool ObjectPropType::FromJson(const base::DictionaryValue* value, + const PropType* base_schema, ErrorPtr* error) { + if (!PropType::FromJson(value, base_schema, error)) + return false; + + using commands::attributes::kObject_Properties; + + std::shared_ptr<const ObjectSchema> base_object_schema; + if (base_schema) + base_object_schema = base_schema->GetObject()->GetObjectSchema(); + + const base::DictionaryValue* props = nullptr; + if (value->GetDictionaryWithoutPathExpansion(kObject_Properties, &props)) { + auto object_schema = std::make_shared<ObjectSchema>(); + if (!object_schema->FromJson(props, base_object_schema.get(), error)) { + Error::AddTo(error, commands::errors::kDomain, + commands::errors::kInvalidObjectSchema, + "Error parsing object property schema"); + return false; + } + object_schema_.value = object_schema; + object_schema_.is_inherited = false; + } else if (base_object_schema) { + object_schema_.value = base_object_schema; + object_schema_.is_inherited = true; + } else { + Error::AddToPrintf(error, commands::errors::kDomain, + commands::errors::kInvalidObjectSchema, + "Object type definition must include the object schema " + "('%s' field not found)", kObject_Properties); + return false; + } + + if (value->HasKey(commands::attributes::kOneOf_Enum)) { + auto constraint = LoadOneOfConstraint<native_types::Object>( + value, GetObjectSchemaPtr(), error); if (!constraint) return false; AddConstraint(constraint);
diff --git a/buffet/commands/prop_types.h b/buffet/commands/prop_types.h index 63d3c44..e39b356 100644 --- a/buffet/commands/prop_types.h +++ b/buffet/commands/prop_types.h
@@ -18,10 +18,11 @@ namespace buffet { -// Helper function to read in a C++ type from JSON value. -template<typename T> -bool TypedValueFromJson(const base::Value* value_in, T* value_out, - ErrorPtr* error); +class IntPropType; +class DoublePropType; +class StringPropType; +class BooleanPropType; +class ObjectPropType; // PropType is a base class for all parameter type definition objects. // Property definitions of a particular type will derive from this class and @@ -70,12 +71,15 @@ virtual StringPropType const* GetString() const { return nullptr; } virtual BooleanPropType* GetBoolean() { return nullptr; } virtual BooleanPropType const* GetBoolean() const { return nullptr; } + virtual ObjectPropType* GetObject() { return nullptr; } + virtual ObjectPropType const* GetObject() const { return nullptr; } // Makes a full copy of this type definition. virtual std::shared_ptr<PropType> Clone() const = 0; // Creates an instance of associated value object, using the parameter // type as a factory class. virtual std::shared_ptr<PropValue> CreateValue() const = 0; + virtual std::shared_ptr<PropValue> CreateValue(const Any& val) const = 0; // Saves the parameter type definition as a JSON object. // If |full_schema| is set to true, the full type definition is saved, @@ -86,22 +90,21 @@ // error information. virtual std::unique_ptr<base::Value> ToJson(bool full_schema, ErrorPtr* error) const; - // Parses an JSON parameter type definition. Optional |schema| may specify - // the base schema type definition this type should be based upon. + // Parses an JSON parameter type definition. Optional |base_schema| may + // specify the base schema type definition this type should be based upon. // If not specified (nullptr), the parameter type is assumed to be a full // definition and any omitted required properties are treated as an error. // Returns true on success, otherwise fills in the |error| with additional // error information. virtual bool FromJson(const base::DictionaryValue* value, - const PropType* schema, ErrorPtr* error); + const PropType* base_schema, ErrorPtr* error); // Validates a JSON value for the parameter type to make sure it satisfies // the parameter type definition including any specified constraints. // Returns false if the |value| does not meet the requirements of the type // definition and returns additional information about the failure via // the |error| parameter. - virtual bool ValidateValue(const base::Value* value, - ErrorPtr* error) const = 0; + bool ValidateValue(const base::Value* value, ErrorPtr* error) const; // Additional helper static methods to help with converting a type enum // value into a string and back. @@ -126,11 +129,19 @@ const Constraint* GetConstraint(ConstraintType constraint_type) const; Constraint* GetConstraint(ConstraintType constraint_type); - protected: - // Validates the given value against all the constraints. - // This is a helper method used by PropType::ValidateValue(). - bool ValidateConstraints(const Any& value, ErrorPtr* error) const; + // Returns a schema for Object-type parameter. This will be nullptr for + // every type but Object. + const ObjectSchema* GetObjectSchemaPtr() const { + return GetObjectSchema().get(); + } + virtual std::shared_ptr<const ObjectSchema> GetObjectSchema() const { + return std::shared_ptr<const ObjectSchema>(); + } + // Validates the given value against all the constraints. + bool ValidateConstraints(const PropValue& value, ErrorPtr* error) const; + + protected: // Helper method to obtaining a vector of OneOf constraint values. template<typename T> std::vector<T> GetOneOfValuesHelper() const { @@ -138,13 +149,6 @@ GetConstraint(ConstraintType::OneOf)); return ofc ? ofc->set_.value : std::vector<T>(); } - // Helper methods to validating parameter values for various types. - template<typename T> - bool ValidateValueHelper(const base::Value* value, ErrorPtr* error) const { - T val; - return TypedValueFromJson(value, &val, error) && - ValidateConstraints(val, error); - } // Specifies if this parameter definition is derived from a base // object schema. @@ -190,7 +194,7 @@ // Helper method for implementing FromJson in derived classes. template<typename T> bool FromJsonHelper(const base::DictionaryValue* value, - const PropType* schema, ErrorPtr* error); + const PropType* base_schema, ErrorPtr* error); }; // Property definition of Integer type. @@ -204,15 +208,11 @@ virtual std::shared_ptr<PropType> Clone() const override; virtual std::shared_ptr<PropValue> CreateValue() const override; + virtual std::shared_ptr<PropValue> CreateValue(const Any& val) const override; virtual bool FromJson(const base::DictionaryValue* value, - const PropType* schema, ErrorPtr* error) override { - return FromJsonHelper<int>(value, schema, error); - } - - virtual bool ValidateValue(const base::Value* value, - ErrorPtr* error) const override { - return ValidateValueHelper<int>(value, error); + const PropType* base_schema, ErrorPtr* error) override { + return FromJsonHelper<int>(value, base_schema, error); } // Helper methods to add and inspect simple constraints. @@ -238,15 +238,11 @@ virtual std::shared_ptr<PropType> Clone() const override; virtual std::shared_ptr<PropValue> CreateValue() const override; + virtual std::shared_ptr<PropValue> CreateValue(const Any& val) const override; virtual bool FromJson(const base::DictionaryValue* value, - const PropType* schema, ErrorPtr* error) override { - return FromJsonHelper<double>(value, schema, error); - } - - virtual bool ValidateValue(const base::Value* value, - ErrorPtr* error) const override { - return ValidateValueHelper<double>(value, error); + const PropType* base_schema, ErrorPtr* error) override { + return FromJsonHelper<double>(value, base_schema, error); } // Helper methods to add and inspect simple constraints. @@ -272,14 +268,10 @@ virtual std::shared_ptr<PropType> Clone() const override; virtual std::shared_ptr<PropValue> CreateValue() const override; + virtual std::shared_ptr<PropValue> CreateValue(const Any& val) const override; virtual bool FromJson(const base::DictionaryValue* value, - const PropType* schema, ErrorPtr* error) override; - - virtual bool ValidateValue(const base::Value* value, - ErrorPtr* error) const override { - return ValidateValueHelper<std::string>(value, error); - } + const PropType* base_schema, ErrorPtr* error) override; // Helper methods to add and inspect simple constraints. // Used mostly for unit testing. @@ -302,14 +294,10 @@ virtual std::shared_ptr<PropType> Clone() const override; virtual std::shared_ptr<PropValue> CreateValue() const override; + virtual std::shared_ptr<PropValue> CreateValue(const Any& val) const override; virtual bool FromJson(const base::DictionaryValue* value, - const PropType* schema, ErrorPtr* error) override; - - virtual bool ValidateValue(const base::Value* value, - ErrorPtr* error) const override { - return ValidateValueHelper<bool>(value, error); - } + const PropType* base_schema, ErrorPtr* error) override; // Helper methods to add and inspect simple constraints. // Used mostly for unit testing. @@ -318,6 +306,38 @@ } }; +// Parameter definition of Object type. +class ObjectPropType : public PropType { + public: + ObjectPropType(); + + // Overrides from the ParamType base class. + virtual ValueType GetType() const override { return ValueType::Object; } + virtual bool HasOverriddenAttributes() const override; + + virtual ObjectPropType* GetObject() override { return this; } + virtual ObjectPropType const* GetObject() const override { return this; } + + virtual std::shared_ptr<PropType> Clone() const override; + virtual std::shared_ptr<PropValue> CreateValue() const override; + virtual std::shared_ptr<PropValue> CreateValue(const Any& val) const override; + + virtual std::unique_ptr<base::Value> ToJson(bool full_schema, + ErrorPtr* error) const override; + virtual bool FromJson(const base::DictionaryValue* value, + const PropType* base_schema, ErrorPtr* error) override; + + virtual std::shared_ptr<const ObjectSchema> GetObjectSchema() const override { + return object_schema_.value; + } + void SetObjectSchema(const std::shared_ptr<const ObjectSchema>& schema) { + object_schema_.value = schema; + object_schema_.is_inherited = false; + } + + private: + InheritableAttribute<std::shared_ptr<const ObjectSchema>> object_schema_; +}; } // namespace buffet #endif // BUFFET_COMMANDS_PROP_TYPES_H_
diff --git a/buffet/commands/prop_values.cc b/buffet/commands/prop_values.cc index e2eabc0..0c98a20 100644 --- a/buffet/commands/prop_values.cc +++ b/buffet/commands/prop_values.cc
@@ -2,22 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// TODO(avakulenko) Remove this file by Aug 1, 2014 if nothing ends up here... + #include "buffet/commands/prop_values.h" -#include <memory> - -#include <base/values.h> - -#include "buffet/commands/prop_types.h" - namespace buffet { -// Specializations of generic GetValueType<T>() for supported C++ types. -template<> ValueType GetValueType<int>() { return ValueType::Int; } -template<> ValueType GetValueType<double>() { return ValueType::Double; } -template<> ValueType GetValueType<std::string>() { return ValueType::String; } -template<> ValueType GetValueType<bool>() { return ValueType::Boolean; } - -PropValue::~PropValue() {} } // namespace buffet
diff --git a/buffet/commands/prop_values.h b/buffet/commands/prop_values.h index e03d39b..adaa91d 100644 --- a/buffet/commands/prop_values.h +++ b/buffet/commands/prop_values.h
@@ -9,7 +9,8 @@ #include <memory> #include <string> -#include "buffet/commands/prop_constraints.h" +#include "buffet/any.h" +#include "buffet/commands/schema_utils.h" #include "buffet/error.h" namespace base { @@ -24,38 +25,52 @@ Int, Double, String, - Boolean + Boolean, + Object }; +class PropValue; +class IntValue; +class DoubleValue; +class StringValue; +class BooleanValue; +class ObjectValue; + +class PropType; + // Helper methods to get the parameter type enum value for the given // native C++ data representation. // The generic GetValueType<T>() is undefined, however particular // type specializations return the appropriate ValueType. template<typename T> ValueType GetValueType(); // Undefined. -template<> ValueType GetValueType<int>(); -template<> ValueType GetValueType<double>(); -template<> ValueType GetValueType<std::string>(); -template<> ValueType GetValueType<bool>(); +template<> +inline ValueType GetValueType<int>() { return ValueType::Int; } +template<> +inline ValueType GetValueType<double>() { return ValueType::Double; } +template<> +inline ValueType GetValueType<std::string>() { return ValueType::String; } +template<> +inline ValueType GetValueType<bool>() { return ValueType::Boolean; } +template<> +inline ValueType GetValueType<native_types::Object>() { + return ValueType::Object; +} -class ObjectSchema; - -class PropValue; -class IntPropType; -class DoublePropType; -class StringPropType; -class BooleanPropType; - -class IntValue; -class DoubleValue; -class StringValue; -class BooleanValue; - -// The base class for parameter values. +// The base class for property values. // Concrete value classes of various types will be derived from this base. +// A property value is the actual command parameter value (or a concrete value +// that can be used in constraints and presets). The PropValue is mostly +// just parsed content of base::Value when a command is dispatched, however +// it does have some additional functionality: +// - it has a reference to the type definition (PropType) which is used +// when validating the value, especially for "object" types. +// - it can be compared with another instances of values of the same type. +// This is used to validate the values against "enum"/"one of" constraints. class PropValue { public: - PropValue() = default; - virtual ~PropValue(); + explicit PropValue(const PropType* type) + : type_(type) {} + virtual ~PropValue() = default; // Gets the type of the value. virtual ValueType GetType() const = 0; @@ -69,6 +84,8 @@ virtual StringValue const* GetString() const { return nullptr; } virtual BooleanValue* GetBoolean() { return nullptr; } virtual BooleanValue const* GetBoolean() const { return nullptr; } + virtual ObjectValue* GetObject() { return nullptr; } + virtual ObjectValue const* GetObject() const { return nullptr; } // Makes a full copy of this value class. virtual std::shared_ptr<PropValue> Clone() const = 0; @@ -80,7 +97,19 @@ // Parses a value from JSON. // If it fails, it returns false and provides additional information // via the |error| parameter. - virtual bool FromJson(const base::Value* value, ErrorPtr* error) = 0; + virtual bool FromJson(const base::Value* value, + ErrorPtr* error) = 0; + + // Returns the contained C++ value as Any. + virtual Any GetValueAsAny() const = 0; + + // Return the type definition of this value. + const PropType* GetPropType() const { return type_; } + // Compares two values and returns true if they are equal. + virtual bool IsEqual(const PropValue* value) const = 0; + + protected: + const PropType* type_; // weak pointer }; // A helper template base class for implementing simple (non-Object) value @@ -88,6 +117,12 @@ template<typename Derived, typename T> class TypedValueBase : public PropValue { public: + // To help refer to this base class from derived classes, define _Base to + // be this class. + using _Base = TypedValueBase<Derived, T>; + // Expose the non-default constructor of the base class. + using PropValue::PropValue; + // Overrides from PropValue base class. virtual ValueType GetType() const override { return GetValueType<T>(); } virtual std::shared_ptr<PropValue> Clone() const override { @@ -95,15 +130,24 @@ } virtual std::unique_ptr<base::Value> ToJson(ErrorPtr* error) const override { - (void)error; // unused. - return TypedValueToJson(value_); + return TypedValueToJson(value_, error); } - virtual bool FromJson(const base::Value* value, ErrorPtr* error) override { - return TypedValueFromJson(value, &value_, error); + virtual bool FromJson(const base::Value* value, + ErrorPtr* error) override { + return TypedValueFromJson(value, GetPropType()->GetObjectSchemaPtr(), + &value_, error); } - // Helper method to get and set the C++ representation of the value. + virtual bool IsEqual(const PropValue* value) const override { + if (GetType() != value->GetType()) + return false; + const _Base* value_base = static_cast<const _Base*>(value); + return CompareValue(GetValue(), value_base->GetValue()); + } + + // Helper methods to get and set the C++ representation of the value. + virtual Any GetValueAsAny() const override { return value_; } const T& GetValue() const { return value_; } void SetValue(T value) { value_ = std::move(value); } @@ -114,6 +158,7 @@ // Value of type Integer. class IntValue final : public TypedValueBase<IntValue, int> { public: + using _Base::_Base; // Expose the custom constructor of the base class. virtual IntValue* GetInt() override { return this; } virtual IntValue const* GetInt() const override { return this; } }; @@ -121,6 +166,7 @@ // Value of type Number. class DoubleValue final : public TypedValueBase<DoubleValue, double> { public: + using _Base::_Base; // Expose the custom constructor of the base class. virtual DoubleValue* GetDouble() override { return this; } virtual DoubleValue const* GetDouble() const override { return this; } }; @@ -128,6 +174,7 @@ // Value of type String. class StringValue final : public TypedValueBase<StringValue, std::string> { public: + using _Base::_Base; // Expose the custom constructor of the base class. virtual StringValue* GetString() override { return this; } virtual StringValue const* GetString() const override { return this; } }; @@ -135,10 +182,19 @@ // Value of type Boolean. class BooleanValue final : public TypedValueBase<BooleanValue, bool> { public: + using _Base::_Base; // Expose the custom constructor of the base class. virtual BooleanValue* GetBoolean() override { return this; } virtual BooleanValue const* GetBoolean() const override { return this; } }; +// Value of type Object. +class ObjectValue final : public TypedValueBase<ObjectValue, + native_types::Object> { + public: + using _Base::_Base; // Expose the custom constructor of the base class. + virtual ObjectValue* GetObject() override { return this; } + virtual ObjectValue const* GetObject() const override { return this; } +}; } // namespace buffet #endif // BUFFET_COMMANDS_PROP_VALUES_H_
diff --git a/buffet/commands/schema_constants.cc b/buffet/commands/schema_constants.cc index 15d6db3..d8ec689 100644 --- a/buffet/commands/schema_constants.cc +++ b/buffet/commands/schema_constants.cc
@@ -8,28 +8,35 @@ namespace commands { namespace errors { -const char kDomain[] = "command_schema"; +const char kDomain[] = "command_schema"; -const char kOutOfRange[] = "out_of_range"; -const char kTypeMismatch[] = "type_mismatch"; -const char kPropTypeChanged[] = "param_type_changed"; -const char kUnknownType[] = "unknown_type"; -const char kInvalidPropDef[] = "invalid_parameter_definition"; -const char kNoTypeInfo[] = "no_type_info"; -const char kPropertyMissing[] = "parameter_missing"; +const char kOutOfRange[] = "out_of_range"; +const char kTypeMismatch[] = "type_mismatch"; +const char kPropTypeChanged[] = "param_type_changed"; +const char kUnknownType[] = "unknown_type"; +const char kInvalidPropDef[] = "invalid_parameter_definition"; +const char kInvalidPropValue[] = "invalid_parameter_value"; +const char kNoTypeInfo[] = "no_type_info"; +const char kPropertyMissing[] = "parameter_missing"; +const char kUnknownProperty[] = "unexpected_parameter"; +const char kInvalidObjectSchema[] = "invalid_object_schema"; } // namespace errors namespace attributes { -const char kType[] = "type"; -const char kDisplayName[] = "displayName"; +const char kType[] = "type"; +const char kDisplayName[] = "displayName"; -const char kNumeric_Min[] = "minimum"; -const char kNumeric_Max[] = "maximum"; +const char kNumeric_Min[] = "minimum"; +const char kNumeric_Max[] = "maximum"; -const char kString_MinLength[] = "minLength"; -const char kString_MaxLength[] = "maxLength"; +const char kString_MinLength[] = "minLength"; +const char kString_MaxLength[] = "maxLength"; -const char kOneOf_Enum[] = "enum"; +const char kOneOf_Enum[] = "enum"; +const char kOneOf_Metadata[] = "metadata"; +const char kOneOf_MetaSchema[] = "schema"; + +const char kObject_Properties[] = "properties"; } // namespace attributes } // namespace commands
diff --git a/buffet/commands/schema_constants.h b/buffet/commands/schema_constants.h index 0e58cff..087f53b 100644 --- a/buffet/commands/schema_constants.h +++ b/buffet/commands/schema_constants.h
@@ -18,8 +18,11 @@ extern const char kPropTypeChanged[]; extern const char kUnknownType[]; extern const char kInvalidPropDef[]; +extern const char kInvalidPropValue[]; extern const char kNoTypeInfo[]; extern const char kPropertyMissing[]; +extern const char kUnknownProperty[]; +extern const char kInvalidObjectSchema[]; } // namespace errors namespace attributes { @@ -34,6 +37,10 @@ extern const char kString_MaxLength[]; extern const char kOneOf_Enum[]; +extern const char kOneOf_Metadata[]; +extern const char kOneOf_MetaSchema[]; + +extern const char kObject_Properties[]; } // namespace attributes } // namespace commands
diff --git a/buffet/commands/schema_utils.cc b/buffet/commands/schema_utils.cc new file mode 100644 index 0000000..3c6ae57 --- /dev/null +++ b/buffet/commands/schema_utils.cc
@@ -0,0 +1,191 @@ +// 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/schema_utils.h" + +#include <algorithm> +#include <set> +#include <string> + +#include <base/json/json_writer.h> + +#include "buffet/commands/object_schema.h" +#include "buffet/commands/prop_types.h" +#include "buffet/commands/prop_values.h" + +namespace buffet { +namespace { +// Helper function to report "type mismatch" errors when parsing JSON. +void ReportJsonTypeMismatch(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, commands::errors::kDomain, + commands::errors::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 base::Value* value_in, T*, ErrorPtr* error) { + ReportJsonTypeMismatch(value_in, + PropType::GetTypeStringFromType(GetValueType<T>()), + error); + return false; +} + +bool ErrorMissingProperty(ErrorPtr* error, const char* param_name) { + Error::AddToPrintf(error, commands::errors::kDomain, + commands::errors::kPropertyMissing, + "Required parameter missing: %s", param_name); + return false; +} +} // namespace + +// Specializations of TypedValueToJson<T>() for supported C++ types. +std::unique_ptr<base::Value> TypedValueToJson(bool value, ErrorPtr* error) { + return std::unique_ptr<base::Value>(base::Value::CreateBooleanValue(value)); +} + +std::unique_ptr<base::Value> TypedValueToJson(int value, ErrorPtr* error) { + return std::unique_ptr<base::Value>(base::Value::CreateIntegerValue(value)); +} + +std::unique_ptr<base::Value> TypedValueToJson(double value, ErrorPtr* error) { + return std::unique_ptr<base::Value>(base::Value::CreateDoubleValue(value)); +} + +std::unique_ptr<base::Value> TypedValueToJson(const std::string& value, + ErrorPtr* error) { + return std::unique_ptr<base::Value>(base::Value::CreateStringValue(value)); +} + +std::unique_ptr<base::Value> TypedValueToJson(const native_types::Object& value, + ErrorPtr* error) { + std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue); + for (const auto& pair : value) { + auto prop_value = pair.second->ToJson(error); + if (!prop_value) + return prop_value; + dict->SetWithoutPathExpansion(pair.first, prop_value.release()); + } + return std::move(dict); +} + +bool TypedValueFromJson(const base::Value* value_in, + const ObjectSchema* object_schema, + bool* value_out, ErrorPtr* error) { + return value_in->GetAsBoolean(value_out) || + ReportUnexpectedJson(value_in, value_out, error); +} + +bool TypedValueFromJson(const base::Value* value_in, + const ObjectSchema* object_schema, + int* value_out, ErrorPtr* error) { + return value_in->GetAsInteger(value_out) || + ReportUnexpectedJson(value_in, value_out, error); +} + +bool TypedValueFromJson(const base::Value* value_in, + const ObjectSchema* object_schema, + double* value_out, ErrorPtr* error) { + return value_in->GetAsDouble(value_out) || + ReportUnexpectedJson(value_in, value_out, error); +} + +bool TypedValueFromJson(const base::Value* value_in, + const ObjectSchema* object_schema, + std::string* value_out, ErrorPtr* error) { + return value_in->GetAsString(value_out) || + ReportUnexpectedJson(value_in, value_out, error); +} + +bool TypedValueFromJson(const base::Value* value_in, + const ObjectSchema* object_schema, + native_types::Object* value_out, ErrorPtr* error) { + const base::DictionaryValue* dict = nullptr; + if (!value_in->GetAsDictionary(&dict)) + return ReportUnexpectedJson(value_in, value_out, error); + + CHECK(object_schema) << "Object schema must be provided"; + + std::set<std::string> keys_processed; + for (const auto& pair : object_schema->GetProps()) { + const PropValue* def_value = pair.second->GetDefaultValue(); + if (dict->HasKey(pair.first)) { + std::shared_ptr<PropValue> value = pair.second->CreateValue(); + const base::Value* param_value = nullptr; + CHECK(dict->GetWithoutPathExpansion(pair.first, ¶m_value)) + << "Unable to get parameter"; + if (!value->FromJson(param_value, error)) + return false; + value_out->insert(std::make_pair(pair.first, std::move(value))); + } else if (def_value) { + std::shared_ptr<PropValue> value = def_value->Clone(); + value_out->insert(std::make_pair(pair.first, std::move(value))); + } else { + return ErrorMissingProperty(error, pair.first.c_str()); + } + keys_processed.insert(pair.first); + } + + // 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, commands::errors::kDomain, + commands::errors::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, commands::errors::kDomain, + commands::errors::kInvalidPropValue, + "Invalid parameter value for property '%s'", + pair.first.c_str()); + return false; + } + } + return true; +} + +// Compares two sets of key-value pairs from two Objects. +static bool obj_cmp(const native_types::Object::value_type& v1, + const native_types::Object::value_type& v2) { + return (v1.first == v2.first) && v1.second->IsEqual(v2.second.get()); +} + +bool operator==(const native_types::Object& obj1, + const native_types::Object& 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()); +} + +std::string ToString(const native_types::Object& obj) { + auto val = TypedValueToJson(obj, nullptr); + std::string str; + base::JSONWriter::Write(val.get(), &str); + return str; +} + + +} // namespace buffet
diff --git a/buffet/commands/schema_utils.h b/buffet/commands/schema_utils.h new file mode 100644 index 0000000..ff9d461 --- /dev/null +++ b/buffet/commands/schema_utils.h
@@ -0,0 +1,118 @@ +// 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. + +#ifndef BUFFET_COMMANDS_SCHEMA_UTILS_H_ +#define BUFFET_COMMANDS_SCHEMA_UTILS_H_ + +#include <limits> +#include <map> +#include <memory> +#include <string> +#include <type_traits> +#include <vector> + +#include <base/values.h> + +#include <buffet/error.h> + +namespace buffet { + +class PropValue; +class ObjectSchema; + +namespace native_types { +// C++ representation of object values. +using Object = std::map<std::string, std::shared_ptr<PropValue>>; +} // namespace native_types +// Converts an object to string. +std::string ToString(const native_types::Object& obj); + +// InheritableAttribute class is used for specifying various command parameter +// attributes that can be inherited from a base (parent) schema. +// The |value| still specifies the actual attribute values, whether it +// is inherited or overridden, while |is_inherited| can be used to identify +// if the attribute was inherited (true) or overridden (false). +template<typename T> +class InheritableAttribute { + public: + InheritableAttribute() = default; + explicit InheritableAttribute(T val) + : value(std::move(val)), is_inherited(true) {} + InheritableAttribute(T val, bool inherited) + : value(std::move(val)), is_inherited(inherited) {} + T value{}; + bool is_inherited{true}; +}; + +// A bunch of helper function to create base::Value for specific C++ classes, +// including vectors of types. These are used in template classes below +// to simplify specialization logic. +std::unique_ptr<base::Value> TypedValueToJson(bool value, ErrorPtr* error); +std::unique_ptr<base::Value> TypedValueToJson(int value, ErrorPtr* error); +std::unique_ptr<base::Value> TypedValueToJson(double value, ErrorPtr* error); +std::unique_ptr<base::Value> TypedValueToJson(const std::string& value, + ErrorPtr* error); +std::unique_ptr<base::Value> TypedValueToJson(const native_types::Object& value, + ErrorPtr* error); +template<typename T> +std::unique_ptr<base::Value> TypedValueToJson(const std::vector<T>& values, + ErrorPtr* error) { + std::unique_ptr<base::ListValue> list(new base::ListValue); + for (const auto& v : values) { + auto json = TypedValueToJson(v, error); + if (!json) + return std::unique_ptr<base::Value>(); + list->Append(json.release()); + } + return std::move(list); +} + +// Similarly to CreateTypedValue() function above, the following overloaded +// helper methods allow to extract specific C++ data types from base::Value. +// Also used in template classes below to simplify specialization logic. +bool TypedValueFromJson(const base::Value* value_in, + const ObjectSchema* object_schema, + bool* value_out, ErrorPtr* error); +bool TypedValueFromJson(const base::Value* value_in, + const ObjectSchema* object_schema, + int* value_out, ErrorPtr* error); +bool TypedValueFromJson(const base::Value* value_in, + const ObjectSchema* object_schema, + double* value_out, ErrorPtr* error); +bool TypedValueFromJson(const base::Value* value_in, + const ObjectSchema* object_schema, + std::string* value_out, ErrorPtr* error); +bool TypedValueFromJson(const base::Value* value_in, + const ObjectSchema* object_schema, + native_types::Object* value_out, ErrorPtr* error); + +bool operator==(const native_types::Object& obj1, + const native_types::Object& obj2); + +// CompareValue is a helper function to help with implementing EqualsTo operator +// for various data types. For most scalar types it is using operator==(), +// however, for floating point values, rounding errors in binary representation +// of IEEE floats/doubles can cause straight == comparison to fail for seemingly +// equivalent values. For these, use approximate comparison with the error +// margin equal to the epsilon value defined for the corresponding data type. +// This is used when looking up values for implementation of OneOf constraints +// which should work reliably for floating points also ("number" type). + +// Compare exact types using ==. +template<typename T> +inline typename std::enable_if<!std::is_floating_point<T>::value, bool>::type +CompareValue(const T& v1, const T& v2) { + return v1 == v2; +} + +// Compare non-exact types (such as double) using precision margin (epsilon). +template<typename T> +inline typename std::enable_if<std::is_floating_point<T>::value, bool>::type +CompareValue(const T& v1, const T& v2) { + return std::abs(v1 - v2) <= std::numeric_limits<T>::epsilon(); +} + +} // namespace buffet + +#endif // BUFFET_COMMANDS_SCHEMA_UTILS_H_
diff --git a/buffet/commands/schema_utils_unittest.cc b/buffet/commands/schema_utils_unittest.cc new file mode 100644 index 0000000..6918186 --- /dev/null +++ b/buffet/commands/schema_utils_unittest.cc
@@ -0,0 +1,210 @@ +// 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 <memory> +#include <string> +#include <vector> + +#include <base/json/json_reader.h> +#include <base/json/json_writer.h> +#include <base/values.h> +#include <gtest/gtest.h> + +#include "buffet/commands/object_schema.h" +#include "buffet/commands/prop_types.h" +#include "buffet/commands/prop_values.h" +#include "buffet/commands/schema_utils.h" + +namespace { +// Helper method to create base::Value from a string as a smart pointer. +// For ease of definition in C++ code, double-quotes in the source definition +// are replaced with apostrophes. +std::unique_ptr<base::Value> CreateValue(const char* json) { + std::string json2(json); + // Convert apostrophes to double-quotes so JSONReader can parse the string. + std::replace(json2.begin(), json2.end(), '\'', '"'); + return std::unique_ptr<base::Value>(base::JSONReader::Read(json2)); +} + +// Helper method to create a JSON dictionary object from a string. +std::unique_ptr<base::DictionaryValue> CreateDictionaryValue(const char* json) { + std::string json2(json); + std::replace(json2.begin(), json2.end(), '\'', '"'); + base::Value* value = base::JSONReader::Read(json2); + base::DictionaryValue* dict; + value->GetAsDictionary(&dict); + return std::unique_ptr<base::DictionaryValue>(dict); +} + +// Converts a JSON value to a string. It also converts double-quotes to +// apostrophes for easy comparisons in C++ source code. +std::string ValueToString(const base::Value* value) { + std::string json; + base::JSONWriter::Write(value, &json); + std::replace(json.begin(), json.end(), '"', '\''); + return json; +} + +} // namespace + +TEST(CommandSchemaUtils, TypedValueToJson_Scalar) { + EXPECT_EQ("true", + ValueToString(buffet::TypedValueToJson(true, nullptr).get())); + EXPECT_EQ("false", + ValueToString(buffet::TypedValueToJson(false, nullptr).get())); + + EXPECT_EQ("0", ValueToString(buffet::TypedValueToJson(0, nullptr).get())); + EXPECT_EQ("-10", ValueToString(buffet::TypedValueToJson(-10, nullptr).get())); + EXPECT_EQ("20", ValueToString(buffet::TypedValueToJson(20, nullptr).get())); + + EXPECT_EQ("0.0", ValueToString(buffet::TypedValueToJson(0.0, nullptr).get())); + EXPECT_EQ("1.2", ValueToString(buffet::TypedValueToJson(1.2, nullptr).get())); + + EXPECT_EQ("'abc'", + ValueToString(buffet::TypedValueToJson(std::string("abc"), + nullptr).get())); + + std::vector<bool> bool_array{true, false}; + EXPECT_EQ("[true,false]", + ValueToString(buffet::TypedValueToJson(bool_array, nullptr).get())); + + std::vector<int> int_array{1, 2, 5}; + EXPECT_EQ("[1,2,5]", + ValueToString(buffet::TypedValueToJson(int_array, nullptr).get())); + + std::vector<double> dbl_array{1.1, 2.2}; + EXPECT_EQ("[1.1,2.2]", + ValueToString(buffet::TypedValueToJson(dbl_array, nullptr).get())); + + std::vector<std::string> str_array{"a", "bc"}; + EXPECT_EQ("['a','bc']", + ValueToString(buffet::TypedValueToJson(str_array, nullptr).get())); +} + +TEST(CommandSchemaUtils, TypedValueToJson_Object) { + buffet::IntPropType int_type; + buffet::native_types::Object object; + + object.insert(std::make_pair("width", int_type.CreateValue(640))); + object.insert(std::make_pair("height", int_type.CreateValue(480))); + EXPECT_EQ("{'height':480,'width':640}", + ValueToString(buffet::TypedValueToJson(object, nullptr).get())); +} + +TEST(CommandSchemaUtils, TypedValueFromJson_Bool) { + bool value; + + EXPECT_TRUE(buffet::TypedValueFromJson(CreateValue("true").get(), nullptr, + &value, nullptr)); + EXPECT_TRUE(value); + + EXPECT_TRUE(buffet::TypedValueFromJson(CreateValue("false").get(), nullptr, + &value, nullptr)); + EXPECT_FALSE(value); + + buffet::ErrorPtr error; + EXPECT_FALSE(buffet::TypedValueFromJson(CreateValue("0").get(), nullptr, + &value, &error)); + EXPECT_EQ("type_mismatch", error->GetCode()); + error.reset(); +} + +TEST(CommandSchemaUtils, TypedValueFromJson_Int) { + int value; + + EXPECT_TRUE(buffet::TypedValueFromJson(CreateValue("0").get(), nullptr, + &value, nullptr)); + EXPECT_EQ(0, value); + + EXPECT_TRUE(buffet::TypedValueFromJson(CreateValue("23").get(), nullptr, + &value, nullptr)); + EXPECT_EQ(23, value); + + EXPECT_TRUE(buffet::TypedValueFromJson(CreateValue("-1234").get(), nullptr, + &value, nullptr)); + EXPECT_EQ(-1234, value); + + buffet::ErrorPtr error; + EXPECT_FALSE(buffet::TypedValueFromJson(CreateValue("'abc'").get(), nullptr, + &value, &error)); + EXPECT_EQ("type_mismatch", error->GetCode()); + error.reset(); +} + +TEST(CommandSchemaUtils, TypedValueFromJson_Double) { + double value; + + EXPECT_TRUE(buffet::TypedValueFromJson(CreateValue("0").get(), nullptr, + &value, nullptr)); + EXPECT_DOUBLE_EQ(0.0, value); + EXPECT_TRUE(buffet::TypedValueFromJson(CreateValue("0.0").get(), nullptr, + &value, nullptr)); + EXPECT_DOUBLE_EQ(0.0, value); + + EXPECT_TRUE(buffet::TypedValueFromJson(CreateValue("23").get(), nullptr, + &value, nullptr)); + EXPECT_EQ(23.0, value); + EXPECT_TRUE(buffet::TypedValueFromJson(CreateValue("23.1").get(), nullptr, + &value, nullptr)); + EXPECT_EQ(23.1, value); + + EXPECT_TRUE(buffet::TypedValueFromJson(CreateValue("-1.23E+02").get(), + nullptr, &value, nullptr)); + EXPECT_EQ(-123.0, value); + + buffet::ErrorPtr error; + EXPECT_FALSE(buffet::TypedValueFromJson(CreateValue("'abc'").get(), nullptr, + &value, &error)); + EXPECT_EQ("type_mismatch", error->GetCode()); + error.reset(); +} + +TEST(CommandSchemaUtils, TypedValueFromJson_String) { + std::string value; + + EXPECT_TRUE(buffet::TypedValueFromJson(CreateValue("''").get(), nullptr, + &value, nullptr)); + EXPECT_EQ("", value); + + EXPECT_TRUE(buffet::TypedValueFromJson(CreateValue("'23'").get(), nullptr, + &value, nullptr)); + EXPECT_EQ("23", value); + + EXPECT_TRUE(buffet::TypedValueFromJson(CreateValue("'abc'").get(), nullptr, + &value, nullptr)); + EXPECT_EQ("abc", value); + + buffet::ErrorPtr error; + EXPECT_FALSE(buffet::TypedValueFromJson(CreateValue("12").get(), nullptr, + &value, &error)); + EXPECT_EQ("type_mismatch", error->GetCode()); + error.reset(); +} + +TEST(CommandSchemaUtils, TypedValueFromJson_Object) { + buffet::native_types::Object value; + buffet::ObjectSchema schema; + + auto age_prop = std::make_shared<buffet::IntPropType>(); + age_prop->AddMinMaxConstraint(0, 150); + schema.AddProp("age", age_prop); + + auto name_prop = std::make_shared<buffet::StringPropType>(); + name_prop->AddLengthConstraint(1, 30); + schema.AddProp("name", name_prop); + + EXPECT_TRUE(buffet::TypedValueFromJson( + CreateValue("{'age':20,'name':'Bob'}").get(), &schema, &value, nullptr)); + buffet::native_types::Object value2; + value2.insert(std::make_pair("age", age_prop->CreateValue(20))); + value2.insert(std::make_pair("name", + name_prop->CreateValue(std::string("Bob")))); + EXPECT_EQ(value2, value); + + buffet::ErrorPtr error; + EXPECT_FALSE(buffet::TypedValueFromJson(CreateValue("'abc'").get(), nullptr, + &value, &error)); + EXPECT_EQ("type_mismatch", error->GetCode()); + error.reset(); +}