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/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_