buffet: Adding GCD command definition schema - phase 1. Initial implementation of GCD command definition schema and parsing command definition provided as JSON. This change introduces the class hierarchy to describe parameter type description (ParamType-derived classes), parameter values (ParamValue-derived classes), constraints, and general object schema which is a collection of parameter definition for an object which corresponds almost directly into a GCD command definition. Object definition parsing from JSON is implemented as well as validation of parameter values with rich error reporting. This is a basis for future command definition implementation and device draft definition for GCD devices. BUG=chromium:374860 TEST=Unit tests pass Change-Id: I82d185b155956ff31a2d2e33f75bec9605ef32ee Reviewed-on: https://chromium-review.googlesource.com/201159 Reviewed-by: Christopher Wiley <wiley@chromium.org> Commit-Queue: Alex Vakulenko <avakulenko@chromium.org> Tested-by: Alex Vakulenko <avakulenko@chromium.org>
diff --git a/buffet/buffet.gyp b/buffet/buffet.gyp index f997532..c16d741 100644 --- a/buffet/buffet.gyp +++ b/buffet/buffet.gyp
@@ -11,10 +11,8 @@ 'libcurl', ], }, - # TODO(sosa): Remove gflags: crbug.com/356745. 'link_settings': { 'libraries': [ - '-lgflags', '-lbase-dbus_test_support-<(libbase_ver)', ], }, @@ -29,6 +27,11 @@ 'sources': [ 'any.cc', 'async_event_sequencer.cc', + 'commands/object_schema.cc', + 'commands/prop_constraints.cc', + 'commands/prop_types.cc', + 'commands/prop_values.cc', + 'commands/schema_constants.cc', 'data_encoding.cc', 'dbus_constants.cc', 'dbus_utils.cc', @@ -80,6 +83,7 @@ 'any_internal_impl_unittest.cc', 'async_event_sequencer_unittest.cc', 'buffet_testrunner.cc', + 'commands/object_schema_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 new file mode 100644 index 0000000..19610be --- /dev/null +++ b/buffet/commands/object_schema.cc
@@ -0,0 +1,251 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "buffet/commands/object_schema.h" + +#include <algorithm> +#include <limits> +#include <set> + +#include <base/json/json_writer.h> +#include <base/logging.h> +#include <base/values.h> + +#include "buffet/commands/prop_types.h" +#include "buffet/commands/prop_values.h" +#include "buffet/commands/schema_constants.h" + +namespace buffet { + +void ObjectSchema::AddProp(const std::string& name, + std::shared_ptr<PropType> prop) { + properties_[name] = prop; +} + +const PropType* ObjectSchema::GetProp(const std::string& name) const { + auto p = properties_.find(name); + return p != properties_.end() ? p->second.get() : nullptr; +} + +std::unique_ptr<base::DictionaryValue> ObjectSchema::ToJson( + bool full_schema, ErrorPtr* error) const { + std::unique_ptr<base::DictionaryValue> value(new base::DictionaryValue); + for (const auto& pair : properties_) { + auto PropDef = pair.second->ToJson(full_schema, error); + if (!PropDef) + return std::unique_ptr<base::DictionaryValue>(); + value->SetWithoutPathExpansion(pair.first, PropDef.release()); + } + return value; +} + +bool ObjectSchema::FromJson(const base::DictionaryValue* value, + const ObjectSchema* 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, + error)) + return false; + iter.Advance(); + } + properties_ = std::move(properties); + return true; +} + +static std::unique_ptr<PropType> CreatePropType(const std::string& type_name, + const std::string& prop_name, + ErrorPtr* error) { + std::unique_ptr<PropType> prop; + ValueType type; + if (PropType::GetTypeFromTypeString(type_name, &type)) + prop = PropType::Create(type); + if (!prop) { + Error::AddToPrintf(error, commands::errors::kDomain, + commands::errors::kUnknownType, + "Unknown type %s for parameter %s", + type_name.c_str(), prop_name.c_str()); + } + return prop; +} + +static bool ErrorInvalidTypeInfo(const std::string& prop_name, + ErrorPtr* error) { + Error::AddToPrintf(error, commands::errors::kDomain, + commands::errors::kNoTypeInfo, + "Unable to determine parameter type for %s", + prop_name.c_str()); + return false; +} + +bool ObjectSchema::PropFromJson(const std::string& prop_name, + const base::Value& value, + const PropType* schemaProp, + 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, + error); + } else if (value.IsType(base::Value::TYPE_LIST)) { + // One of the enumerated types. + return PropFromJsonArray(prop_name, value, schemaProp, properties, + error); + } else if (value.IsType(base::Value::TYPE_DICTIONARY)) { + // Full parameter definition. + return PropFromJsonObject(prop_name, value, schemaProp, properties, + error); + } + Error::AddToPrintf(error, commands::errors::kDomain, + commands::errors::kInvalidPropDef, + "Invalid parameter definition for %s", prop_name.c_str()); + return false; +} + +bool ObjectSchema::PropFromJsonString(const std::string& prop_name, + const base::Value& value, + const PropType* schemaProp, + Properties* properties, + ErrorPtr* error) const { + std::string type_name; + CHECK(value.GetAsString(&type_name)) << "Unable to get string value"; + std::unique_ptr<PropType> prop = CreatePropType(type_name, prop_name, error); + if (!prop) + return false; + base::DictionaryValue empty; + if (!prop->FromJson(&empty, schemaProp, 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) { + std::string type_name; + if (schemaProp) { + type_name = schemaProp->GetTypeAsString(); + } else if (list->GetSize() > 0) { + const base::Value* first_element = nullptr; + if (list->Get(0, &first_element)) { + switch (first_element->GetType()) { + case base::Value::TYPE_BOOLEAN: + type_name = PropType::GetTypeStringFromType(ValueType::Boolean); + break; + case base::Value::TYPE_INTEGER: + type_name = PropType::GetTypeStringFromType(ValueType::Int); + break; + case base::Value::TYPE_DOUBLE: + type_name = PropType::GetTypeStringFromType(ValueType::Double); + break; + case base::Value::TYPE_STRING: + type_name = PropType::GetTypeStringFromType(ValueType::String); + break; + default: + // The rest are unsupported. + break; + } + } + } + return type_name; +} + +bool ObjectSchema::PropFromJsonArray(const std::string& prop_name, + const base::Value& value, + const PropType* schemaProp, + 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); + if (type_name.empty()) + return ErrorInvalidTypeInfo(prop_name, error); + std::unique_ptr<PropType> prop = CreatePropType(type_name, prop_name, error); + if (!prop) + return false; + base::DictionaryValue array_object; + array_object.SetWithoutPathExpansion(commands::attributes::kOneOf_Enum, + list->DeepCopy()); + if (!prop->FromJson(&array_object, schemaProp, 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) { + bool has_min_max = dict->HasKey(commands::attributes::kNumeric_Min) || + dict->HasKey(commands::attributes::kNumeric_Max); + + // Here we are trying to "detect the type and read in the object based on + // the deduced type". Later, we'll verify that this detected type matches + // the expectation of the base schema, if applicable, to make sure we are not + // changing the expected type. This makes the vendor-side (re)definition of + // standard and custom commands behave exactly the same. + // The only problem with this approach was the double-vs-int types. + // If the type is meant to be a double we want to allow its definition as + // "min:0, max:0" instead of just forcing it to be only "min:0.0, max:0.0". + // If we have "minimum" or "maximum", and we have a Double schema object, + // treat this object as a Double (even if both min and max are integers). + if (has_min_max && schemaProp && schemaProp->GetType() == ValueType::Double) + return PropType::GetTypeStringFromType(ValueType::Double); + + // If we have at least one "minimum" or "maximum" that is Double, + // it's a Double. + const base::Value* value = nullptr; + if (dict->Get(commands::attributes::kNumeric_Min, &value) && + value->IsType(base::Value::TYPE_DOUBLE)) + return PropType::GetTypeStringFromType(ValueType::Double); + if (dict->Get(commands::attributes::kNumeric_Max, &value) && + value->IsType(base::Value::TYPE_DOUBLE)) + return PropType::GetTypeStringFromType(ValueType::Double); + + // If we have "minimum" or "maximum", it's an Integer. + if (has_min_max) + return PropType::GetTypeStringFromType(ValueType::Int); + + // If we have "minLength" or "maxLength", it's a String. + if (dict->HasKey(commands::attributes::kString_MinLength) || + dict->HasKey(commands::attributes::kString_MaxLength)) + return PropType::GetTypeStringFromType(ValueType::String); + + // If we have "values", it's an array. + const base::ListValue* list = nullptr; + if (dict->GetListWithoutPathExpansion( + commands::attributes::kOneOf_Enum, &list)) + return DetectArrayType(list, schemaProp); + + return std::string(); +} + +bool ObjectSchema::PropFromJsonObject(const std::string& prop_name, + const base::Value& value, + const PropType* schemaProp, + Properties* properties, + ErrorPtr* error) const { + const base::DictionaryValue* dict = nullptr; + CHECK(value.GetAsDictionary(&dict)) << "Unable to get dictionary value"; + std::string type_name; + if (dict->HasKey(commands::attributes::kType)) { + if (!dict->GetString(commands::attributes::kType, &type_name)) + return ErrorInvalidTypeInfo(prop_name, error); + } else { + type_name = DetectObjectType(dict, schemaProp); + } + if (type_name.empty()) { + if (!schemaProp) + return ErrorInvalidTypeInfo(prop_name, error); + type_name = schemaProp->GetTypeAsString(); + } + std::unique_ptr<PropType> prop = CreatePropType(type_name, prop_name, error); + if (!prop || !prop->FromJson(dict, schemaProp, error)) + return false; + properties->insert(std::make_pair(prop_name, std::move(prop))); + return true; +} + +} // namespace buffet
diff --git a/buffet/commands/object_schema.h b/buffet/commands/object_schema.h new file mode 100644 index 0000000..0e48129 --- /dev/null +++ b/buffet/commands/object_schema.h
@@ -0,0 +1,88 @@ +// 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_OBJECT_SCHEMA_H_ +#define BUFFET_COMMANDS_OBJECT_SCHEMA_H_ + +#include <map> +#include <memory> +#include <string> + +#include <base/basictypes.h> + +#include "buffet/error.h" + +namespace base { +class Value; +class DictionaryValue; +} // namespace base + +namespace buffet { + +class PropType; + +// ObjectSchema is a class representing an object definition in GCD command +// schema. This could represent a GCD command definition, but also it can be +// used when defining custom object types for command properties such as +// output media type (paper) for print command. The schema definition for +// these type of object description is the same. +class ObjectSchema final { + public: + // Properties is a string-to-PropType map representing a list of + // properties defined for a command/object. The key is the parameter + // name and the value is the parameter type definition object. + using Properties = std::map<std::string, std::shared_ptr<PropType>>; + + ObjectSchema() = default; + + // Add a new parameter definition. + void AddProp(const std::string& name, std::shared_ptr<PropType> prop); + // Finds parameter type definition by name. Returns nullptr if not found. + const PropType* GetProp(const std::string& name) const; + // Gets the list of all the properties defined. + const Properties& GetProps() const { return properties_; } + + // 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 + // used as a base schema to inherit omitted properties and constraints from. + bool FromJson(const base::DictionaryValue* value, const ObjectSchema* 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, + 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, + 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, + 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, + Properties* properties, ErrorPtr* error) const; + + // Internal parameter type definition map. + Properties properties_; + DISALLOW_COPY_AND_ASSIGN(ObjectSchema); +}; + +} // namespace buffet + +#endif // BUFFET_COMMANDS_OBJECT_SCHEMA_H_
diff --git a/buffet/commands/object_schema_unittest.cc b/buffet/commands/object_schema_unittest.cc new file mode 100644 index 0000000..fa901ae --- /dev/null +++ b/buffet/commands/object_schema_unittest.cc
@@ -0,0 +1,742 @@ +// 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 <algorithm> +#include <limits> +#include <memory> +#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" + +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(CommandSchema, IntPropType_Empty) { + buffet::IntPropType prop; + EXPECT_TRUE(prop.GetConstraints().empty()); + EXPECT_FALSE(prop.HasOverriddenAttributes()); + EXPECT_FALSE(prop.IsBasedOnSchema()); +} + +TEST(CommandSchema, IntPropType_Types) { + buffet::IntPropType prop; + EXPECT_EQ(nullptr, prop.GetBoolean()); + EXPECT_EQ(&prop, prop.GetInt()); + EXPECT_EQ(nullptr, prop.GetDouble()); + EXPECT_EQ(nullptr, prop.GetString()); +} + +TEST(CommandSchema, IntPropType_ToJson) { + buffet::IntPropType prop; + EXPECT_EQ("'integer'", ValueToString(prop.ToJson(false, nullptr).get())); + EXPECT_EQ("{'type':'integer'}", + ValueToString(prop.ToJson(true, nullptr).get())); + buffet::IntPropType param2; + param2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr); + EXPECT_EQ("{}", ValueToString(param2.ToJson(false, nullptr).get())); + param2.FromJson(CreateDictionaryValue("{'minimum':3}").get(), + &prop, nullptr); + EXPECT_EQ("{'minimum':3}", + ValueToString(param2.ToJson(false, nullptr).get())); + param2.FromJson(CreateDictionaryValue("{'maximum':-7}").get(), + &prop, nullptr); + EXPECT_EQ("{'maximum':-7}", + ValueToString(param2.ToJson(false, nullptr).get())); + param2.FromJson(CreateDictionaryValue("{'minimum':0,'maximum':5}").get(), + &prop, nullptr); + EXPECT_EQ("{'maximum':5,'minimum':0}", + ValueToString(param2.ToJson(false, nullptr).get())); + param2.FromJson(CreateDictionaryValue("{'enum':[1,2,3]}").get(), &prop, + nullptr); + EXPECT_EQ("[1,2,3]", + ValueToString(param2.ToJson(false, nullptr).get())); +} + +TEST(CommandSchema, IntPropType_FromJson) { + buffet::IntPropType prop; + prop.AddMinMaxConstraint(2, 8); + buffet::IntPropType param2; + param2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr); + EXPECT_FALSE(param2.HasOverriddenAttributes()); + EXPECT_TRUE(param2.IsBasedOnSchema()); + EXPECT_EQ(2, prop.GetMinValue()); + EXPECT_EQ(8, prop.GetMaxValue()); + prop.AddMinMaxConstraint(-2, 30); + param2.FromJson(CreateDictionaryValue("{'minimum':7}").get(), + &prop, nullptr); + EXPECT_TRUE(param2.HasOverriddenAttributes()); + EXPECT_TRUE(param2.IsBasedOnSchema()); + EXPECT_EQ(7, param2.GetMinValue()); + EXPECT_EQ(30, param2.GetMaxValue()); + param2.FromJson(CreateDictionaryValue("{'maximum':17}").get(), + &prop, nullptr); + EXPECT_TRUE(param2.HasOverriddenAttributes()); + EXPECT_TRUE(param2.IsBasedOnSchema()); + EXPECT_EQ(-2, param2.GetMinValue()); + EXPECT_EQ(17, param2.GetMaxValue()); + param2.FromJson(CreateDictionaryValue("{'minimum':0,'maximum':6}").get(), + &prop, nullptr); + EXPECT_TRUE(param2.HasOverriddenAttributes()); + EXPECT_TRUE(param2.IsBasedOnSchema()); + EXPECT_EQ(0, param2.GetMinValue()); + EXPECT_EQ(6, param2.GetMaxValue()); +} + +TEST(CommandSchema, IntPropType_Validate) { + buffet::IntPropType prop; + prop.AddMinMaxConstraint(2, 4); + buffet::ErrorPtr error; + EXPECT_FALSE(prop.ValidateValue(CreateValue("-1").get(), &error)); + EXPECT_EQ("out_of_range", error->GetCode()); + error.reset(); + EXPECT_FALSE(prop.ValidateValue(CreateValue("0").get(), &error)); + EXPECT_EQ("out_of_range", error->GetCode()); + error.reset(); + EXPECT_FALSE(prop.ValidateValue(CreateValue("1").get(), &error)); + EXPECT_EQ("out_of_range", error->GetCode()); + error.reset(); + EXPECT_TRUE(prop.ValidateValue(CreateValue("2").get(), &error)); + EXPECT_EQ(nullptr, error.get()); + EXPECT_TRUE(prop.ValidateValue(CreateValue("3").get(), &error)); + EXPECT_EQ(nullptr, error.get()); + EXPECT_TRUE(prop.ValidateValue(CreateValue("4").get(), &error)); + EXPECT_EQ(nullptr, error.get()); + EXPECT_FALSE(prop.ValidateValue(CreateValue("5").get(), &error)); + EXPECT_EQ("out_of_range", error->GetCode()); + error.reset(); + EXPECT_FALSE(prop.ValidateValue(CreateValue("true").get(), &error)); + EXPECT_EQ("type_mismatch", error->GetCode()); + error.reset(); + EXPECT_FALSE(prop.ValidateValue(CreateValue("3.0").get(), &error)); + EXPECT_EQ("type_mismatch", error->GetCode()); + error.reset(); + EXPECT_FALSE(prop.ValidateValue(CreateValue("'3'").get(), &error)); + EXPECT_EQ("type_mismatch", error->GetCode()); +} + +/////////////////////////////////////////////////////////////////////////////// + +TEST(CommandSchema, BoolPropType_Empty) { + buffet::BooleanPropType prop; + EXPECT_TRUE(prop.GetConstraints().empty()); + EXPECT_FALSE(prop.HasOverriddenAttributes()); + EXPECT_FALSE(prop.IsBasedOnSchema()); +} + +TEST(CommandSchema, BoolPropType_Types) { + buffet::BooleanPropType prop; + EXPECT_EQ(nullptr, prop.GetInt()); + EXPECT_EQ(&prop, prop.GetBoolean()); + EXPECT_EQ(nullptr, prop.GetDouble()); + EXPECT_EQ(nullptr, prop.GetString()); +} + +TEST(CommandSchema, BoolPropType_ToJson) { + buffet::BooleanPropType prop; + EXPECT_EQ("'boolean'", ValueToString(prop.ToJson(false, nullptr).get())); + EXPECT_EQ("{'type':'boolean'}", + ValueToString(prop.ToJson(true, nullptr).get())); + buffet::BooleanPropType param2; + param2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr); + EXPECT_EQ("{}", ValueToString(param2.ToJson(false, nullptr).get())); + param2.FromJson(CreateDictionaryValue("{'enum':[true,false]}").get(), &prop, + nullptr); + EXPECT_EQ("[true,false]", ValueToString(param2.ToJson(false, nullptr).get())); + EXPECT_EQ("{'enum':[true,false],'type':'boolean'}", + ValueToString(param2.ToJson(true, nullptr).get())); +} + +TEST(CommandSchema, BoolPropType_FromJson) { + buffet::BooleanPropType prop; + prop.FromJson(CreateDictionaryValue("{'enum':[true]}").get(), &prop, + nullptr); + buffet::BooleanPropType param2; + param2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr); + EXPECT_FALSE(param2.HasOverriddenAttributes()); + EXPECT_TRUE(param2.IsBasedOnSchema()); + EXPECT_EQ(std::vector<bool>{true}, prop.GetOneOfValues()); +} + +TEST(CommandSchema, BoolPropType_Validate) { + buffet::BooleanPropType prop; + prop.FromJson(CreateDictionaryValue("{'enum':[true]}").get(), &prop, + nullptr); + buffet::ErrorPtr error; + EXPECT_FALSE(prop.ValidateValue(CreateValue("false").get(), &error)); + EXPECT_EQ("out_of_range", error->GetCode()); + error.reset(); + EXPECT_TRUE(prop.ValidateValue(CreateValue("true").get(), &error)); + error.reset(); + EXPECT_FALSE(prop.ValidateValue(CreateValue("1").get(), &error)); + EXPECT_EQ("type_mismatch", error->GetCode()); + error.reset(); + EXPECT_FALSE(prop.ValidateValue(CreateValue("3.0").get(), &error)); + EXPECT_EQ("type_mismatch", error->GetCode()); + error.reset(); + EXPECT_FALSE(prop.ValidateValue(CreateValue("'3'").get(), &error)); + EXPECT_EQ("type_mismatch", error->GetCode()); +} + +/////////////////////////////////////////////////////////////////////////////// + +TEST(CommandSchema, DoublePropType_Empty) { + buffet::DoublePropType prop; + EXPECT_DOUBLE_EQ(std::numeric_limits<double>::lowest(), prop.GetMinValue()); + EXPECT_DOUBLE_EQ((std::numeric_limits<double>::max)(), prop.GetMaxValue()); + EXPECT_FALSE(prop.HasOverriddenAttributes()); + EXPECT_FALSE(prop.IsBasedOnSchema()); +} + +TEST(CommandSchema, DoublePropType_Types) { + buffet::DoublePropType prop; + EXPECT_EQ(nullptr, prop.GetInt()); + EXPECT_EQ(nullptr, prop.GetBoolean()); + EXPECT_EQ(&prop, prop.GetDouble()); + EXPECT_EQ(nullptr, prop.GetString()); +} + +TEST(CommandSchema, DoublePropType_ToJson) { + buffet::DoublePropType prop; + EXPECT_EQ("'number'", ValueToString(prop.ToJson(false, nullptr).get())); + EXPECT_EQ("{'type':'number'}", + ValueToString(prop.ToJson(true, nullptr).get())); + buffet::DoublePropType param2; + param2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr); + EXPECT_EQ("{}", ValueToString(param2.ToJson(false, nullptr).get())); + param2.FromJson(CreateDictionaryValue("{'minimum':3}").get(), &prop, + nullptr); + EXPECT_EQ("{'minimum':3.0}", + ValueToString(param2.ToJson(false, nullptr).get())); + param2.FromJson(CreateDictionaryValue("{'maximum':-7}").get(), &prop, + nullptr); + EXPECT_EQ("{'maximum':-7.0}", + ValueToString(param2.ToJson(false, nullptr).get())); + param2.FromJson(CreateDictionaryValue("{'minimum':0,'maximum':5}").get(), + &prop, nullptr); + EXPECT_EQ("{'maximum':5.0,'minimum':0.0}", + ValueToString(param2.ToJson(false, nullptr).get())); +} + +TEST(CommandSchema, DoublePropType_FromJson) { + buffet::DoublePropType prop; + prop.AddMinMaxConstraint(2.5, 8.7); + buffet::DoublePropType param2; + param2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr); + EXPECT_FALSE(param2.HasOverriddenAttributes()); + EXPECT_TRUE(param2.IsBasedOnSchema()); + EXPECT_DOUBLE_EQ(2.5, prop.GetMinValue()); + EXPECT_DOUBLE_EQ(8.7, prop.GetMaxValue()); + prop.AddMinMaxConstraint(-2.2, 30.4); + param2.FromJson(CreateDictionaryValue("{'minimum':7}").get(), &prop, + nullptr); + EXPECT_TRUE(param2.HasOverriddenAttributes()); + EXPECT_TRUE(param2.IsBasedOnSchema()); + EXPECT_DOUBLE_EQ(7.0, param2.GetMinValue()); + EXPECT_DOUBLE_EQ(30.4, param2.GetMaxValue()); + param2.FromJson(CreateDictionaryValue("{'maximum':17.2}").get(), &prop, + nullptr); + EXPECT_TRUE(param2.HasOverriddenAttributes()); + EXPECT_TRUE(param2.IsBasedOnSchema()); + EXPECT_DOUBLE_EQ(-2.2, param2.GetMinValue()); + EXPECT_DOUBLE_EQ(17.2, param2.GetMaxValue()); + param2.FromJson(CreateDictionaryValue("{'minimum':0,'maximum':6.1}").get(), + &prop, nullptr); + EXPECT_TRUE(param2.HasOverriddenAttributes()); + EXPECT_TRUE(param2.IsBasedOnSchema()); + EXPECT_DOUBLE_EQ(0.0, param2.GetMinValue()); + EXPECT_DOUBLE_EQ(6.1, param2.GetMaxValue()); +} + +TEST(CommandSchema, DoublePropType_Validate) { + buffet::DoublePropType prop; + prop.AddMinMaxConstraint(-1.2, 1.3); + buffet::ErrorPtr error; + EXPECT_FALSE(prop.ValidateValue(CreateValue("-2").get(), &error)); + EXPECT_EQ("out_of_range", error->GetCode()); + error.reset(); + EXPECT_FALSE(prop.ValidateValue(CreateValue("-1.3").get(), &error)); + EXPECT_EQ("out_of_range", error->GetCode()); + error.reset(); + EXPECT_TRUE(prop.ValidateValue(CreateValue("-1.2").get(), &error)); + EXPECT_EQ(nullptr, error.get()); + EXPECT_TRUE(prop.ValidateValue(CreateValue("0.0").get(), &error)); + EXPECT_EQ(nullptr, error.get()); + EXPECT_TRUE(prop.ValidateValue(CreateValue("1.3").get(), &error)); + EXPECT_EQ(nullptr, error.get()); + EXPECT_FALSE(prop.ValidateValue(CreateValue("1.31").get(), &error)); + EXPECT_EQ("out_of_range", error->GetCode()); + error.reset(); + EXPECT_FALSE(prop.ValidateValue(CreateValue("true").get(), &error)); + EXPECT_EQ("type_mismatch", error->GetCode()); + error.reset(); + EXPECT_FALSE(prop.ValidateValue(CreateValue("'0.0'").get(), &error)); + EXPECT_EQ("type_mismatch", error->GetCode()); +} + +/////////////////////////////////////////////////////////////////////////////// + +TEST(CommandSchema, StringPropType_Empty) { + buffet::StringPropType prop; + EXPECT_EQ(0, prop.GetMinLength()); + EXPECT_EQ((std::numeric_limits<int>::max)(), prop.GetMaxLength()); + EXPECT_FALSE(prop.HasOverriddenAttributes()); + EXPECT_FALSE(prop.IsBasedOnSchema()); +} + +TEST(CommandSchema, StringPropType_Types) { + buffet::StringPropType prop; + EXPECT_EQ(nullptr, prop.GetInt()); + EXPECT_EQ(nullptr, prop.GetBoolean()); + EXPECT_EQ(nullptr, prop.GetDouble()); + EXPECT_EQ(&prop, prop.GetString()); +} + +TEST(CommandSchema, StringPropType_ToJson) { + buffet::StringPropType prop; + EXPECT_EQ("'string'", ValueToString(prop.ToJson(false, nullptr).get())); + EXPECT_EQ("{'type':'string'}", + ValueToString(prop.ToJson(true, nullptr).get())); + buffet::StringPropType param2; + param2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr); + EXPECT_EQ("{}", ValueToString(param2.ToJson(false, nullptr).get())); + param2.FromJson(CreateDictionaryValue("{'minLength':3}").get(), &prop, + nullptr); + EXPECT_EQ("{'minLength':3}", + ValueToString(param2.ToJson(false, nullptr).get())); + param2.FromJson(CreateDictionaryValue("{'maxLength':7}").get(), &prop, + nullptr); + EXPECT_EQ("{'maxLength':7}", + ValueToString(param2.ToJson(false, nullptr).get())); + param2.FromJson(CreateDictionaryValue("{'minLength':0,'maxLength':5}").get(), + &prop, nullptr); + EXPECT_EQ("{'maxLength':5,'minLength':0}", + ValueToString(param2.ToJson(false, nullptr).get())); +} + +TEST(CommandSchema, StringPropType_FromJson) { + buffet::StringPropType prop; + prop.AddLengthConstraint(2, 8); + buffet::StringPropType param2; + param2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr); + EXPECT_FALSE(param2.HasOverriddenAttributes()); + EXPECT_TRUE(param2.IsBasedOnSchema()); + EXPECT_EQ(2, prop.GetMinLength()); + EXPECT_EQ(8, prop.GetMaxLength()); + prop.AddLengthConstraint(3, 5); + param2.FromJson(CreateDictionaryValue("{'minLength':4}").get(), &prop, + nullptr); + EXPECT_TRUE(param2.HasOverriddenAttributes()); + EXPECT_TRUE(param2.IsBasedOnSchema()); + EXPECT_EQ(4, param2.GetMinLength()); + EXPECT_EQ(5, param2.GetMaxLength()); + param2.FromJson(CreateDictionaryValue("{'maxLength':8}").get(), &prop, + nullptr); + EXPECT_TRUE(param2.HasOverriddenAttributes()); + EXPECT_TRUE(param2.IsBasedOnSchema()); + EXPECT_EQ(3, param2.GetMinLength()); + EXPECT_EQ(8, param2.GetMaxLength()); + param2.FromJson(CreateDictionaryValue( + "{'minLength':1,'maxLength':7}").get(), &prop, nullptr); + EXPECT_TRUE(param2.HasOverriddenAttributes()); + EXPECT_TRUE(param2.IsBasedOnSchema()); + EXPECT_EQ(1, param2.GetMinLength()); + EXPECT_EQ(7, param2.GetMaxLength()); +} + +TEST(CommandSchema, StringPropType_Validate) { + buffet::StringPropType prop; + prop.AddLengthConstraint(1, 3); + buffet::ErrorPtr error; + EXPECT_FALSE(prop.ValidateValue(CreateValue("''").get(), &error)); + EXPECT_EQ("out_of_range", error->GetCode()); + error.reset(); + prop.AddLengthConstraint(2, 3); + EXPECT_FALSE(prop.ValidateValue(CreateValue("''").get(), &error)); + EXPECT_EQ("out_of_range", error->GetCode()); + error.reset(); + EXPECT_FALSE(prop.ValidateValue(CreateValue("'a'").get(), &error)); + EXPECT_EQ("out_of_range", error->GetCode()); + error.reset(); + EXPECT_TRUE(prop.ValidateValue(CreateValue("'ab'").get(), &error)); + EXPECT_EQ(nullptr, error.get()); + EXPECT_TRUE(prop.ValidateValue(CreateValue("'abc'").get(), &error)); + EXPECT_EQ(nullptr, error.get()); + EXPECT_FALSE(prop.ValidateValue(CreateValue("'abcd'").get(), &error)); + EXPECT_EQ("out_of_range", error->GetCode()); + error.reset(); + + prop.FromJson(CreateDictionaryValue("{'enum':['abc','def','xyz!!']}").get(), + nullptr, &error); + EXPECT_TRUE(prop.ValidateValue(CreateValue("'abc'").get(), &error)); + EXPECT_TRUE(prop.ValidateValue(CreateValue("'def'").get(), &error)); + EXPECT_TRUE(prop.ValidateValue(CreateValue("'xyz!!'").get(), &error)); + EXPECT_FALSE(prop.ValidateValue(CreateValue("'xyz'").get(), &error)); + EXPECT_EQ("out_of_range", error->GetCode()); + error.reset(); +} + +TEST(CommandSchema, ObjectSchema_FromJson_Shorthand_TypeName) { + buffet::ObjectSchema schema; + const char* schema_str = "{" + "'param1':'integer'," + "'param2':'number'," + "'param3':'string'" + "}"; + EXPECT_TRUE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr, + nullptr)); + EXPECT_EQ(buffet::ValueType::Int, schema.GetProp("param1")->GetType()); + EXPECT_EQ(buffet::ValueType::Double, schema.GetProp("param2")->GetType()); + EXPECT_EQ(buffet::ValueType::String, schema.GetProp("param3")->GetType()); + EXPECT_EQ("integer", schema.GetProp("param1")->GetTypeAsString()); + EXPECT_EQ("number", schema.GetProp("param2")->GetTypeAsString()); + EXPECT_EQ("string", schema.GetProp("param3")->GetTypeAsString()); + EXPECT_EQ(nullptr, schema.GetProp("param4")); + + int min_int = (std::numeric_limits<int>::min)(); + int max_int = (std::numeric_limits<int>::max)(); + double min_dbl = (std::numeric_limits<double>::lowest)(); + double max_dbl = (std::numeric_limits<double>::max)(); + EXPECT_EQ(min_int, schema.GetProp("param1")->GetInt()->GetMinValue()); + EXPECT_EQ(max_int, schema.GetProp("param1")->GetInt()->GetMaxValue()); + EXPECT_EQ(min_dbl, schema.GetProp("param2")->GetDouble()->GetMinValue()); + EXPECT_EQ(max_dbl, schema.GetProp("param2")->GetDouble()->GetMaxValue()); + EXPECT_EQ(0, schema.GetProp("param3")->GetString()->GetMinLength()); + EXPECT_EQ(max_int, schema.GetProp("param3")->GetString()->GetMaxLength()); +} + +TEST(CommandSchema, ObjectSchema_FromJson_Full_TypeName) { + buffet::ObjectSchema schema; + const char* schema_str = "{" + "'param1':{'type':'integer'}," + "'param2':{'type':'number'}," + "'param3':{'type':'string'}" + "}"; + EXPECT_TRUE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr, + nullptr)); + EXPECT_EQ(buffet::ValueType::Int, schema.GetProp("param1")->GetType()); + EXPECT_EQ(buffet::ValueType::Double, schema.GetProp("param2")->GetType()); + EXPECT_EQ(buffet::ValueType::String, schema.GetProp("param3")->GetType()); + EXPECT_EQ("integer", schema.GetProp("param1")->GetTypeAsString()); + EXPECT_EQ("number", schema.GetProp("param2")->GetTypeAsString()); + EXPECT_EQ("string", schema.GetProp("param3")->GetTypeAsString()); + EXPECT_EQ(nullptr, schema.GetProp("param4")); + + int min_int = (std::numeric_limits<int>::min)(); + int max_int = (std::numeric_limits<int>::max)(); + double min_dbl = (std::numeric_limits<double>::lowest)(); + double max_dbl = (std::numeric_limits<double>::max)(); + EXPECT_EQ(min_int, schema.GetProp("param1")->GetInt()->GetMinValue()); + EXPECT_EQ(max_int, schema.GetProp("param1")->GetInt()->GetMaxValue()); + EXPECT_EQ(min_dbl, schema.GetProp("param2")->GetDouble()->GetMinValue()); + EXPECT_EQ(max_dbl, schema.GetProp("param2")->GetDouble()->GetMaxValue()); + EXPECT_EQ(0, schema.GetProp("param3")->GetString()->GetMinLength()); + EXPECT_EQ(max_int, schema.GetProp("param3")->GetString()->GetMaxLength()); +} + +TEST(CommandSchema, ObjectSchema_FromJson_Shorthand_TypeDeduction_Scalar) { + buffet::ObjectSchema schema; + const char* schema_str = "{" + "'param1' :{'minimum':2}," + "'param2' :{'maximum':10}," + "'param3' :{'maximum':8, 'minimum':2}," + "'param4' :{'minimum':2.1}," + "'param5' :{'maximum':10.1}," + "'param6' :{'maximum':8.1, 'minimum':3.1}," + "'param7' :{'maximum':8, 'minimum':3.1}," + "'param8' :{'maximum':8.1, 'minimum':3}," + "'param9' :{'minLength':2}," + "'param10':{'maxLength':10}," + "'param11':{'maxLength':8, 'minLength':3}" + "}"; + EXPECT_TRUE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr, + nullptr)); + EXPECT_EQ("integer", schema.GetProp("param1")->GetTypeAsString()); + EXPECT_EQ("integer", schema.GetProp("param2")->GetTypeAsString()); + EXPECT_EQ("integer", schema.GetProp("param3")->GetTypeAsString()); + EXPECT_EQ("number", schema.GetProp("param4")->GetTypeAsString()); + EXPECT_EQ("number", schema.GetProp("param5")->GetTypeAsString()); + EXPECT_EQ("number", schema.GetProp("param6")->GetTypeAsString()); + EXPECT_EQ("number", schema.GetProp("param7")->GetTypeAsString()); + EXPECT_EQ("number", schema.GetProp("param8")->GetTypeAsString()); + EXPECT_EQ("string", schema.GetProp("param9")->GetTypeAsString()); + EXPECT_EQ("string", schema.GetProp("param10")->GetTypeAsString()); + EXPECT_EQ("string", schema.GetProp("param11")->GetTypeAsString()); + + int min_int = (std::numeric_limits<int>::min)(); + int max_int = (std::numeric_limits<int>::max)(); + double min_dbl = (std::numeric_limits<double>::lowest)(); + double max_dbl = (std::numeric_limits<double>::max)(); + EXPECT_EQ(2, schema.GetProp("param1")->GetInt()->GetMinValue()); + EXPECT_EQ(max_int, schema.GetProp("param1")->GetInt()->GetMaxValue()); + EXPECT_EQ(min_int, schema.GetProp("param2")->GetInt()->GetMinValue()); + EXPECT_EQ(10, schema.GetProp("param2")->GetInt()->GetMaxValue()); + EXPECT_EQ(2, schema.GetProp("param3")->GetInt()->GetMinValue()); + EXPECT_EQ(8, schema.GetProp("param3")->GetInt()->GetMaxValue()); + EXPECT_DOUBLE_EQ(2.1, schema.GetProp("param4")->GetDouble()->GetMinValue()); + EXPECT_DOUBLE_EQ(max_dbl, + schema.GetProp("param4")->GetDouble()->GetMaxValue()); + EXPECT_DOUBLE_EQ(min_dbl, + schema.GetProp("param5")->GetDouble()->GetMinValue()); + EXPECT_DOUBLE_EQ(10.1, schema.GetProp("param5")->GetDouble()->GetMaxValue()); + EXPECT_DOUBLE_EQ(3.1, schema.GetProp("param6")->GetDouble()->GetMinValue()); + EXPECT_DOUBLE_EQ(8.1, schema.GetProp("param6")->GetDouble()->GetMaxValue()); + EXPECT_DOUBLE_EQ(3.1, schema.GetProp("param7")->GetDouble()->GetMinValue()); + EXPECT_DOUBLE_EQ(8.0, schema.GetProp("param7")->GetDouble()->GetMaxValue()); + EXPECT_DOUBLE_EQ(3.0, schema.GetProp("param8")->GetDouble()->GetMinValue()); + EXPECT_DOUBLE_EQ(8.1, schema.GetProp("param8")->GetDouble()->GetMaxValue()); + EXPECT_EQ(2, schema.GetProp("param9")->GetString()->GetMinLength()); + EXPECT_EQ(max_int, schema.GetProp("param9")->GetString()->GetMaxLength()); + EXPECT_EQ(0, schema.GetProp("param10")->GetString()->GetMinLength()); + EXPECT_EQ(10, schema.GetProp("param10")->GetString()->GetMaxLength()); + EXPECT_EQ(3, schema.GetProp("param11")->GetString()->GetMinLength()); + EXPECT_EQ(8, schema.GetProp("param11")->GetString()->GetMaxLength()); +} + +TEST(CommandSchema, ObjectSchema_FromJson_Shorthand_TypeDeduction_Array) { + buffet::ObjectSchema schema; + const char* schema_str = "{" + "'param1' :[0,1,2,3]," + "'param2' :[0.0,1.1,2.2]," + "'param3' :['id1', 'id2']," + "'param4' :{'enum':[1,2,3]}," + "'param5' :{'enum':[-1.1,2.2,3]}," + "'param6' :{'enum':['id0', 'id1']}," + "'param7' :{'type':'integer', 'enum':[1,2,3]}," + "'param8' :{'type':'number', 'enum':[1,2,3]}," + "'param9' :{'type':'number', 'enum':[]}," + "'param10':{'type':'integer', 'enum':[]}" + "}"; + EXPECT_TRUE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr, + nullptr)); + EXPECT_EQ("integer", schema.GetProp("param1")->GetTypeAsString()); + EXPECT_EQ("number", schema.GetProp("param2")->GetTypeAsString()); + EXPECT_EQ("string", schema.GetProp("param3")->GetTypeAsString()); + EXPECT_EQ("integer", schema.GetProp("param4")->GetTypeAsString()); + EXPECT_EQ("number", schema.GetProp("param5")->GetTypeAsString()); + EXPECT_EQ("string", schema.GetProp("param6")->GetTypeAsString()); + EXPECT_EQ("integer", schema.GetProp("param7")->GetTypeAsString()); + EXPECT_EQ("number", schema.GetProp("param8")->GetTypeAsString()); + EXPECT_EQ("number", schema.GetProp("param9")->GetTypeAsString()); + EXPECT_EQ("integer", schema.GetProp("param10")->GetTypeAsString()); + + EXPECT_EQ(4, schema.GetProp("param1")->GetInt()->GetOneOfValues().size()); + EXPECT_EQ(3, schema.GetProp("param2")->GetDouble()->GetOneOfValues().size()); + EXPECT_EQ(2, schema.GetProp("param3")->GetString()->GetOneOfValues().size()); + EXPECT_EQ(3, schema.GetProp("param4")->GetInt()->GetOneOfValues().size()); + EXPECT_EQ(3, schema.GetProp("param5")->GetDouble()->GetOneOfValues().size()); + EXPECT_EQ(2, schema.GetProp("param6")->GetString()->GetOneOfValues().size()); + EXPECT_EQ(3, schema.GetProp("param7")->GetInt()->GetOneOfValues().size()); + EXPECT_EQ(3, schema.GetProp("param8")->GetDouble()->GetOneOfValues().size()); + EXPECT_EQ(0, schema.GetProp("param9")->GetDouble()->GetOneOfValues().size()); + EXPECT_EQ(0, schema.GetProp("param10")->GetInt()->GetOneOfValues().size()); + + EXPECT_EQ(std::vector<int>({0, 1, 2, 3}), + schema.GetProp("param1")->GetInt()->GetOneOfValues()); + EXPECT_EQ(std::vector<double>({0.0, 1.1, 2.2}), + schema.GetProp("param2")->GetDouble()->GetOneOfValues()); + EXPECT_EQ(std::vector<std::string>({"id1", "id2"}), + schema.GetProp("param3")->GetString()->GetOneOfValues()); + + EXPECT_EQ(std::vector<int>({1, 2, 3}), + schema.GetProp("param4")->GetInt()->GetOneOfValues()); + EXPECT_EQ(std::vector<double>({-1.1, 2.2, 3.0}), + schema.GetProp("param5")->GetDouble()->GetOneOfValues()); + EXPECT_EQ(std::vector<std::string>({"id0", "id1"}), + schema.GetProp("param6")->GetString()->GetOneOfValues()); + EXPECT_EQ(std::vector<int>({1, 2, 3}), + schema.GetProp("param7")->GetInt()->GetOneOfValues()); + EXPECT_EQ(std::vector<double>({1.0, 2.0, 3.0}), + schema.GetProp("param8")->GetDouble()->GetOneOfValues()); +} + +TEST(CommandSchema, ObjectSchema_FromJson_Inheritance) { + const char* base_schema_str = "{" + "'param0' :{'minimum':1, 'maximum':5}," + "'param1' :{'minimum':1, 'maximum':5}," + "'param2' :{'minimum':1, 'maximum':5}," + "'param3' :{'minimum':1, 'maximum':5}," + "'param4' :{'minimum':1, 'maximum':5}," + "'param5' :{'minimum':1.1, 'maximum':5.5}," + "'param6' :{'minimum':1.1, 'maximum':5.5}," + "'param7' :{'minimum':1.1, 'maximum':5.5}," + "'param8' :{'minimum':1.1, 'maximum':5.5}," + "'param9' :{'minLength':1, 'maxLength':5}," + "'param10':{'minLength':1, 'maxLength':5}," + "'param11':{'minLength':1, 'maxLength':5}," + "'param12':{'minLength':1, 'maxLength':5}," + "'param13':[1,2,3]," + "'param14':[1,2,3]," + "'param15':[1.1,2.2,3.3]," + "'param16':[1.1,2.2,3.3]," + "'param17':['id1', 'id2']," + "'param18':['id1', 'id2']," + "'param19':{'minimum':1, 'maximum':5}" + "}"; + buffet::ObjectSchema base_schema; + EXPECT_TRUE(base_schema.FromJson(CreateDictionaryValue(base_schema_str).get(), + nullptr, nullptr)); + const char* schema_str = "{" + "'param1' :{}," + "'param2' :{'minimum':2}," + "'param3' :{'maximum':9}," + "'param4' :{'minimum':2, 'maximum':9}," + "'param5' :{}," + "'param6' :{'minimum':2.2}," + "'param7' :{'maximum':9.9}," + "'param8' :{'minimum':2.2, 'maximum':9.9}," + "'param9' :{}," + "'param10':{'minLength':3}," + "'param11':{'maxLength':8}," + "'param12':{'minLength':3, 'maxLength':8}," + "'param13':{}," + "'param14':[1,2,3,4]," + "'param15':{}," + "'param16':[1.1,2.2,3.3,4.4]," + "'param17':{}," + "'param18':['id1', 'id3']," + "'param19':{}" + "}"; + buffet::ObjectSchema schema; + EXPECT_TRUE(schema.FromJson(CreateDictionaryValue(schema_str).get(), + &base_schema, nullptr)); + EXPECT_EQ(nullptr, schema.GetProp("param0")); + EXPECT_NE(nullptr, schema.GetProp("param1")); + EXPECT_EQ("integer", schema.GetProp("param1")->GetTypeAsString()); + EXPECT_EQ(1, schema.GetProp("param1")->GetInt()->GetMinValue()); + EXPECT_EQ(5, schema.GetProp("param1")->GetInt()->GetMaxValue()); + EXPECT_EQ("integer", schema.GetProp("param2")->GetTypeAsString()); + EXPECT_EQ(2, schema.GetProp("param2")->GetInt()->GetMinValue()); + EXPECT_EQ(5, schema.GetProp("param2")->GetInt()->GetMaxValue()); + EXPECT_EQ("integer", schema.GetProp("param3")->GetTypeAsString()); + EXPECT_EQ(1, schema.GetProp("param3")->GetInt()->GetMinValue()); + EXPECT_EQ(9, schema.GetProp("param3")->GetInt()->GetMaxValue()); + EXPECT_EQ("integer", schema.GetProp("param4")->GetTypeAsString()); + EXPECT_EQ(2, schema.GetProp("param4")->GetInt()->GetMinValue()); + EXPECT_EQ(9, schema.GetProp("param4")->GetInt()->GetMaxValue()); + EXPECT_EQ("number", schema.GetProp("param5")->GetTypeAsString()); + EXPECT_EQ(1.1, schema.GetProp("param5")->GetDouble()->GetMinValue()); + EXPECT_EQ(5.5, schema.GetProp("param5")->GetDouble()->GetMaxValue()); + EXPECT_EQ("number", schema.GetProp("param6")->GetTypeAsString()); + EXPECT_EQ(2.2, schema.GetProp("param6")->GetDouble()->GetMinValue()); + EXPECT_EQ(5.5, schema.GetProp("param6")->GetDouble()->GetMaxValue()); + EXPECT_EQ("number", schema.GetProp("param7")->GetTypeAsString()); + EXPECT_EQ(1.1, schema.GetProp("param7")->GetDouble()->GetMinValue()); + EXPECT_EQ(9.9, schema.GetProp("param7")->GetDouble()->GetMaxValue()); + EXPECT_EQ("number", schema.GetProp("param8")->GetTypeAsString()); + EXPECT_EQ(2.2, schema.GetProp("param8")->GetDouble()->GetMinValue()); + EXPECT_EQ(9.9, schema.GetProp("param8")->GetDouble()->GetMaxValue()); + EXPECT_EQ("string", schema.GetProp("param9")->GetTypeAsString()); + EXPECT_EQ(1, schema.GetProp("param9")->GetString()->GetMinLength()); + EXPECT_EQ(5, schema.GetProp("param9")->GetString()->GetMaxLength()); + EXPECT_EQ("string", schema.GetProp("param10")->GetTypeAsString()); + EXPECT_EQ(3, schema.GetProp("param10")->GetString()->GetMinLength()); + EXPECT_EQ(5, schema.GetProp("param10")->GetString()->GetMaxLength()); + EXPECT_EQ("string", schema.GetProp("param11")->GetTypeAsString()); + EXPECT_EQ(1, schema.GetProp("param11")->GetString()->GetMinLength()); + EXPECT_EQ(8, schema.GetProp("param11")->GetString()->GetMaxLength()); + EXPECT_EQ("string", schema.GetProp("param12")->GetTypeAsString()); + EXPECT_EQ(3, schema.GetProp("param12")->GetString()->GetMinLength()); + EXPECT_EQ(8, schema.GetProp("param12")->GetString()->GetMaxLength()); + EXPECT_EQ("integer", schema.GetProp("param13")->GetTypeAsString()); + EXPECT_EQ(std::vector<int>({1, 2, 3}), + schema.GetProp("param13")->GetInt()->GetOneOfValues()); + EXPECT_EQ("integer", schema.GetProp("param14")->GetTypeAsString()); + EXPECT_EQ(std::vector<int>({1, 2, 3, 4}), + schema.GetProp("param14")->GetInt()->GetOneOfValues()); + EXPECT_EQ("number", schema.GetProp("param15")->GetTypeAsString()); + EXPECT_EQ(std::vector<double>({1.1, 2.2, 3.3}), + schema.GetProp("param15")->GetDouble()->GetOneOfValues()); + EXPECT_EQ("number", schema.GetProp("param16")->GetTypeAsString()); + EXPECT_EQ(std::vector<double>({1.1, 2.2, 3.3, 4.4}), + schema.GetProp("param16")->GetDouble()->GetOneOfValues()); + EXPECT_EQ("string", schema.GetProp("param17")->GetTypeAsString()); + EXPECT_EQ(std::vector<std::string>({"id1", "id2"}), + schema.GetProp("param17")->GetString()->GetOneOfValues()); + EXPECT_EQ("string", schema.GetProp("param18")->GetTypeAsString()); + EXPECT_EQ(std::vector<std::string>({"id1", "id3"}), + schema.GetProp("param18")->GetString()->GetOneOfValues()); + EXPECT_EQ("integer", schema.GetProp("param19")->GetTypeAsString()); + EXPECT_EQ(1, schema.GetProp("param19")->GetInt()->GetMinValue()); + EXPECT_EQ(5, schema.GetProp("param19")->GetInt()->GetMaxValue()); +} + +TEST(CommandSchema, ObjectSchema_FromJson_BaseSchema_Failures) { + buffet::ObjectSchema schema; + buffet::ErrorPtr error; + const char* schema_str = "{" + "'param1':{}" + "}"; + EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr, + &error)); + EXPECT_EQ("no_type_info", error->GetFirstError()->GetCode()); + error.reset(); + + schema_str = "{" + "'param1':{'type':'foo'}" + "}"; + EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr, + &error)); + EXPECT_EQ("unknown_type", error->GetFirstError()->GetCode()); + error.reset(); + + schema_str = "{" + "'param1':[]" + "}"; + EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr, + &error)); + EXPECT_EQ("no_type_info", error->GetFirstError()->GetCode()); + error.reset(); + + schema_str = "{" + "'param1':{'minimum':'foo'}" + "}"; + EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr, + &error)); + EXPECT_EQ("type_mismatch", error->GetFirstError()->GetCode()); + error.reset(); + + schema_str = "{" + "'param1':[1,2.2]" + "}"; + EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr, + &error)); + EXPECT_EQ("type_mismatch", error->GetFirstError()->GetCode()); + error.reset(); +} +
diff --git a/buffet/commands/prop_constraints.cc b/buffet/commands/prop_constraints.cc new file mode 100644 index 0000000..2376588 --- /dev/null +++ b/buffet/commands/prop_constraints.cc
@@ -0,0 +1,139 @@ +// 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/prop_constraints.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() {} + +bool Constraint::ReportErrorLessThan( + ErrorPtr* error, const std::string& val, const std::string& limit) { + Error::AddToPrintf(error, commands::errors::kDomain, + commands::errors::kOutOfRange, + "Value %s is out of range. It must not be less than %s", + val.c_str(), limit.c_str()); + return false; +} + +bool Constraint::ReportErrorGreaterThan( + ErrorPtr* error, const std::string& val, const std::string& limit) { + Error::AddToPrintf(error, commands::errors::kDomain, + commands::errors::kOutOfRange, + "Value %s is out of range. It must not be greater than %s", + val.c_str(), limit.c_str()); + return false; +} + +bool Constraint::ReportErrorNotOneOf( + ErrorPtr* error, const std::string& val, + const std::vector<std::string>& values) { + Error::AddToPrintf(error, commands::errors::kDomain, + commands::errors::kOutOfRange, + "Value %s is invalid. Expected one of [%s]", + val.c_str(), string_utils::Join(',', values).c_str()); + return false; +} + +bool Constraint::AddToJsonDict(base::DictionaryValue* dict, + bool overridden_only, + ErrorPtr* error) const { + if (!overridden_only || HasOverriddenAttributes()) { + auto value = ToJson(error); + if (!value) + return false; + dict->SetWithoutPathExpansion(GetDictKey(), value.release()); + } + return true; +} + +// ConstraintStringLength ----------------------------------------------------- +ConstraintStringLength::ConstraintStringLength( + const InheritableAttribute<int>& limit) : limit_(limit) {} +ConstraintStringLength::ConstraintStringLength(int limit) : limit_(limit) {} + +bool ConstraintStringLength::HasOverriddenAttributes() const { + return !limit_.is_inherited; +} + +std::unique_ptr<base::Value> ConstraintStringLength::ToJson( + ErrorPtr* error) const { + return TypedValueToJson(limit_.value); +} + +// ConstraintStringLengthMin -------------------------------------------------- +ConstraintStringLengthMin::ConstraintStringLengthMin( + const InheritableAttribute<int>& limit) : ConstraintStringLength(limit) {} +ConstraintStringLengthMin::ConstraintStringLengthMin(int limit) + : ConstraintStringLength(limit) {} + +bool ConstraintStringLengthMin::Validate(const Any& value, + ErrorPtr* error) const { + std::string str = value.Get<std::string>(); + int length = static_cast<int>(str.size()); + if (length < limit_.value) { + if (limit_.value == 1) { + Error::AddTo(error, commands::errors::kDomain, + commands::errors::kOutOfRange, "String must not be empty"); + } else { + Error::AddToPrintf(error, commands::errors::kDomain, + commands::errors::kOutOfRange, + "String must be at least %d characters long, " + "actual length of string '%s' is %d", limit_.value, + str.c_str(), length); + } + return false; + } + return true; +} + +std::shared_ptr<Constraint> + ConstraintStringLengthMin::CloneAsInherited() const { + return std::make_shared<ConstraintStringLengthMin>(limit_.value); +} + +// ConstraintStringLengthMax -------------------------------------------------- +ConstraintStringLengthMax::ConstraintStringLengthMax( + const InheritableAttribute<int>& limit) : ConstraintStringLength(limit) {} +ConstraintStringLengthMax::ConstraintStringLengthMax(int limit) + : ConstraintStringLength(limit) {} + +bool ConstraintStringLengthMax::Validate(const Any& value, + ErrorPtr* error) const { + std::string str = value.Get<std::string>(); + int length = static_cast<int>(str.size()); + if (length > limit_.value) { + Error::AddToPrintf(error, commands::errors::kDomain, + commands::errors::kOutOfRange, + "String must be no more than %d character(s) long, " + "actual length of string '%s' is %d", limit_.value, + str.c_str(), length); + return false; + } + return true; +} + +std::shared_ptr<Constraint> + ConstraintStringLengthMax::CloneAsInherited() const { + return std::make_shared<ConstraintStringLengthMax>(limit_.value); +} + +} // namespace buffet
diff --git a/buffet/commands/prop_constraints.h b/buffet/commands/prop_constraints.h new file mode 100644 index 0000000..96bfdfb --- /dev/null +++ b/buffet/commands/prop_constraints.h
@@ -0,0 +1,387 @@ +// 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_PROP_CONSTRAINTS_H_ +#define BUFFET_COMMANDS_PROP_CONSTRAINTS_H_ + +#include <limits> +#include <string> +#include <type_traits> +#include <vector> + +#include <base/basictypes.h> +#include <base/values.h> + +#include "buffet/any.h" +#include "buffet/commands/schema_constants.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, + StringLengthMin, + StringLengthMax, + OneOf +}; + +// Abstract base class for all parameter constraints. Many constraints are +// type-dependent. Thus, a numeric parameter could have "minimum" and/or +// "maximum" constraints specified. Some constraints, such as "OneOf" apply to +// any data type. +class Constraint { + public: + Constraint() = default; + virtual ~Constraint(); + + // Gets the constraint type. + virtual ConstraintType GetType() const = 0; + // Checks if any of the constraint properties/attributes are overridden + // from their base schema definition. If the constraint is inherited, then + // it will not be written to JSON when saving partial schema. + virtual bool HasOverriddenAttributes() const = 0; + // 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; + // 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; + // Saves the constraint into the specified JSON |dict| object, representing + // the object schema. If |overridden_only| is set to true, then the + // inherited constraints will not be added to the schema object. + virtual bool AddToJsonDict(base::DictionaryValue* dict, bool overridden_only, + ErrorPtr* error) const; + // Saves the value of constraint to JSON value. E.g., if the numeric + // constraint was defined as {"minimum":20} this will create a JSON value + // of 20. The current design implies that each constraint has one value + // only. If this assumption changes, this interface needs to be updated + // accordingly. + virtual std::unique_ptr<base::Value> ToJson(ErrorPtr* error) const = 0; + // Overloaded by the concrete class implementation, it should return the + // JSON object property name to store the constraint's value as. + // E.g., if the numeric constraint was defined as {"minimum":20} this + // method should return "minimum". + virtual const char* GetDictKey() const = 0; + + protected: + // Static helper methods to format common constraint validation errors. + // They fill the |error| object with specific error message. + // Since these functions could be used by constraint objects for various + // data types, the values used in validation are expected to be + // send as strings already. + static bool ReportErrorLessThan(ErrorPtr* error, const std::string& val, + const std::string& limit); + static bool ReportErrorGreaterThan(ErrorPtr* error, const std::string& val, + const std::string& limit); + + static bool ReportErrorNotOneOf(ErrorPtr* error, const std::string& val, + const std::vector<std::string>& values); + + private: + DISALLOW_COPY_AND_ASSIGN(Constraint); +}; + +// ConstraintMinMaxBase is a base class for numeric Minimum and Maximum +// constraints. +template<typename T> +class ConstraintMinMaxBase : public Constraint { + public: + explicit ConstraintMinMaxBase(const InheritableAttribute<T>& limit) + : limit_(limit) {} + explicit ConstraintMinMaxBase(const T& limit) + : limit_(limit) {} + + // Implementation of Constraint::HasOverriddenAttributes(). + virtual bool HasOverriddenAttributes() const override { + return !limit_.is_inherited; + } + + // Implementation of Constraint::ToJson(). + virtual std::unique_ptr<base::Value> ToJson(ErrorPtr* error) const override { + return TypedValueToJson(limit_.value); + } + + // Stores the upper/lower value limit for maximum/minimum constraint. + // |limit_.is_inherited| indicates whether the constraint is inherited + // from base schema or overridden. + InheritableAttribute<T> limit_; + + private: + DISALLOW_COPY_AND_ASSIGN(ConstraintMinMaxBase); +}; + +// Implementation of Minimum value constraint for Integer/Double types. +template<typename T> +class ConstraintMin : public ConstraintMinMaxBase<T> { + public: + explicit ConstraintMin(const InheritableAttribute<T>& limit) + : ConstraintMinMaxBase<T>(limit) {} + explicit ConstraintMin(const T& limit) + : ConstraintMinMaxBase<T>(limit) {} + + // Implementation of Constraint::GetType(). + 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>(); + if (v < this->limit_.value) + return this->ReportErrorLessThan( + error, string_utils::ToString(v), + string_utils::ToString(this->limit_.value)); + return true; + } + + // Implementation of Constraint::CloneAsInherited(). + virtual std::shared_ptr<Constraint> CloneAsInherited() const override { + return std::make_shared<ConstraintMin>(this->limit_.value); + } + + // Implementation of Constraint::GetDictKey(). + virtual const char* GetDictKey() const override { + return commands::attributes::kNumeric_Min; + } + + private: + DISALLOW_COPY_AND_ASSIGN(ConstraintMin); +}; + +// Implementation of Maximum value constraint for Integer/Double types. +template<typename T> +class ConstraintMax : public ConstraintMinMaxBase<T> { + public: + explicit ConstraintMax(const InheritableAttribute<T>& limit) + : ConstraintMinMaxBase<T>(limit) {} + explicit ConstraintMax(const T& limit) + : ConstraintMinMaxBase<T>(limit) {} + + // Implementation of Constraint::GetType(). + 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>(); + if (v > this->limit_.value) + return this->ReportErrorGreaterThan( + error, string_utils::ToString(v), + string_utils::ToString(this->limit_.value)); + return true; + } + + // Implementation of Constraint::CloneAsInherited(). + virtual std::shared_ptr<Constraint> CloneAsInherited() const override { + return std::make_shared<ConstraintMax>(this->limit_.value); + } + + // Implementation of Constraint::GetDictKey(). + virtual const char* GetDictKey() const override { + return commands::attributes::kNumeric_Max; + } + + private: + DISALLOW_COPY_AND_ASSIGN(ConstraintMax); +}; + +// ConstraintStringLength is a base class for Minimum/Maximum string length +// constraints, similar to ConstraintMinMaxBase of numeric types. +class ConstraintStringLength : public Constraint { + public: + explicit ConstraintStringLength(const InheritableAttribute<int>& limit); + explicit ConstraintStringLength(int limit); + + // Implementation of Constraint::HasOverriddenAttributes(). + virtual bool HasOverriddenAttributes() const override; + // Implementation of Constraint::ToJson(). + virtual std::unique_ptr<base::Value> ToJson(ErrorPtr* error) const override; + + // Stores the upper/lower value limit for string length constraint. + // |limit_.is_inherited| indicates whether the constraint is inherited + // from base schema or overridden. + InheritableAttribute<int> limit_; + + private: + DISALLOW_COPY_AND_ASSIGN(ConstraintStringLength); +}; + +// Implementation of Minimum string length constraint. +class ConstraintStringLengthMin : public ConstraintStringLength { + public: + explicit ConstraintStringLengthMin(const InheritableAttribute<int>& limit); + explicit ConstraintStringLengthMin(int limit); + // Implementation of Constraint::GetType(). + virtual ConstraintType GetType() const override { + return ConstraintType::StringLengthMin; + } + // Implementation of Constraint::Validate(). + virtual bool Validate(const Any& value, ErrorPtr* error) const override; + // Implementation of Constraint::CloneAsInherited(). + virtual std::shared_ptr<Constraint> CloneAsInherited() const override; + // Implementation of Constraint::GetDictKey(). + virtual const char* GetDictKey() const override { + return commands::attributes::kString_MinLength; + } + private: + DISALLOW_COPY_AND_ASSIGN(ConstraintStringLengthMin); +}; + +// Implementation of Maximum string length constraint. +class ConstraintStringLengthMax : public ConstraintStringLength { + public: + explicit ConstraintStringLengthMax(const InheritableAttribute<int>& limit); + explicit ConstraintStringLengthMax(int limit); + // Implementation of Constraint::GetType(). + virtual ConstraintType GetType() const override { + return ConstraintType::StringLengthMax; + } + // Implementation of Constraint::Validate(). + virtual bool Validate(const Any& value, ErrorPtr* error) const override; + // Implementation of Constraint::CloneAsInherited(). + virtual std::shared_ptr<Constraint> CloneAsInherited() const override; + // Implementation of Constraint::GetDictKey(). + virtual const char* GetDictKey() const override { + return commands::attributes::kString_MaxLength; + } + + private: + 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 { + public: + explicit ConstraintOneOf(const InheritableAttribute<std::vector<T>>& set) + : set_(set) {} + explicit ConstraintOneOf(const std::vector<T>& set) + : set_(set) {} + + // Implementation of Constraint::GetType(). + virtual ConstraintType GetType() const override { + return ConstraintType::OneOf; + } + + // Implementation of Constraint::HasOverriddenAttributes(). + virtual bool HasOverriddenAttributes() const override { + return !set_.is_inherited; + } + + // 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; + for (const auto& item : set_.value) { + if (CompareValue<T, exact_type>()(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)); + } + return ReportErrorNotOneOf(error, string_utils::ToString(v), values); + } + + // Implementation of Constraint::CloneAsInherited(). + virtual std::shared_ptr<Constraint> CloneAsInherited() const override { + return std::make_shared<ConstraintOneOf>(set_.value); + } + + // Implementation of Constraint::ToJson(). + virtual std::unique_ptr<base::Value> ToJson(ErrorPtr* error) const override { + return TypedValueToJson(set_.value); + } + + // Implementation of Constraint::GetDictKey(). + virtual const char* GetDictKey() const override { + return commands::attributes::kOneOf_Enum; + } + + // Stores the list of acceptable values for the parameter. + // |set_.is_inherited| indicates whether the constraint is inherited + // from base schema or overridden. + InheritableAttribute<std::vector<T>> set_; + + private: + DISALLOW_COPY_AND_ASSIGN(ConstraintOneOf); +}; + +} // namespace buffet + +#endif // BUFFET_COMMANDS_PROP_CONSTRAINTS_H_
diff --git a/buffet/commands/prop_types.cc b/buffet/commands/prop_types.cc new file mode 100644 index 0000000..6efdfb6 --- /dev/null +++ b/buffet/commands/prop_types.cc
@@ -0,0 +1,373 @@ +// 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/prop_types.h" + +#include <algorithm> +#include <limits> +#include <set> + +#include <base/json/json_writer.h> +#include <base/logging.h> +#include <base/values.h> + +#include "buffet/commands/prop_values.h" +#include "buffet/commands/object_schema.h" +#include "buffet/commands/schema_constants.h" +#include "buffet/string_utils.h" + +namespace buffet { + +// PropType ------------------------------------------------------------------- +PropType::PropType() { +} + +PropType::~PropType() { +} + +std::string PropType::GetTypeAsString() const { + return GetTypeStringFromType(GetType()); +} + +bool PropType::HasOverriddenAttributes() const { + for (const auto& pair : constraints_) { + if (pair.second->HasOverriddenAttributes()) + return true; + } + return false; +} + +std::unique_ptr<base::Value> PropType::ToJson(bool full_schema, + ErrorPtr* error) const { + if (!full_schema && !HasOverriddenAttributes()) { + if (based_on_schema_) + return std::unique_ptr<base::Value>(new base::DictionaryValue); + return TypedValueToJson(GetTypeAsString()); + } + + std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue); + if (full_schema) { + // If we are asked for full_schema definition, then we need to output every + // parameter property, including the "type", and any constraints. + // So, we write the "type" only if asked for full schema. + // Otherwise we will be able to infer the parameter type based on + // the constraints and their types. + // That is, the following JSONs could possibly define a parameter: + // {'type':'integer'} -> explicit "integer" with no constraints + // {'minimum':10} -> no type specified, but since we have "minimum" + // and 10 is integer, than this is an integer + // parameter with min constraint. + // {'enum':[1,2,3]} -> integer with OneOf constraint. + // And so is this: [1,2,3] -> an array of ints assume it's an "int" enum. + dict->SetString(commands::attributes::kType, GetTypeAsString()); + } + + if (!full_schema && constraints_.size() == 1) { + // If we are not asked for full schema, and we have only one constraint + // which is OneOf, we short-circuit the whole thing and return just + // the array [1,2,3] instead of an object with "enum" property like: + // {'enum':[1,2,3]} + auto p = constraints_.find(ConstraintType::OneOf); + if (p != constraints_.end()) { + return p->second->ToJson(error); + } + } + + for (const auto& pair : constraints_) { + if (!pair.second->AddToJsonDict(dict.get(), !full_schema, error)) + return std::unique_ptr<base::Value>(); + } + return std::unique_ptr<base::Value>(dict.release()); +} + +bool PropType::FromJson(const base::DictionaryValue* value, + const PropType* schema, ErrorPtr* error) { + if (schema && schema->GetType() != GetType()) { + Error::AddToPrintf(error, commands::errors::kDomain, + commands::errors::kPropTypeChanged, + "Redefining a command of type %s as %s", + schema->GetTypeAsString().c_str(), + GetTypeAsString().c_str()); + return false; + } + based_on_schema_ = (schema != nullptr); + constraints_.clear(); + if (schema) { + for (const auto& pair : schema->GetConstraints()) { + std::shared_ptr<Constraint> inherited(pair.second->CloneAsInherited()); + constraints_.insert(std::make_pair(pair.first, inherited)); + } + } + return true; +} + +void PropType::AddConstraint(std::shared_ptr<Constraint> constraint) { + constraints_[constraint->GetType()] = constraint; +} + +void PropType::RemoveConstraint(ConstraintType constraint_type) { + constraints_.erase(constraint_type); +} + +const Constraint* PropType::GetConstraint( + ConstraintType constraint_type) const { + auto p = constraints_.find(constraint_type); + return p != constraints_.end() ? p->second.get() : nullptr; +} + +Constraint* PropType::GetConstraint(ConstraintType constraint_type) { + auto p = constraints_.find(constraint_type); + return p != constraints_.end() ? p->second.get() : nullptr; +} + +bool PropType::ValidateConstraints(const Any& value, ErrorPtr* error) const { + for (const auto& pair : constraints_) { + if (!pair.second->Validate(value, error)) + return false; + } + return true; +} + +const PropType::TypeMap& PropType::GetTypeMap() { + static TypeMap map = { + {ValueType::Int, "integer"}, + {ValueType::Double, "number"}, + {ValueType::String, "string"}, + {ValueType::Boolean, "boolean"}, + }; + return map; +} + +std::string PropType::GetTypeStringFromType(ValueType type) { + for (const auto& pair : GetTypeMap()) { + if (pair.first == type) + return pair.second; + } + LOG(FATAL) << "Type map is missing a type"; + return std::string(); +} + +bool PropType::GetTypeFromTypeString(const std::string& name, ValueType* type) { + for (const auto& pair : GetTypeMap()) { + if (pair.second == name) { + *type = pair.first; + return true; + } + } + return false; +} + +std::unique_ptr<PropType> PropType::Create(ValueType type) { + PropType* prop = nullptr; + switch (type) { + case buffet::ValueType::Int: + prop = new IntPropType; + break; + case buffet::ValueType::Double: + prop = new DoublePropType; + break; + case buffet::ValueType::String: + prop = new StringPropType; + break; + case buffet::ValueType::Boolean: + prop = new BooleanPropType; + 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::ListValue* list = nullptr; + if (!value->GetListWithoutPathExpansion(commands::attributes::kOneOf_Enum, + &list)) { + Error::AddTo(error, commands::errors::kDomain, + commands::errors::kTypeMismatch, "Expecting an array"); + return std::shared_ptr<Constraint>(); + } + std::vector<T> set; + set.reserve(list->GetSize()); + for (const base::Value* item : *list) { + T val{}; + if (!TypedValueFromJson(item, &val, error)) + return std::shared_ptr<Constraint>(); + set.push_back(val); + } + InheritableAttribute<std::vector<T>> val(set, false); + return std::make_shared<ConstraintOneOf<T>>(val); +} + +template<class ConstraintClass, typename T> +static std::shared_ptr<Constraint> LoadMinMaxConstraint( + const char* dict_key, const base::DictionaryValue* value, 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)) + return std::shared_ptr<Constraint>(); + limit.is_inherited = false; + + return std::make_shared<ConstraintClass>(limit); +} + +template<typename T> +bool NumericPropTypeBase::FromJsonHelper(const base::DictionaryValue* value, + const PropType* schema, + ErrorPtr* error) { + if (!PropType::FromJson(value, schema, error)) + return false; + + if (value->HasKey(commands::attributes::kOneOf_Enum)) { + auto constraint = LoadOneOfConstraint<T>(value, error); + if (!constraint) + return false; + AddConstraint(constraint); + RemoveConstraint(ConstraintType::Min); + RemoveConstraint(ConstraintType::Max); + } else { + if (value->HasKey(commands::attributes::kNumeric_Min)) { + auto constraint = LoadMinMaxConstraint<ConstraintMin<T>, T>( + commands::attributes::kNumeric_Min, value, error); + if (!constraint) + return false; + AddConstraint(constraint); + RemoveConstraint(ConstraintType::OneOf); + } + if (value->HasKey(commands::attributes::kNumeric_Max)) { + auto constraint = LoadMinMaxConstraint<ConstraintMax<T>, T>( + commands::attributes::kNumeric_Max, value, error); + if (!constraint) + return false; + AddConstraint(constraint); + RemoveConstraint(ConstraintType::OneOf); + } + } + + return true; +} + +// IntPropType ---------------------------------------------------------------- + +std::shared_ptr<PropType> IntPropType::Clone() const { + return std::make_shared<IntPropType>(*this); +} + +std::shared_ptr<PropValue> IntPropType::CreateValue() const { + return std::make_shared<IntValue>(); +} + +// DoublePropType ------------------------------------------------------------- + +std::shared_ptr<PropType> DoublePropType::Clone() const { + return std::make_shared<DoublePropType>(*this); +} + +std::shared_ptr<PropValue> DoublePropType::CreateValue() const { + return std::make_shared<DoubleValue>(); +} + +// StringPropType ------------------------------------------------------------- + +std::shared_ptr<PropType> StringPropType::Clone() const { + return std::make_shared<StringPropType>(*this); +} + +std::shared_ptr<PropValue> StringPropType::CreateValue() const { + return std::make_shared<StringValue>(); +} + +bool StringPropType::FromJson(const base::DictionaryValue* value, + const PropType* schema, ErrorPtr* error) { + if (!PropType::FromJson(value, schema, error)) + return false; + if (value->HasKey(commands::attributes::kOneOf_Enum)) { + auto constraint = LoadOneOfConstraint<std::string>(value, error); + if (!constraint) + return false; + AddConstraint(constraint); + RemoveConstraint(ConstraintType::StringLengthMin); + RemoveConstraint(ConstraintType::StringLengthMax); + } else { + if (value->HasKey(commands::attributes::kString_MinLength)) { + auto constraint = LoadMinMaxConstraint<ConstraintStringLengthMin, int>( + commands::attributes::kString_MinLength, value, error); + if (!constraint) + return false; + AddConstraint(constraint); + RemoveConstraint(ConstraintType::OneOf); + } + if (value->HasKey(commands::attributes::kString_MaxLength)) { + auto constraint = LoadMinMaxConstraint<ConstraintStringLengthMax, int>( + commands::attributes::kString_MaxLength, value, error); + if (!constraint) + return false; + AddConstraint(constraint); + RemoveConstraint(ConstraintType::OneOf); + } + } + return true; +} + +void StringPropType::AddLengthConstraint(int min_len, int max_len) { + InheritableAttribute<int> min_attr(min_len, false); + InheritableAttribute<int> max_attr(max_len, false); + AddConstraint(std::make_shared<ConstraintStringLengthMin>(min_attr)); + AddConstraint(std::make_shared<ConstraintStringLengthMax>(max_attr)); +} + +int StringPropType::GetMinLength() const { + auto slc = static_cast<const ConstraintStringLength*>( + GetConstraint(ConstraintType::StringLengthMin)); + return slc ? slc->limit_.value : 0; +} + +int StringPropType::GetMaxLength() const { + auto slc = static_cast<const ConstraintStringLength*>( + GetConstraint(ConstraintType::StringLengthMax)); + return slc ? slc->limit_.value : std::numeric_limits<int>::max(); +} + +// BooleanPropType ----------------------------------------------------------- + +std::shared_ptr<PropType> BooleanPropType::Clone() const { + return std::make_shared<BooleanPropType>(*this); +} + +std::shared_ptr<PropValue> BooleanPropType::CreateValue() const { + return std::make_shared<BooleanValue>(); +} + +bool BooleanPropType::FromJson(const base::DictionaryValue* value, + const PropType* schema, ErrorPtr* error) { + if (!PropType::FromJson(value, schema, error)) + return false; + + if (value->HasKey(commands::attributes::kOneOf_Enum)) { + auto constraint = LoadOneOfConstraint<bool>(value, error); + if (!constraint) + return false; + AddConstraint(constraint); + } + + return true; +} + +} // namespace buffet
diff --git a/buffet/commands/prop_types.h b/buffet/commands/prop_types.h new file mode 100644 index 0000000..63d3c44 --- /dev/null +++ b/buffet/commands/prop_types.h
@@ -0,0 +1,323 @@ +// 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_PROP_TYPES_H_ +#define BUFFET_COMMANDS_PROP_TYPES_H_ + +#include <limits> +#include <map> +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#include "buffet/commands/prop_constraints.h" +#include "buffet/commands/prop_values.h" +#include "buffet/error.h" + +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); + +// PropType is a base class for all parameter type definition objects. +// Property definitions of a particular type will derive from this class and +// provide type-specific implementations. +class PropType { + public: + // ConstraintMap is a type alias for a map containing parameter + // constraints. It is implemented as a map for fast look-ups of constraints + // of particular type. Also it is expected to have at most one constraint + // of each type (e.g. it makes no sense to impose two "minimum" constraints + // onto a numeric parameter). + using ConstraintMap = std::map<ConstraintType, + std::shared_ptr<Constraint>>; + + PropType(); + virtual ~PropType(); + + // Gets the parameter type as an enumeration. + virtual ValueType GetType() const = 0; + // Gets the parameter type as a string. + std::string GetTypeAsString() const; + // Returns true if this parameter definition inherits a type + // definition from a base object schema. + bool IsBasedOnSchema() const { return based_on_schema_; } + // Returns a default value specified for the type, or nullptr if no default + // is available. + const PropValue* GetDefaultValue() const { return default_.value.get(); } + // Gets the constraints specified for the parameter, if any. + const ConstraintMap& GetConstraints() const { + return constraints_; + } + // Checks if any of the type attributes were overridden from the base + // schema definition. If this type does not inherit from a base schema, + // this method returns true. + // An attribute could be the value of any of the constraints, default + // value of a parameter or any other data that may be specified in + // parameter type definition in and can be inherited from the base schema. + virtual bool HasOverriddenAttributes() const; + + // Type conversion methods. Used in lieu of RTTI and dynamic_cast<>. + virtual IntPropType* GetInt() { return nullptr; } + virtual IntPropType const* GetInt() const { return nullptr; } + virtual DoublePropType* GetDouble() { return nullptr; } + virtual DoublePropType const* GetDouble() const { return nullptr; } + virtual StringPropType* GetString() { return nullptr; } + virtual StringPropType const* GetString() const { return nullptr; } + virtual BooleanPropType* GetBoolean() { return nullptr; } + virtual BooleanPropType const* GetBoolean() 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; + + // Saves the parameter type definition as a JSON object. + // If |full_schema| is set to true, the full type definition is saved, + // otherwise only the overridden properties and attributes from the base + // schema is saved. That is, inherited properties and attributes are not + // saved. + // If it fails, returns "nullptr" and fills in the |error| with additional + // 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. + // 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); + + // 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; + + // Additional helper static methods to help with converting a type enum + // value into a string and back. + using TypeMap = std::vector<std::pair<ValueType, std::string>>; + // Returns a list of value types and corresponding type names. + static const TypeMap& GetTypeMap(); + // Gets the type name string for the given type. + static std::string GetTypeStringFromType(ValueType type); + // Finds the type for the given type name. Returns true on success. + static bool GetTypeFromTypeString(const std::string& name, ValueType* type); + + // Creates an instance of PropType-derived class for the specified + // parameter type. + static std::unique_ptr<PropType> Create(ValueType type); + + // Adds a constraint to the type definition. + void AddConstraint(std::shared_ptr<Constraint> constraint); + // Removes a constraint of given type, if it exists. + void RemoveConstraint(ConstraintType constraint_type); + + // Finds a constraint of given type. Returns nullptr if not found. + 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; + + // Helper method to obtaining a vector of OneOf constraint values. + template<typename T> + std::vector<T> GetOneOfValuesHelper() const { + auto ofc = static_cast<const ConstraintOneOf<T>*>( + 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. + bool based_on_schema_ = false; + // A list of constraints specified for the parameter. + ConstraintMap constraints_; + // The default value specified for the parameter, if any. If the default + // value is present, the parameter is treated as optional and the default + // value is used if the parameter value is omitted when sending a command. + // Otherwise the parameter is treated as required and, if it is omitted, + // this is treated as an error. + InheritableAttribute<std::shared_ptr<PropValue>> default_; +}; + +// Helper base class for Int and Double parameter types. +class NumericPropTypeBase : public PropType { + protected: + // Helper method for implementing AddMinMaxConstraint in derived classes. + template<typename T> + void AddMinMaxConstraintHelper(T min_value, T max_value) { + InheritableAttribute<T> min_attr(min_value, false); + InheritableAttribute<T> max_attr(max_value, false); + AddConstraint(std::make_shared<ConstraintMin<T>>(min_attr)); + AddConstraint(std::make_shared<ConstraintMax<T>>(max_attr)); + } + + // Helper method for implementing GetMinValue in derived classes. + template<typename T> + T GetMinValueHelper() const { + auto mmc = static_cast<const ConstraintMin<T>*>( + GetConstraint(ConstraintType::Min)); + return mmc ? mmc->limit_.value : std::numeric_limits<T>::lowest(); + } + + // Helper method for implementing GetMaxValue in derived classes. + template<typename T> + T GetMaxValueHelper() const { + auto mmc = static_cast<const ConstraintMax<T>*>( + GetConstraint(ConstraintType::Max)); + return mmc ? mmc->limit_.value : (std::numeric_limits<T>::max)(); + } + + // Helper method for implementing FromJson in derived classes. + template<typename T> + bool FromJsonHelper(const base::DictionaryValue* value, + const PropType* schema, ErrorPtr* error); +}; + +// Property definition of Integer type. +class IntPropType : public NumericPropTypeBase { + public: + // Overrides from the PropType base class. + virtual ValueType GetType() const override { return ValueType::Int; } + + virtual IntPropType* GetInt() override { return this; } + virtual IntPropType const* GetInt() const override { return this; } + + virtual std::shared_ptr<PropType> Clone() const override; + virtual std::shared_ptr<PropValue> CreateValue() 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); + } + + // Helper methods to add and inspect simple constraints. + // Used mostly for unit testing. + void AddMinMaxConstraint(int min_value, int max_value) { + AddMinMaxConstraintHelper(min_value, max_value); + } + int GetMinValue() const { return GetMinValueHelper<int>(); } + int GetMaxValue() const { return GetMaxValueHelper<int>(); } + std::vector<int> GetOneOfValues() const { + return GetOneOfValuesHelper<int>(); + } +}; + +// Property definition of Number type. +class DoublePropType : public NumericPropTypeBase { + public: + // Overrides from the PropType base class. + virtual ValueType GetType() const override { return ValueType::Double; } + + virtual DoublePropType* GetDouble() override { return this; } + virtual DoublePropType const* GetDouble() const override { return this; } + + virtual std::shared_ptr<PropType> Clone() const override; + virtual std::shared_ptr<PropValue> CreateValue() 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); + } + + // Helper methods to add and inspect simple constraints. + // Used mostly for unit testing. + void AddMinMaxConstraint(double min_value, double max_value) { + AddMinMaxConstraintHelper(min_value, max_value); + } + double GetMinValue() const { return GetMinValueHelper<double>(); } + double GetMaxValue() const { return GetMaxValueHelper<double>(); } + std::vector<double> GetOneOfValues() const { + return GetOneOfValuesHelper<double>(); + } +}; + +// Property definition of String type. +class StringPropType : public PropType { + public: + // Overrides from the PropType base class. + virtual ValueType GetType() const override { return ValueType::String; } + + virtual StringPropType* GetString() override { return this; } + virtual StringPropType const* GetString() const override { return this; } + + virtual std::shared_ptr<PropType> Clone() const override; + virtual std::shared_ptr<PropValue> CreateValue() 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); + } + + // Helper methods to add and inspect simple constraints. + // Used mostly for unit testing. + void AddLengthConstraint(int min_len, int max_len); + int GetMinLength() const; + int GetMaxLength() const; + std::vector<std::string> GetOneOfValues() const { + return GetOneOfValuesHelper<std::string>(); + } +}; + +// Property definition of Boolean type. +class BooleanPropType : public PropType { + public: + // Overrides from the PropType base class. + virtual ValueType GetType() const override { return ValueType::Boolean; } + + virtual BooleanPropType* GetBoolean() override { return this; } + virtual BooleanPropType const* GetBoolean() const override { return this; } + + virtual std::shared_ptr<PropType> Clone() const override; + virtual std::shared_ptr<PropValue> CreateValue() 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); + } + + // Helper methods to add and inspect simple constraints. + // Used mostly for unit testing. + std::vector<bool> GetOneOfValues() const { + return GetOneOfValuesHelper<bool>(); + } +}; + +} // namespace buffet + +#endif // BUFFET_COMMANDS_PROP_TYPES_H_
diff --git a/buffet/commands/prop_values.cc b/buffet/commands/prop_values.cc new file mode 100644 index 0000000..e2eabc0 --- /dev/null +++ b/buffet/commands/prop_values.cc
@@ -0,0 +1,23 @@ +// 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/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 new file mode 100644 index 0000000..e03d39b --- /dev/null +++ b/buffet/commands/prop_values.h
@@ -0,0 +1,144 @@ +// 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_PROP_VALUES_H_ +#define BUFFET_COMMANDS_PROP_VALUES_H_ + +#include <map> +#include <memory> +#include <string> + +#include "buffet/commands/prop_constraints.h" +#include "buffet/error.h" + +namespace base { +class Value; +class DictionaryValue; +} // namespace base + +namespace buffet { + +// Enumeration to indicate supported command parameter types. +enum class ValueType { + Int, + Double, + String, + Boolean +}; + +// 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>(); + +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. +// Concrete value classes of various types will be derived from this base. +class PropValue { + public: + PropValue() = default; + virtual ~PropValue(); + + // Gets the type of the value. + virtual ValueType GetType() const = 0; + + // Type conversion methods. Used in lieu of RTTI and dynamic_cast<>. + virtual IntValue* GetInt() { return nullptr; } + virtual IntValue const* GetInt() const { return nullptr; } + virtual DoubleValue* GetDouble() { return nullptr; } + virtual DoubleValue const* GetDouble() const { return nullptr; } + virtual StringValue* GetString() { return nullptr; } + virtual StringValue const* GetString() const { return nullptr; } + virtual BooleanValue* GetBoolean() { return nullptr; } + virtual BooleanValue const* GetBoolean() const { return nullptr; } + + // Makes a full copy of this value class. + virtual std::shared_ptr<PropValue> Clone() const = 0; + + // Saves the value as a JSON object. + // If it fails, returns nullptr value and fills in the details for the + // failure in the |error| parameter. + virtual std::unique_ptr<base::Value> ToJson(ErrorPtr* error) const = 0; + // 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; +}; + +// A helper template base class for implementing simple (non-Object) value +// classes. +template<typename Derived, typename T> +class TypedValueBase : public PropValue { + public: + // Overrides from PropValue base class. + virtual ValueType GetType() const override { return GetValueType<T>(); } + virtual std::shared_ptr<PropValue> Clone() const override { + return std::make_shared<Derived>(*static_cast<const Derived*>(this)); + } + + virtual std::unique_ptr<base::Value> ToJson(ErrorPtr* error) const override { + (void)error; // unused. + return TypedValueToJson(value_); + } + + virtual bool FromJson(const base::Value* value, ErrorPtr* error) override { + return TypedValueFromJson(value, &value_, error); + } + + // Helper method to get and set the C++ representation of the value. + const T& GetValue() const { return value_; } + void SetValue(T value) { value_ = std::move(value); } + + protected: + T value_{}; // The value of the parameter in C++ data representation. +}; + +// Value of type Integer. +class IntValue final : public TypedValueBase<IntValue, int> { + public: + virtual IntValue* GetInt() override { return this; } + virtual IntValue const* GetInt() const override { return this; } +}; + +// Value of type Number. +class DoubleValue final : public TypedValueBase<DoubleValue, double> { + public: + virtual DoubleValue* GetDouble() override { return this; } + virtual DoubleValue const* GetDouble() const override { return this; } +}; + +// Value of type String. +class StringValue final : public TypedValueBase<StringValue, std::string> { + public: + virtual StringValue* GetString() override { return this; } + virtual StringValue const* GetString() const override { return this; } +}; + +// Value of type Boolean. +class BooleanValue final : public TypedValueBase<BooleanValue, bool> { + public: + virtual BooleanValue* GetBoolean() override { return this; } + virtual BooleanValue const* GetBoolean() 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 new file mode 100644 index 0000000..15d6db3 --- /dev/null +++ b/buffet/commands/schema_constants.cc
@@ -0,0 +1,36 @@ +// 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_constants.h" + +namespace buffet { +namespace commands { + +namespace errors { +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"; +} // namespace errors + +namespace attributes { +const char kType[] = "type"; +const char kDisplayName[] = "displayName"; + +const char kNumeric_Min[] = "minimum"; +const char kNumeric_Max[] = "maximum"; + +const char kString_MinLength[] = "minLength"; +const char kString_MaxLength[] = "maxLength"; + +const char kOneOf_Enum[] = "enum"; +} // namespace attributes + +} // namespace commands +} // namespace buffet
diff --git a/buffet/commands/schema_constants.h b/buffet/commands/schema_constants.h new file mode 100644 index 0000000..0e58cff --- /dev/null +++ b/buffet/commands/schema_constants.h
@@ -0,0 +1,42 @@ +// 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_CONSTANTS_H_ +#define BUFFET_COMMANDS_SCHEMA_CONSTANTS_H_ + +namespace buffet { +namespace commands { + +namespace errors { +// Error domain for command schema description. +extern const char kDomain[]; + +// Common command definition error codes. +extern const char kOutOfRange[]; +extern const char kTypeMismatch[]; +extern const char kPropTypeChanged[]; +extern const char kUnknownType[]; +extern const char kInvalidPropDef[]; +extern const char kNoTypeInfo[]; +extern const char kPropertyMissing[]; +} // namespace errors + +namespace attributes { +// Command description JSON schema attributes. +extern const char kType[]; +extern const char kDisplayName[]; + +extern const char kNumeric_Min[]; +extern const char kNumeric_Max[]; + +extern const char kString_MinLength[]; +extern const char kString_MaxLength[]; + +extern const char kOneOf_Enum[]; +} // namespace attributes + +} // namespace commands +} // namespace buffet + +#endif // BUFFET_COMMANDS_SCHEMA_CONSTANTS_H_