blob: 6efdfb61fdf257799b4d6d43d29b4502b34fdc68 [file] [log] [blame]
// 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