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_