| // 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 <chromeos/any.h> |
| #include <chromeos/strings/string_utils.h> |
| |
| #include "buffet/commands/object_schema.h" |
| #include "buffet/commands/prop_values.h" |
| #include "buffet/commands/schema_constants.h" |
| |
| namespace buffet { |
| |
| // PropType ------------------------------------------------------------------- |
| PropType::PropType() { |
| } |
| |
| PropType::~PropType() { |
| } |
| |
| std::string PropType::GetTypeAsString() const { |
| return GetTypeStringFromType(GetType()); |
| } |
| |
| bool PropType::HasOverriddenAttributes() const { |
| if (default_.value && !default_.is_inherited) |
| return true; |
| |
| for (const auto& pair : constraints_) { |
| if (pair.second->HasOverriddenAttributes()) |
| return true; |
| } |
| return false; |
| } |
| |
| std::unique_ptr<base::Value> PropType::ToJson(bool full_schema, |
| chromeos::ErrorPtr* error) const { |
| if (!full_schema && !HasOverriddenAttributes()) { |
| if (based_on_schema_) |
| return std::unique_ptr<base::Value>(new base::DictionaryValue); |
| return TypedValueToJson(GetTypeAsString(), error); |
| } |
| |
| 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>(); |
| } |
| |
| if (default_.value && (full_schema || !default_.is_inherited)) { |
| auto defval = default_.value->ToJson(error); |
| if (!defval) |
| return std::unique_ptr<base::Value>(); |
| dict->Set(commands::attributes::kDefault, defval.release()); |
| } |
| |
| return std::unique_ptr<base::Value>(dict.release()); |
| } |
| |
| std::unique_ptr<PropType> PropType::Clone() const { |
| auto cloned = PropType::Create(GetType()); |
| cloned->based_on_schema_ = based_on_schema_; |
| for (const auto& pair : constraints_) { |
| cloned->constraints_.emplace(pair.first, pair.second->Clone()); |
| } |
| cloned->default_.is_inherited = default_.is_inherited; |
| if (default_.value) |
| cloned->default_.value = default_.value->Clone(); |
| return cloned; |
| } |
| |
| bool PropType::FromJson(const base::DictionaryValue* value, |
| const PropType* base_schema, |
| chromeos::ErrorPtr* error) { |
| if (base_schema && base_schema->GetType() != GetType()) { |
| chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain, |
| errors::commands::kPropTypeChanged, |
| "Redefining a property of type %s as %s", |
| base_schema->GetTypeAsString().c_str(), |
| GetTypeAsString().c_str()); |
| return false; |
| } |
| based_on_schema_ = (base_schema != nullptr); |
| constraints_.clear(); |
| // Add the well-known object properties first (like "type", "displayName", |
| // "default") to the list of "processed" keys so we do not complain about them |
| // when we check for unknown/unexpected keys below. |
| std::set<std::string> processed_keys{ |
| commands::attributes::kType, |
| commands::attributes::kDisplayName, |
| commands::attributes::kDefault, |
| }; |
| if (!ObjectSchemaFromJson(value, base_schema, &processed_keys, error)) |
| return false; |
| if (base_schema) { |
| for (const auto& pair : base_schema->GetConstraints()) { |
| constraints_.emplace(pair.first, pair.second->CloneAsInherited()); |
| } |
| } |
| if (!ConstraintsFromJson(value, &processed_keys, error)) |
| return false; |
| |
| // Now make sure there are no unexpected/unknown keys in the property schema |
| // definition object. |
| base::DictionaryValue::Iterator iter(*value); |
| while (!iter.IsAtEnd()) { |
| std::string key = iter.key(); |
| if (processed_keys.find(key) == processed_keys.end()) { |
| chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain, |
| errors::commands::kUnknownProperty, |
| "Unexpected property '%s'", key.c_str()); |
| return false; |
| } |
| iter.Advance(); |
| } |
| |
| // Read the default value, if specified. |
| // We need to do this last since the current type definition must be complete, |
| // so we can parse and validate the value of the default. |
| const base::Value* defval = nullptr; // Owned by value |
| if (value->GetWithoutPathExpansion(commands::attributes::kDefault, &defval)) { |
| std::unique_ptr<PropValue> prop_value = CreateValue(); |
| if (!prop_value->FromJson(defval, error) || |
| !ValidateValue(prop_value->GetValueAsAny(), error)) { |
| chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain, |
| errors::commands::kInvalidPropValue, |
| "Invalid value for property '%s'", |
| commands::attributes::kDefault); |
| return false; |
| } |
| default_.value = std::move(prop_value); |
| default_.is_inherited = false; |
| } else if (base_schema) { |
| // If we have the base schema, inherit the type's default value from it. |
| // It doesn't matter if the base schema actually has a default value |
| // specified or not. If it doesn't, then the current type definition will |
| // have no default value set either (|default_.value| is a unique_ptr to |
| // PropValue, which can be set to nullptr). |
| if (base_schema->default_.value) |
| default_.value = base_schema->default_.value->Clone(); |
| default_.is_inherited = true; |
| } |
| return true; |
| } |
| |
| void PropType::AddConstraint(std::unique_ptr<Constraint> constraint) { |
| constraints_[constraint->GetType()] = std::move(constraint); |
| } |
| |
| void PropType::RemoveConstraint(ConstraintType constraint_type) { |
| constraints_.erase(constraint_type); |
| } |
| |
| void PropType::RemoveAllConstraints() { |
| constraints_.clear(); |
| } |
| |
| 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::ValidateValue(const base::Value* value, |
| chromeos::ErrorPtr* error) const { |
| std::unique_ptr<PropValue> val = CreateValue(); |
| CHECK(val) << "Failed to create value object"; |
| return val->FromJson(value, error) && ValidateConstraints(*val, error); |
| } |
| |
| bool PropType::ValidateValue(const chromeos::Any& value, |
| chromeos::ErrorPtr* error) const { |
| return !!CreateValue(value, error); |
| } |
| |
| bool PropType::ValidateConstraints(const PropValue& value, |
| chromeos::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"}, |
| {ValueType::Object, "object"}, |
| {ValueType::Array, "array"}, |
| }; |
| 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; |
| case buffet::ValueType::Object: |
| prop = new ObjectPropType; |
| break; |
| case buffet::ValueType::Array: |
| prop = new ArrayPropType; |
| break; |
| } |
| return std::unique_ptr<PropType>(prop); |
| } |
| |
| bool PropType::GenerateErrorValueTypeMismatch(chromeos::ErrorPtr* error) const { |
| chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain, |
| errors::commands::kTypeMismatch, |
| "Unable to convert value to type '%s'", |
| GetTypeAsString().c_str()); |
| return false; |
| } |
| |
| template<typename T> |
| static std::unique_ptr<Constraint> LoadOneOfConstraint( |
| const base::DictionaryValue* value, |
| const PropType* prop_type, |
| chromeos::ErrorPtr* error) { |
| std::unique_ptr<Constraint> constraint; |
| const base::Value* list = nullptr; // Owned by |value| |
| CHECK(value->Get(commands::attributes::kOneOf_Enum, &list)) |
| << "'enum' property missing in JSON dictionary"; |
| native_types::Array choice_list; |
| ArrayPropType array_type; |
| array_type.SetItemType(prop_type->Clone()); |
| if (!TypedValueFromJson(list, &array_type, &choice_list, error)) |
| return constraint; |
| InheritableAttribute<native_types::Array> val(std::move(choice_list), false); |
| constraint.reset(new ConstraintOneOf{std::move(val)}); |
| return constraint; |
| } |
| |
| template<class ConstraintClass, typename T> |
| static std::unique_ptr<Constraint> LoadMinMaxConstraint( |
| const char* dict_key, |
| const base::DictionaryValue* value, |
| chromeos::ErrorPtr* error) { |
| std::unique_ptr<Constraint> constraint; |
| 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, nullptr, &limit.value, error)) |
| return constraint; |
| limit.is_inherited = false; |
| |
| constraint.reset(new ConstraintClass{limit}); |
| return constraint; |
| } |
| |
| // PropTypeBase ---------------------------------------------------------------- |
| |
| template<class Derived, class Value, typename T> |
| bool PropTypeBase<Derived, Value, T>::ConstraintsFromJson( |
| const base::DictionaryValue* value, |
| std::set<std::string>* processed_keys, |
| chromeos::ErrorPtr* error) { |
| if (!PropType::ConstraintsFromJson(value, processed_keys, error)) |
| return false; |
| |
| if (value->HasKey(commands::attributes::kOneOf_Enum)) { |
| auto type = Clone(); |
| type->RemoveAllConstraints(); |
| auto constraint = LoadOneOfConstraint<T>(value, type.get(), error); |
| if (!constraint) |
| return false; |
| this->AddConstraint(std::move(constraint)); |
| this->RemoveConstraint(ConstraintType::Min); |
| this->RemoveConstraint(ConstraintType::Max); |
| processed_keys->insert(commands::attributes::kOneOf_Enum); |
| } |
| |
| return true; |
| } |
| |
| // NumericPropTypeBase --------------------------------------------------------- |
| |
| template<class Derived, class Value, typename T> |
| bool NumericPropTypeBase<Derived, Value, T>::ConstraintsFromJson( |
| const base::DictionaryValue* value, |
| std::set<std::string>* processed_keys, |
| chromeos::ErrorPtr* error) { |
| if (!Base::ConstraintsFromJson(value, processed_keys, error)) |
| return false; |
| |
| if (processed_keys->find(commands::attributes::kOneOf_Enum) == |
| processed_keys->end()) { |
| // Process min/max constraints only if "enum" constraint wasn't already |
| // specified. |
| if (value->HasKey(commands::attributes::kNumeric_Min)) { |
| auto constraint = LoadMinMaxConstraint<ConstraintMin<T>, T>( |
| commands::attributes::kNumeric_Min, value, error); |
| if (!constraint) |
| return false; |
| this->AddConstraint(std::move(constraint)); |
| this->RemoveConstraint(ConstraintType::OneOf); |
| processed_keys->insert(commands::attributes::kNumeric_Min); |
| } |
| if (value->HasKey(commands::attributes::kNumeric_Max)) { |
| auto constraint = LoadMinMaxConstraint<ConstraintMax<T>, T>( |
| commands::attributes::kNumeric_Max, value, error); |
| if (!constraint) |
| return false; |
| this->AddConstraint(std::move(constraint)); |
| this->RemoveConstraint(ConstraintType::OneOf); |
| processed_keys->insert(commands::attributes::kNumeric_Max); |
| } |
| } |
| |
| return true; |
| } |
| |
| // StringPropType ------------------------------------------------------------- |
| |
| bool StringPropType::ConstraintsFromJson( |
| const base::DictionaryValue* value, |
| std::set<std::string>* processed_keys, |
| chromeos::ErrorPtr* error) { |
| if (!Base::ConstraintsFromJson(value, processed_keys, error)) |
| return false; |
| |
| if (processed_keys->find(commands::attributes::kOneOf_Enum) == |
| processed_keys->end()) { |
| // Process min/max constraints only if "enum" constraint wasn't already |
| // specified. |
| if (value->HasKey(commands::attributes::kString_MinLength)) { |
| auto constraint = LoadMinMaxConstraint<ConstraintStringLengthMin, int>( |
| commands::attributes::kString_MinLength, value, error); |
| if (!constraint) |
| return false; |
| AddConstraint(std::move(constraint)); |
| RemoveConstraint(ConstraintType::OneOf); |
| processed_keys->insert(commands::attributes::kString_MinLength); |
| } |
| if (value->HasKey(commands::attributes::kString_MaxLength)) { |
| auto constraint = LoadMinMaxConstraint<ConstraintStringLengthMax, int>( |
| commands::attributes::kString_MaxLength, value, error); |
| if (!constraint) |
| return false; |
| AddConstraint(std::move(constraint)); |
| RemoveConstraint(ConstraintType::OneOf); |
| processed_keys->insert(commands::attributes::kString_MaxLength); |
| } |
| } |
| 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::unique_ptr<ConstraintStringLengthMin>{ |
| new ConstraintStringLengthMin{min_attr}}); |
| AddConstraint(std::unique_ptr<ConstraintStringLengthMax>{ |
| new 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(); |
| } |
| |
| // ObjectPropType ------------------------------------------------------------- |
| |
| ObjectPropType::ObjectPropType() |
| : object_schema_{ObjectSchema::Create(), false} {} |
| |
| bool ObjectPropType::HasOverriddenAttributes() const { |
| return PropType::HasOverriddenAttributes() || |
| !object_schema_.is_inherited; |
| } |
| |
| std::unique_ptr<PropType> ObjectPropType::Clone() const { |
| auto cloned = Base::Clone(); |
| |
| cloned->GetObject()->object_schema_.is_inherited = |
| object_schema_.is_inherited; |
| cloned->GetObject()->object_schema_.value = object_schema_.value->Clone(); |
| return cloned; |
| } |
| |
| std::unique_ptr<base::Value> ObjectPropType::ToJson( |
| bool full_schema, chromeos::ErrorPtr* error) const { |
| std::unique_ptr<base::Value> value = PropType::ToJson(full_schema, error); |
| if (value) { |
| base::DictionaryValue* dict = nullptr; |
| CHECK(value->GetAsDictionary(&dict)) << "Expecting a JSON object"; |
| if (!object_schema_.is_inherited || full_schema) { |
| auto object_schema = object_schema_.value->ToJson(full_schema, error); |
| if (!object_schema) { |
| value.reset(); |
| return value; |
| } |
| dict->SetWithoutPathExpansion(commands::attributes::kObject_Properties, |
| object_schema.release()); |
| dict->SetBooleanWithoutPathExpansion( |
| commands::attributes::kObject_AdditionalProperties, |
| object_schema_.value->GetExtraPropertiesAllowed()); |
| } |
| } |
| return value; |
| } |
| |
| bool ObjectPropType::ObjectSchemaFromJson(const base::DictionaryValue* value, |
| const PropType* base_schema, |
| std::set<std::string>* processed_keys, |
| chromeos::ErrorPtr* error) { |
| if (!Base::ObjectSchemaFromJson(value, base_schema, processed_keys, error)) |
| return false; |
| |
| using commands::attributes::kObject_Properties; |
| using commands::attributes::kObject_AdditionalProperties; |
| |
| const ObjectSchema* base_object_schema = nullptr; |
| if (base_schema) |
| base_object_schema = base_schema->GetObject()->GetObjectSchemaPtr(); |
| |
| const base::DictionaryValue* props = nullptr; |
| if (value->GetDictionaryWithoutPathExpansion(kObject_Properties, &props)) { |
| processed_keys->insert(kObject_Properties); |
| std::unique_ptr<ObjectSchema> object_schema{new ObjectSchema}; |
| if (!object_schema->FromJson(props, base_object_schema, error)) { |
| chromeos::Error::AddTo(error, FROM_HERE, errors::commands::kDomain, |
| errors::commands::kInvalidObjectSchema, |
| "Error parsing object property schema"); |
| return false; |
| } |
| bool extra_properties_allowed = false; |
| if (value->GetBooleanWithoutPathExpansion(kObject_AdditionalProperties, |
| &extra_properties_allowed)) { |
| processed_keys->insert(kObject_AdditionalProperties); |
| object_schema->SetExtraPropertiesAllowed(extra_properties_allowed); |
| } |
| object_schema_.value = std::move(object_schema); |
| object_schema_.is_inherited = false; |
| } else if (base_object_schema) { |
| auto cloned_object_schema = base_object_schema->Clone(); |
| bool extra_properties_allowed = false; |
| if (value->GetBooleanWithoutPathExpansion(kObject_AdditionalProperties, |
| &extra_properties_allowed)) { |
| processed_keys->insert(kObject_AdditionalProperties); |
| cloned_object_schema->SetExtraPropertiesAllowed(extra_properties_allowed); |
| object_schema_.is_inherited = false; |
| } else { |
| object_schema_.is_inherited = true; |
| } |
| object_schema_.value = std::move(cloned_object_schema); |
| } else { |
| chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain, |
| errors::commands::kInvalidObjectSchema, |
| "Object type definition must include the " |
| "object schema ('%s' field not found)", |
| kObject_Properties); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| chromeos::Any ObjectPropType::ConvertArrayToDBusVariant( |
| const native_types::Array& source) const { |
| std::vector<chromeos::VariantDictionary> result; |
| result.reserve(source.size()); |
| for (const auto& prop_value : source) { |
| chromeos::Any dict = PropValueToDBusVariant(prop_value.get()); |
| result.push_back(std::move(*dict.GetPtr<chromeos::VariantDictionary>())); |
| } |
| return result; |
| } |
| |
| bool ObjectPropType::ConvertDBusVariantToArray( |
| const chromeos::Any& source, |
| native_types::Array* result, |
| chromeos::ErrorPtr* error) const { |
| if (!source.IsTypeCompatible<std::vector<chromeos::VariantDictionary>>()) |
| return GenerateErrorValueTypeMismatch(error); |
| |
| const auto& source_array = |
| source.Get<std::vector<chromeos::VariantDictionary>>(); |
| result->reserve(source_array.size()); |
| for (const auto& value : source_array) { |
| auto prop_value = PropValueFromDBusVariant(this, value, error); |
| if (!prop_value) |
| return false; |
| result->push_back(std::move(prop_value)); |
| } |
| return true; |
| } |
| |
| void ObjectPropType::SetObjectSchema( |
| std::unique_ptr<const ObjectSchema> schema) { |
| object_schema_.value = std::move(schema); |
| object_schema_.is_inherited = false; |
| } |
| |
| // ArrayPropType ------------------------------------------------------------- |
| |
| ArrayPropType::ArrayPropType() {} |
| |
| bool ArrayPropType::HasOverriddenAttributes() const { |
| return PropType::HasOverriddenAttributes() || |
| !item_type_.is_inherited; |
| } |
| |
| std::unique_ptr<PropType> ArrayPropType::Clone() const { |
| auto cloned = Base::Clone(); |
| |
| cloned->GetArray()->item_type_.is_inherited = item_type_.is_inherited; |
| cloned->GetArray()->item_type_.value = item_type_.value->Clone(); |
| return cloned; |
| } |
| |
| std::unique_ptr<base::Value> ArrayPropType::ToJson( |
| bool full_schema, chromeos::ErrorPtr* error) const { |
| std::unique_ptr<base::Value> value = PropType::ToJson(full_schema, error); |
| if (value) { |
| base::DictionaryValue* dict = nullptr; |
| CHECK(value->GetAsDictionary(&dict)) << "Expecting a JSON object"; |
| if (!item_type_.is_inherited || full_schema) { |
| auto type = item_type_.value->ToJson(full_schema, error); |
| if (!type) { |
| value.reset(); |
| return value; |
| } |
| dict->SetWithoutPathExpansion(commands::attributes::kItems, |
| type.release()); |
| } |
| } |
| return value; |
| } |
| |
| bool ArrayPropType::ObjectSchemaFromJson(const base::DictionaryValue* value, |
| const PropType* base_schema, |
| std::set<std::string>* processed_keys, |
| chromeos::ErrorPtr* error) { |
| if (!Base::ObjectSchemaFromJson(value, base_schema, processed_keys, error)) |
| return false; |
| |
| using commands::attributes::kItems; |
| |
| const PropType* base_type = nullptr; |
| if (base_schema) |
| base_type = base_schema->GetArray()->GetItemTypePtr(); |
| |
| const base::Value* type_value = nullptr; |
| if (value->GetWithoutPathExpansion(kItems, &type_value)) { |
| processed_keys->insert(kItems); |
| auto item_type = ObjectSchema::PropFromJson(*type_value, base_type, error); |
| if (!item_type) |
| return false; |
| if (item_type->GetType() == ValueType::Array) { |
| chromeos::Error::AddTo(error, FROM_HERE, errors::commands::kDomain, |
| errors::commands::kInvalidObjectSchema, |
| "Arrays of arrays are not supported"); |
| return false; |
| } |
| SetItemType(std::move(item_type)); |
| } else if (!item_type_.value) { |
| if (base_type) { |
| item_type_.value = base_type->Clone(); |
| item_type_.is_inherited = true; |
| } else { |
| chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain, |
| errors::commands::kInvalidObjectSchema, |
| "Array type definition must include the " |
| "array item type ('%s' field not found)", |
| kItems); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| void ArrayPropType::SetItemType(std::unique_ptr<const PropType> item_type) { |
| item_type_.value = std::move(item_type); |
| item_type_.is_inherited = false; |
| } |
| |
| } // namespace buffet |