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_