buffet: Clean up property system in preparation for Array support

- Made ObjectSchema::PropFromJson a public method to allow parsing
  type definitions for other elements, not only object properties
  (it will be used to parse "items" definition for arrays). Made this
  method to return std::unique_ptr<PropType> instead of putting the
  type directly into property map.

- Cleaned up error reporting when parsing type definitions from JSON.
  Now PropFromJson does not refer to any particular property name
  in error messages. The property name is added on the level higher
  (at ObjectSchema::FromJson).

- Moved GetObjectSchema() method from base PropType class to the
  only place where it is being used right now - ObjectPropType.

- Switched value constraints to use generic PropType instead of
  ObjectSchema which will allow to add support for constraints
  on more complex types (such as arrays). Also added
  CloneWithNoConstraints() method to base class to help implement
  OneOf() constraint of polymorphic type (will be used more in the
  following CLs to support arrays of values).

- Fixed some formatting of function definitions.

- Moved static methods in object_schema.cc into an anonymous namespace
  at the beginning of the source file.

BUG=brillo:107
TEST=`FEATURES=test emerge-link buffet`

Change-Id: I86dd6c0780a46f4996ffe698dffb5d409699ff31
Reviewed-on: https://chromium-review.googlesource.com/260852
Reviewed-by: Vitaly Buka <vitalybuka@chromium.org>
Tested-by: Alex Vakulenko <avakulenko@chromium.org>
Commit-Queue: Alex Vakulenko <avakulenko@chromium.org>
Trybot-Ready: Alex Vakulenko <avakulenko@chromium.org>
diff --git a/buffet/commands/object_schema.cc b/buffet/commands/object_schema.cc
index 6437262..b89de98 100644
--- a/buffet/commands/object_schema.cc
+++ b/buffet/commands/object_schema.cc
@@ -9,6 +9,7 @@
 
 #include <base/logging.h>
 #include <base/values.h>
+#include <chromeos/map_utils.h>
 
 #include "buffet/commands/prop_types.h"
 #include "buffet/commands/prop_values.h"
@@ -16,49 +17,12 @@
 
 namespace buffet {
 
-void ObjectSchema::AddProp(const std::string& name,
-                           std::shared_ptr<PropType> prop) {
-  properties_[name] = prop;
-}
+namespace {
 
-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, chromeos::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* object_schema,
-                            chromeos::ErrorPtr* error) {
-  Properties properties;
-  base::DictionaryValue::Iterator iter(*value);
-  while (!iter.IsAtEnd()) {
-    std::string name = iter.key();
-    const PropType* base_schema =
-        object_schema ? object_schema->GetProp(iter.key()) : nullptr;
-    if (!PropFromJson(iter.key(), iter.value(), base_schema, &properties,
-                      error))
-      return false;
-    iter.Advance();
-  }
-  properties_ = std::move(properties);
-  return true;
-}
-
-static std::unique_ptr<PropType> CreatePropType(const std::string& type_name,
-                                                const std::string& prop_name,
-                                                chromeos::ErrorPtr* error) {
+// Helper function for to create a PropType based on type string.
+// Generates an error if the string identifies an unknown type.
+std::unique_ptr<PropType> CreatePropType(const std::string& type_name,
+                                         chromeos::ErrorPtr* error) {
   std::unique_ptr<PropType> prop;
   ValueType type;
   if (PropType::GetTypeFromTypeString(type_name, &type))
@@ -66,71 +30,40 @@
   if (!prop) {
     chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
                                  errors::commands::kUnknownType,
-                                 "Unknown type %s for parameter %s",
-                                 type_name.c_str(), prop_name.c_str());
+                                 "Unknown type %s", type_name.c_str());
   }
   return prop;
 }
 
-static bool ErrorInvalidTypeInfo(const std::string& prop_name,
-                                 chromeos::ErrorPtr* error) {
-  chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
-                               errors::commands::kNoTypeInfo,
-                               "Unable to determine parameter type for %s",
-                               prop_name.c_str());
-  return false;
+// Generates "no_type_info" error.
+void ErrorInvalidTypeInfo(chromeos::ErrorPtr* error) {
+  chromeos::Error::AddTo(error, FROM_HERE, errors::commands::kDomain,
+                         errors::commands::kNoTypeInfo,
+                         "Unable to determine parameter type");
 }
 
-bool ObjectSchema::PropFromJson(const std::string& prop_name,
-                                const base::Value& value,
-                                const PropType* base_schema,
-                                Properties* properties,
-                                chromeos::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, base_schema, properties,
-                              error);
-  } else if (value.IsType(base::Value::TYPE_LIST)) {
-    // One of the enumerated types.
-    return PropFromJsonArray(prop_name, value, base_schema, properties,
-                             error);
-  } else if (value.IsType(base::Value::TYPE_DICTIONARY)) {
-    // Full parameter definition.
-    return PropFromJsonObject(prop_name, value, base_schema, properties,
-                              error);
-  }
-  chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
-                               errors::commands::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* base_schema,
-                                      Properties* properties,
-                                      chromeos::ErrorPtr* error) const {
+// Helper function for PropFromJson to handle the case of parameter being
+// defined as a JSON string like this:
+//   "prop":"..."
+std::unique_ptr<PropType> PropFromJsonString(
+    const base::Value& value,
+    const PropType* base_schema,
+    chromeos::ErrorPtr* error) {
   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;
+  std::unique_ptr<PropType> prop = CreatePropType(type_name, error);
   base::DictionaryValue empty;
-  if (!prop->FromJson(&empty, base_schema, error)) {
-    chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
-                                 errors::commands::kInvalidPropDef,
-                                 "Error in definition of property '%s'",
-                                 prop_name.c_str());
-    return false;
-  }
-  properties->insert(std::make_pair(prop_name, std::move(prop)));
-  return true;
+  if (prop && !prop->FromJson(&empty, base_schema, error))
+    prop.reset();
+
+  return prop;
 }
 
-static std::string DetectArrayType(const base::ListValue* list,
-                                   const PropType* base_schema) {
+// Detects a type based on JSON array. Inspects the first element of the array
+// to deduce the PropType from. Returns the string name of the type detected
+// or empty string is type detection failed.
+std::string DetectArrayType(const base::ListValue* list,
+                            const PropType* base_schema) {
   std::string type_name;
   if (base_schema) {
     type_name = base_schema->GetTypeAsString();
@@ -162,35 +95,37 @@
   return type_name;
 }
 
-bool ObjectSchema::PropFromJsonArray(const std::string& prop_name,
-                                     const base::Value& value,
-                                     const PropType* base_schema,
-                                     Properties* properties,
-                                     chromeos::ErrorPtr* error) const {
+// Helper function for PropFromJson to handle the case of parameter being
+// defined as a JSON array like this:
+//   "prop":[...]
+std::unique_ptr<PropType> PropFromJsonArray(
+    const base::Value& value,
+    const PropType* base_schema,
+    chromeos::ErrorPtr* error) {
+  std::unique_ptr<PropType> prop;
   const base::ListValue* list = nullptr;
   CHECK(value.GetAsList(&list)) << "Unable to get array value";
   std::string type_name = DetectArrayType(list, base_schema);
-  if (type_name.empty())
-    return ErrorInvalidTypeInfo(prop_name, error);
-  std::unique_ptr<PropType> prop = CreatePropType(type_name, prop_name, error);
-  if (!prop)
-    return false;
+  if (type_name.empty()) {
+    ErrorInvalidTypeInfo(error);
+    return prop;
+  }
   base::DictionaryValue array_object;
   array_object.SetWithoutPathExpansion(commands::attributes::kOneOf_Enum,
                                        list->DeepCopy());
-  if (!prop->FromJson(&array_object, base_schema, error)) {
-    chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
-                                 errors::commands::kInvalidPropDef,
-                                 "Error in definition of property '%s'",
-                                 prop_name.c_str());
-    return false;
-  }
-  properties->insert(std::make_pair(prop_name, std::move(prop)));
-  return true;
+  prop = CreatePropType(type_name, error);
+  if (prop && !prop->FromJson(&array_object, base_schema, error))
+    prop.reset();
+
+  return prop;
 }
 
-static std::string DetectObjectType(const base::DictionaryValue* dict,
-                                    const PropType* base_schema) {
+// Detects a type based on JSON object definition of type. Looks at various
+// members such as minimum/maximum constraints, default and enum values to
+// try to deduce the underlying type of the element. Returns the string name of
+// the type detected or empty string is type detection failed.
+std::string DetectObjectType(const base::DictionaryValue* dict,
+                             const PropType* base_schema) {
   bool has_min_max = dict->HasKey(commands::attributes::kNumeric_Min) ||
                      dict->HasKey(commands::attributes::kNumeric_Max);
 
@@ -251,37 +186,119 @@
   return std::string();
 }
 
-bool ObjectSchema::PropFromJsonObject(const std::string& prop_name,
-                                      const base::Value& value,
-                                      const PropType* base_schema,
-                                      Properties* properties,
-                                      chromeos::ErrorPtr* error) const {
+// Helper function for PropFromJson to handle the case of parameter being
+// defined as a JSON object like this:
+//   "prop":{...}
+std::unique_ptr<PropType> PropFromJsonObject(
+    const base::Value& value,
+    const PropType* base_schema,
+    chromeos::ErrorPtr* error) {
+  std::unique_ptr<PropType> prop;
   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);
+    if (!dict->GetString(commands::attributes::kType, &type_name)) {
+      ErrorInvalidTypeInfo(error);
+      return prop;
+    }
   } else {
     type_name = DetectObjectType(dict, base_schema);
   }
   if (type_name.empty()) {
-    if (!base_schema)
-      return ErrorInvalidTypeInfo(prop_name, error);
+    if (!base_schema) {
+      ErrorInvalidTypeInfo(error);
+      return prop;
+    }
     type_name = base_schema->GetTypeAsString();
   }
-  std::unique_ptr<PropType> prop = CreatePropType(type_name, prop_name, error);
-  if (!prop)
-    return false;
-  if (!prop->FromJson(dict, base_schema, error)) {
-    chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
-                                 errors::commands::kInvalidPropDef,
-                                 "Error in definition of property '%s'",
-                                 prop_name.c_str());
-    return false;
+  prop = CreatePropType(type_name, error);
+  if (prop && !prop->FromJson(dict, base_schema, error))
+    prop.reset();
+
+  return prop;
+}
+
+}  // anonymous namespace
+
+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, chromeos::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());
   }
-  properties->insert(std::make_pair(prop_name, std::move(prop)));
+  return value;
+}
+
+bool ObjectSchema::FromJson(const base::DictionaryValue* value,
+                            const ObjectSchema* object_schema,
+                            chromeos::ErrorPtr* error) {
+  Properties properties;
+  base::DictionaryValue::Iterator iter(*value);
+  while (!iter.IsAtEnd()) {
+    std::string name = iter.key();
+    const PropType* base_schema =
+        object_schema ? object_schema->GetProp(iter.key()) : nullptr;
+    auto prop_type = PropFromJson(iter.value(), base_schema, error);
+    if (prop_type) {
+      properties.insert(std::make_pair(iter.key(), std::move(prop_type)));
+    } else {
+      chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                                   errors::commands::kInvalidPropDef,
+                                   "Error in definition of property '%s'",
+                                   iter.key().c_str());
+      return false;
+    }
+    iter.Advance();
+  }
+  properties_ = std::move(properties);
   return true;
 }
 
+std::unique_ptr<PropType> ObjectSchema::PropFromJson(
+    const base::Value& value,
+    const PropType* base_schema,
+    chromeos::ErrorPtr* error) {
+  if (value.IsType(base::Value::TYPE_STRING)) {
+    // A string value is a short-hand object specification and provides
+    // the parameter type.
+    return PropFromJsonString(value, base_schema, error);
+  } else if (value.IsType(base::Value::TYPE_LIST)) {
+    // One of the enumerated types.
+    return PropFromJsonArray(value, base_schema, error);
+  } else if (value.IsType(base::Value::TYPE_DICTIONARY)) {
+    // Full parameter definition.
+    return PropFromJsonObject(value, base_schema, error);
+  }
+  static const std::map<base::Value::Type, const char*> type_names = {
+    {base::Value::TYPE_NULL, "Null"},
+    {base::Value::TYPE_BOOLEAN, "Boolean"},
+    {base::Value::TYPE_INTEGER, "Integer"},
+    {base::Value::TYPE_DOUBLE, "Double"},
+    {base::Value::TYPE_STRING, "String"},
+    {base::Value::TYPE_BINARY, "Binary"},
+    {base::Value::TYPE_DICTIONARY, "Object"},
+    {base::Value::TYPE_LIST, "Array"},
+  };
+  const char* type_name = chromeos::GetOrDefault(type_names, value.GetType(),
+                                                 "<unknown>");
+  chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                               errors::commands::kUnknownType,
+                               "Unexpected JSON value type: %s", type_name);
+  return {};
+}
+
 }  // namespace buffet