buffet: GCD command defintion. Compound object type support.

Added support for "object" type. Refactored parameter validation
to make sure we have object schema context when we validate
a value of parameter.

Parameter |schema| was used in two different contexts, as both
a base parameter definition and as a custom object definition.
Renamed the former to be 'base_schema' and latter as
'object_schema' to remove the confusion.

Extracted common data type manipulation functions into
schema_utils.cc/.h files.

BUG=chromium:374860
TEST=All unit tests pass.

Change-Id: I6c3549849a258bcc94b3d754acd14e072438d140
Reviewed-on: https://chromium-review.googlesource.com/204793
Tested-by: Alex Vakulenko <avakulenko@chromium.org>
Reviewed-by: Christopher Wiley <wiley@chromium.org>
Commit-Queue: Alex Vakulenko <avakulenko@chromium.org>
diff --git a/buffet/commands/object_schema_unittest.cc b/buffet/commands/object_schema_unittest.cc
index fa901ae..be93073 100644
--- a/buffet/commands/object_schema_unittest.cc
+++ b/buffet/commands/object_schema_unittest.cc
@@ -60,6 +60,7 @@
   EXPECT_EQ(&prop, prop.GetInt());
   EXPECT_EQ(nullptr, prop.GetDouble());
   EXPECT_EQ(nullptr, prop.GetString());
+  EXPECT_EQ(nullptr, prop.GetObject());
 }
 
 TEST(CommandSchema, IntPropType_ToJson) {
@@ -165,6 +166,7 @@
   EXPECT_EQ(&prop, prop.GetBoolean());
   EXPECT_EQ(nullptr, prop.GetDouble());
   EXPECT_EQ(nullptr, prop.GetString());
+  EXPECT_EQ(nullptr, prop.GetObject());
 }
 
 TEST(CommandSchema, BoolPropType_ToJson) {
@@ -229,6 +231,7 @@
   EXPECT_EQ(nullptr, prop.GetBoolean());
   EXPECT_EQ(&prop, prop.GetDouble());
   EXPECT_EQ(nullptr, prop.GetString());
+  EXPECT_EQ(nullptr, prop.GetObject());
 }
 
 TEST(CommandSchema, DoublePropType_ToJson) {
@@ -325,6 +328,7 @@
   EXPECT_EQ(nullptr, prop.GetBoolean());
   EXPECT_EQ(nullptr, prop.GetDouble());
   EXPECT_EQ(&prop, prop.GetString());
+  EXPECT_EQ(nullptr, prop.GetObject());
 }
 
 TEST(CommandSchema, StringPropType_ToJson) {
@@ -411,6 +415,121 @@
   error.reset();
 }
 
+///////////////////////////////////////////////////////////////////////////////
+
+TEST(CommandSchema, ObjectPropType_Empty) {
+  buffet::ObjectPropType prop;
+  EXPECT_TRUE(prop.HasOverriddenAttributes());
+  EXPECT_FALSE(prop.IsBasedOnSchema());
+}
+
+TEST(CommandSchema, ObjectPropType_Types) {
+  buffet::ObjectPropType prop;
+  EXPECT_EQ(nullptr, prop.GetInt());
+  EXPECT_EQ(nullptr, prop.GetBoolean());
+  EXPECT_EQ(nullptr, prop.GetDouble());
+  EXPECT_EQ(nullptr, prop.GetString());
+  EXPECT_EQ(&prop, prop.GetObject());
+}
+
+TEST(CommandSchema, ObjectPropType_ToJson) {
+  buffet::ObjectPropType prop;
+  EXPECT_EQ("{'properties':{}}",
+            ValueToString(prop.ToJson(false, nullptr).get()));
+  EXPECT_EQ("{'properties':{},'type':'object'}",
+            ValueToString(prop.ToJson(true, nullptr).get()));
+  EXPECT_FALSE(prop.IsBasedOnSchema());
+  buffet::ObjectPropType prop2;
+  prop2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr);
+  EXPECT_EQ("{}", ValueToString(prop2.ToJson(false, nullptr).get()));
+  EXPECT_TRUE(prop2.IsBasedOnSchema());
+
+  auto schema = std::make_shared<buffet::ObjectSchema>();
+  schema->AddProp("expires", std::make_shared<buffet::IntPropType>());
+  auto pw = std::make_shared<buffet::StringPropType>();
+  pw->AddLengthConstraint(6, 100);
+  schema->AddProp("password", pw);
+  prop2.SetObjectSchema(schema);
+  EXPECT_EQ("{'properties':{'expires':'integer',"
+            "'password':{'maxLength':100,'minLength':6}}}",
+            ValueToString(prop2.ToJson(false, nullptr).get()));
+  EXPECT_EQ("{'properties':{'expires':{'type':'integer'},"
+            "'password':{'maxLength':100,'minLength':6,'type':'string'}},"
+            "'type':'object'}",
+            ValueToString(prop2.ToJson(true, nullptr).get()));
+}
+
+TEST(CommandSchema, ObjectPropType_FromJson) {
+  buffet::ObjectPropType base_prop;
+  EXPECT_TRUE(base_prop.FromJson(CreateDictionaryValue(
+      "{'properties':{'name':'string','age':'integer'}}").get(), nullptr,
+      nullptr));
+  auto schema = base_prop.GetObjectSchemaPtr();
+  const buffet::PropType* prop = schema->GetProp("name");
+  EXPECT_EQ(buffet::ValueType::String, prop->GetType());
+  prop = schema->GetProp("age");
+  EXPECT_EQ(buffet::ValueType::Int, prop->GetType());
+}
+
+TEST(CommandSchema, ObjectPropType_Validate) {
+  buffet::ObjectPropType prop;
+  prop.FromJson(CreateDictionaryValue(
+      "{'properties':{'expires':'integer',"
+      "'password':{'maxLength':100,'minLength':6}}}").get(), nullptr,
+      nullptr);
+  buffet::ErrorPtr error;
+  EXPECT_TRUE(prop.ValidateValue(CreateValue(
+      "{'expires':10,'password':'abcdef'}").get(), &error));
+  error.reset();
+
+  EXPECT_FALSE(prop.ValidateValue(CreateValue(
+      "{'expires':10}").get(), &error));
+  EXPECT_EQ("parameter_missing", error->GetCode());
+  error.reset();
+
+  EXPECT_FALSE(prop.ValidateValue(CreateValue(
+      "{'password':'abcdef'}").get(), &error));
+  EXPECT_EQ("parameter_missing", error->GetCode());
+  error.reset();
+
+  EXPECT_FALSE(prop.ValidateValue(CreateValue(
+      "{'expires':10,'password':'abcde'}").get(), &error));
+  EXPECT_EQ("out_of_range", error->GetFirstError()->GetCode());
+  error.reset();
+
+  EXPECT_FALSE(prop.ValidateValue(CreateValue("2").get(), &error));
+  EXPECT_EQ("type_mismatch", error->GetCode());
+  error.reset();
+
+  EXPECT_FALSE(prop.ValidateValue(CreateValue(
+      "{'expires':10,'password':'abcdef','retry':true}").get(), &error));
+  EXPECT_EQ("unexpected_parameter", error->GetCode());
+  error.reset();
+}
+
+TEST(CommandSchema, ObjectPropType_Validate_Enum) {
+  buffet::ObjectPropType prop;
+  EXPECT_TRUE(prop.FromJson(CreateDictionaryValue(
+      "{'properties':{'width':'integer','height':'integer'},"
+      "'enum':[{'width':10,'height':20},{'width':100,'height':200}]}").get(),
+      nullptr, nullptr));
+  buffet::ErrorPtr error;
+  EXPECT_TRUE(prop.ValidateValue(CreateValue(
+      "{'height':20,'width':10}").get(), &error));
+  error.reset();
+
+  EXPECT_TRUE(prop.ValidateValue(CreateValue(
+      "{'height':200,'width':100}").get(), &error));
+  error.reset();
+
+  EXPECT_FALSE(prop.ValidateValue(CreateValue(
+      "{'height':12,'width':10}").get(), &error));
+  EXPECT_EQ("out_of_range", error->GetCode());
+  error.reset();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
 TEST(CommandSchema, ObjectSchema_FromJson_Shorthand_TypeName) {
   buffet::ObjectSchema schema;
   const char* schema_str = "{"