Completely rework Validator API

This commit is contained in:
Bruno Martins
2019-05-23 12:30:50 -04:00
committed by mdavidsaver
parent 9923459c4b
commit f22b5d5b7b
7 changed files with 547 additions and 631 deletions

View File

@ -41,7 +41,6 @@ LIBSRCS += ntcontinuum.cpp
LIBSRCS += nthistogram.cpp
LIBSRCS += nturi.cpp
LIBSRCS += ntndarrayAttribute.cpp
LIBSRCS += validator.cpp
LIBRARY = nt

View File

@ -5,6 +5,7 @@
*/
#include <pv/lock.h>
#include "validator.h"
#define epicsExportSharedSymbols
#include <pv/ntfield.h>
@ -31,198 +32,103 @@ NTField::NTField()
{
}
Result& NTField::isEnumerated(Result& result)
{
return result
.is<Structure>()
.has<Scalar>("index")
.has<ScalarArray>("choices");
}
bool NTField::isEnumerated(FieldConstPtr const & field)
{
if(field->getType()!=structure) return false;
StructureConstPtr structurePtr = static_pointer_cast<const Structure>(field);
FieldConstPtrArray fields = structurePtr->getFields();
StringArray names = structurePtr->getFieldNames();
size_t n = structurePtr->getNumberFields();
if(n!=2) return false;
FieldConstPtr f = fields[0];
if(names[0].compare("index")!=0) return false;
if(f->getType()!=scalar) return false;
ScalarConstPtr s = static_pointer_cast<const Scalar>(f);
if(s->getScalarType()!=pvInt) return false;
f = fields[1];
if(names[1].compare("choices")!=0) return false;
if(f->getType()!=scalarArray) return false;
ScalarConstPtr sa = static_pointer_cast<const Scalar>(f);
if(sa->getScalarType()!=pvString) return false;
return true;
Result result(field);
return isEnumerated(result).valid();
}
Result& NTField::isTimeStamp(Result& result)
{
return result
.is<Structure>()
.has<Scalar>("secondsPastEpoch")
.has<Scalar>("nanoseconds")
.has<Scalar>("userTag");
}
bool NTField::isTimeStamp(FieldConstPtr const & field)
{
if(field->getType()!=structure) return false;
StructureConstPtr structurePtr = static_pointer_cast<const Structure>(field);
FieldConstPtrArray fields = structurePtr->getFields();
StringArray names = structurePtr->getFieldNames();
size_t n = structurePtr->getNumberFields();
if(n!=3) return false;
FieldConstPtr f = fields[0];
if(names[0].compare("secondsPastEpoch")!=0) return false;
if(f->getType()!=scalar) return false;
ScalarConstPtr s = static_pointer_cast<const Scalar>(f);
if(s->getScalarType()!=pvLong) return false;
f = fields[1];
if(names[1].compare("nanoseconds")!=0) return false;
if(f->getType()!=scalar) return false;
s = static_pointer_cast<const Scalar>(f);
if(s->getScalarType()!=pvInt) return false;
f = fields[2];
if(names[2].compare("userTag")!=0) return false;
if(f->getType()!=scalar) return false;
s = static_pointer_cast<const Scalar>(f);
if(s->getScalarType()!=pvInt) return false;
return true;
Result result(field);
return isTimeStamp(result).valid();
}
Result& NTField::isAlarm(Result& result)
{
return result
.is<Structure>()
.has<Scalar>("severity")
.has<Scalar>("status")
.has<Scalar>("message");
}
bool NTField::isAlarm(FieldConstPtr const & field)
{
if(field->getType()!=structure) return false;
StructureConstPtr structurePtr = static_pointer_cast<const Structure>(field);
FieldConstPtrArray fields = structurePtr->getFields();
StringArray names = structurePtr->getFieldNames();
size_t n = structurePtr->getNumberFields();
if(n!=3) return false;
FieldConstPtr f = fields[0];
if(names[0].compare("severity")!=0) return false;
if(f->getType()!=scalar) return false;
ScalarConstPtr s = static_pointer_cast<const Scalar>(f);
if(s->getScalarType()!=pvInt) return false;
f = fields[1];
if(names[1].compare("status")!=0) return false;
if(f->getType()!=scalar) return false;
s = static_pointer_cast<const Scalar>(f);
if(s->getScalarType()!=pvInt) return false;
f = fields[2];
if(names[2].compare("message")!=0) return false;
if(f->getType()!=scalar) return false;
s = static_pointer_cast<const Scalar>(f);
if(s->getScalarType()!=pvString) return false;
return true;
Result result(field);
return isAlarm(result).valid();
}
Result& NTField::isDisplay(Result& result)
{
return result
.is<Structure>()
.has<Scalar>("limitLow")
.has<Scalar>("limitHigh")
.has<Scalar>("description")
.has<Scalar>("format")
.has<Scalar>("units");
}
bool NTField::isDisplay(FieldConstPtr const & field)
{
if(field->getType()!=structure) return false;
StructureConstPtr structurePtr = static_pointer_cast<const Structure>(field);
FieldConstPtrArray fields = structurePtr->getFields();
StringArray names = structurePtr->getFieldNames();
size_t n = structurePtr->getNumberFields();
if(n!=5) return false;
FieldConstPtr f = fields[0];
if(names[0].compare("limitLow")!=0) return false;
if(f->getType()!=scalar) return false;
ScalarConstPtr s = static_pointer_cast<const Scalar>(f);
if(s->getScalarType()!=pvDouble) return false;
f = fields[1];
if(names[1].compare("limitHigh")!=0) return false;
if(f->getType()!=scalar) return false;
s = static_pointer_cast<const Scalar>(f);
if(s->getScalarType()!=pvDouble) return false;
f = fields[2];
if(names[2].compare("description")!=0) return false;
if(f->getType()!=scalar) return false;
s = static_pointer_cast<const Scalar>(f);
if(s->getScalarType()!=pvString) return false;
f = fields[3];
if(names[3].compare("format")!=0) return false;
if(f->getType()!=scalar) return false;
s = static_pointer_cast<const Scalar>(f);
if(s->getScalarType()!=pvString) return false;
f = fields[4];
if(names[4].compare("units")!=0) return false;
if(f->getType()!=scalar) return false;
s = static_pointer_cast<const Scalar>(f);
if(s->getScalarType()!=pvString) return false;
return true;
Result result(field);
return isDisplay(result).valid();
}
Result& NTField::isAlarmLimit(Result& result)
{
return result
.is<Structure>()
.has<Scalar>("active")
.has<Scalar>("lowAlarmLimit")
.has<Scalar>("lowWarningLimit")
.has<Scalar>("highWarningLimit")
.has<Scalar>("highAlarmLimit")
.has<Scalar>("lowAlarmSeverity")
.has<Scalar>("lowWarningSeverity")
.has<Scalar>("highWarningSeverity")
.has<Scalar>("highAlarmSeverity")
.has<Scalar>("hysteresis");
}
bool NTField::isAlarmLimit(FieldConstPtr const & field)
{
if(field->getType()!=structure) return false;
StructureConstPtr structurePtr = static_pointer_cast<const Structure>(field);
FieldConstPtrArray fields = structurePtr->getFields();
StringArray names = structurePtr->getFieldNames();
size_t n = structurePtr->getNumberFields();
if(n!=10) return false;
FieldConstPtr f = fields[0];
if(names[0].compare("active")!=0) return false;
if(f->getType()!=scalar) return false;
ScalarConstPtr s = static_pointer_cast<const Scalar>(f);
if(s->getScalarType()!=pvBoolean) return false;
f = fields[1];
if(names[1].compare("lowAlarmLimit")!=0) return false;
if(f->getType()!=scalar) return false;
s = static_pointer_cast<const Scalar>(f);
if(s->getScalarType()!=pvDouble) return false;
f = fields[2];
if(names[2].compare("lowWarningLimit")!=0) return false;
if(f->getType()!=scalar) return false;
s = static_pointer_cast<const Scalar>(f);
if(s->getScalarType()!=pvDouble) return false;
f = fields[3];
if(names[3].compare("highWarningLimit")!=0) return false;
if(f->getType()!=scalar) return false;
s = static_pointer_cast<const Scalar>(f);
if(s->getScalarType()!=pvDouble) return false;
f = fields[4];
if(names[4].compare("highAlarmLimit")!=0) return false;
if(f->getType()!=scalar) return false;
s = static_pointer_cast<const Scalar>(f);
if(s->getScalarType()!=pvDouble) return false;
f = fields[5];
if(names[5].compare("lowAlarmSeverity")!=0) return false;
if(f->getType()!=scalar) return false;
s = static_pointer_cast<const Scalar>(f);
if(s->getScalarType()!=pvInt) return false;
f = fields[6];
if(names[6].compare("lowWarningSeverity")!=0) return false;
if(f->getType()!=scalar) return false;
s = static_pointer_cast<const Scalar>(f);
if(s->getScalarType()!=pvInt) return false;
f = fields[7];
if(names[7].compare("highWarningSeverity")!=0) return false;
if(f->getType()!=scalar) return false;
s = static_pointer_cast<const Scalar>(f);
if(s->getScalarType()!=pvInt) return false;
f = fields[8];
if(names[8].compare("highAlarmSeverity")!=0) return false;
if(f->getType()!=scalar) return false;
s = static_pointer_cast<const Scalar>(f);
if(s->getScalarType()!=pvInt) return false;
f = fields[9];
if(names[9].compare("hysteresis")!=0) return false;
if(f->getType()!=scalar) return false;
return true;
Result result(field);
return isAlarmLimit(result).valid();
}
Result& NTField::isControl(Result& result)
{
return result
.is<Structure>()
.has<Scalar>("limitLow")
.has<Scalar>("limitHigh")
.has<Scalar>("minStep");
}
bool NTField::isControl(FieldConstPtr const & field)
{
if(field->getType()!=structure) return false;
StructureConstPtr structurePtr = static_pointer_cast<const Structure>(field);
FieldConstPtrArray fields = structurePtr->getFields();
StringArray names = structurePtr->getFieldNames();
size_t n = structurePtr->getNumberFields();
if(n!=3) return false;
FieldConstPtr f = fields[0];
if(names[0].compare("limitLow")!=0) return false;
if(f->getType()!=scalar) return false;
ScalarConstPtr s = static_pointer_cast<const Scalar>(f);
if(s->getScalarType()!=pvDouble) return false;
f = fields[1];
if(names[1].compare("limitHigh")!=0) return false;
if(f->getType()!=scalar) return false;
s = static_pointer_cast<const Scalar>(f);
if(s->getScalarType()!=pvDouble) return false;
f = fields[2];
if(names[2].compare("minStep")!=0) return false;
if(f->getType()!=scalar) return false;
s = static_pointer_cast<const Scalar>(f);
if(s->getScalarType()!=pvDouble) return false;
return true;
Result result(field);
return isControl(result).valid();
}
StructureConstPtr NTField::createEnumerated()

View File

@ -175,50 +175,75 @@ bool NTNDArray::is_a(PVStructurePtr const & pvStructure)
return is_a(pvStructure->getStructure());
}
static Validator* validator;
static epicsThreadOnceId validator_once = EPICS_THREAD_ONCE_INIT;
namespace {
Result& isValue(Result& result)
{
result.is<Union>(Union::defaultId());
static void validator_init(void *)
{
StructureConstPtr structure(
NTNDArray::createBuilder()->
addDescriptor()->
addAlarm()->
addDisplay()->
addTimeStamp()->
createStructure());
for (int i = pvBoolean; i < pvString; ++i) {
ScalarType type = static_cast<ScalarType>(i);
string name(ScalarTypeFunc::name(type));
result.has<ScalarArray>(name + "Value");
}
std::set<Field const *> optional;
return result;
}
// TODO: these should be all getFieldT
optional.insert(structure->getField("descriptor").get());
optional.insert(structure->getField("alarm").get());
optional.insert(structure->getField("timeStamp").get());
optional.insert(structure->getField("display").get());
Result& isCodec(Result& result)
{
return result
.is<Structure>("codec_t")
.has<Scalar>("name")
.has<Union>("parameters");
}
// TODO: the following should come from a helper in NTNDArrayAttribute
StructureConstPtr attribute(
std::static_pointer_cast<const StructureArray>(
structure->getField("attribute")
)->getStructure()
);
Result& isDimension(Result& result)
{
return result
.is<StructureArray>("dimension_t[]")
.has<Scalar>("size")
.has<Scalar>("offset")
.has<Scalar>("fullSize")
.has<Scalar>("binning")
.has<Scalar>("reverse");
}
optional.insert(attribute->getField("tags").get());
optional.insert(attribute->getField("alarm").get());
optional.insert(attribute->getField("timeStamp").get());
validator = new Validator(structure, optional);
// TODO: move to NTNDArrayAttribute
Result& isAttribute(Result& result)
{
return result
.is<StructureArray>()
.has<Scalar>("name")
.has<Union>("value")
.maybeHas<ScalarArray>("tags")
.has<Scalar>("descriptor")
.maybeHas<&NTField::isAlarm>("alarm")
.maybeHas<&NTField::isTimeStamp>("timeStamp");
}
}
// TODO: I want to deprecate this and replace it with one that accepts
// "Field const &" so nullptr shouldn't have to be checked for here
bool NTNDArray::isCompatible(StructureConstPtr const &structure)
{
if(!structure.get()) return false;
if (!structure)
return false;
epicsThreadOnce(&validator_once, &validator_init, 0);
Result result(structure);
return validator->isCompatible(*structure);
return result
.is<Structure>()
.has<&isValue>("value")
.has<&isCodec>("codec")
.has<Scalar>("compressedSize")
.has<Scalar>("uncompressedSize")
.has<&isDimension>("dimension")
.has<Scalar>("uniqueId")
.has<&NTField::isTimeStamp>("dataTimeStamp")
.has<&isAttribute>("attribute")
.maybeHas<Scalar>("descriptor")
.maybeHas<&NTField::isAlarm>("alarm")
.maybeHas<&NTField::isTimeStamp>("timeStamp")
.maybeHas<&NTField::isDisplay>("display")
.valid();
}

View File

@ -30,6 +30,8 @@
namespace epics { namespace nt {
class Result;
class NTField;
typedef std::tr1::shared_ptr<NTField> NTFieldPtr;
@ -149,6 +151,16 @@ private:
NTField();
epics::pvData::FieldCreatePtr fieldCreate;
epics::pvData::StandardFieldPtr standardField;
// These won't be public just yet
static Result& isEnumerated(Result&);
static Result& isTimeStamp(Result&);
static Result& isAlarm(Result&);
static Result& isDisplay(Result&);
static Result& isAlarmLimit(Result&);
static Result& isControl(Result&);
friend class NTNDArray;
};
/**

View File

@ -1,272 +0,0 @@
/* ntvalidator.cpp */
/*
* Copyright information and license terms for this software can be
* found in the file LICENSE that is included with the distribution
*/
#include <sstream>
#include <algorithm>
#include <deque>
#include "validator.h"
#define epicsExportSharedSymbols
#include <pv/bitSet.h>
namespace epics { namespace nt {
using epics::pvData::Type;
using epics::pvData::Field;
using epics::pvData::Union;
using epics::pvData::UnionArray;
using epics::pvData::Structure;
using epics::pvData::StructureConstPtr;
using epics::pvData::StructureArray;
using epics::pvData::StringArray;
using epics::pvData::FieldConstPtr;
using epics::pvData::FieldConstPtrArray;
using epics::pvData::BitSet;
using std::string;
using std::set;
struct Validator::Helper {
Validator const & validator;
std::deque<string> path;
Validator::Result result;
Helper(Validator const & validator);
bool isOptional(Field const & field) const;
string getCurrentPath(void) const;
void appendError(Validator::ErrorType type);
void appendError(Validator::ErrorType type, string const & field_name);
void appendError(Validator::ErrorType type, string const & ref_field_name,
string const & field_name);
bool validate(Union const & ref, Union const & un);
bool validate(Structure const & ref, Structure const & struc);
bool validate(Field const & reference, Field const & field);
bool validate(Field const & field);
};
Validator::Helper::Helper(Validator const & validator) : validator(validator) {}
bool Validator::Helper::isOptional(Field const & field) const
{
return validator.optional.find(&field) != validator.optional.end();
}
string Validator::Helper::getCurrentPath(void) const
{
std::ostringstream os;
// TODO: is there a better string join?
std::deque<string>::const_iterator it;
for (it = path.cbegin(); it != path.cend(); ++it) {
if (it != path.cbegin())
os << ".";
os << *it;
}
return os.str();
}
void Validator::Helper::appendError(Validator::ErrorType type)
{
result.errors.push_back(Validator::Error(getCurrentPath(), type));
}
void Validator::Helper::appendError(Validator::ErrorType type, string const & field_name)
{
path.push_back(field_name);
appendError(type);
path.pop_back();
}
void Validator::Helper::appendError(Validator::ErrorType type, string const & ref_field_name,
string const & field_name)
{
path.push_back(ref_field_name);
string ref_field_path = getCurrentPath();
path.pop_back();
path.push_back(field_name);
string field_path(getCurrentPath());
path.pop_back();
result.errors.push_back(Validator::Error(ref_field_path, field_path, type));
}
bool Validator::Helper::validate(Union const & ref, Union const & un)
{
if (&un == &ref)
return true;
if (ref.isVariant() != un.isVariant()) {
appendError(Validator::ErrorType::INCORRECT_TYPE);
return false;
}
if (ref.isVariant())
return true;
StringArray const & rnames = ref.getFieldNames();
StringArray const & unames = un.getFieldNames();
FieldConstPtrArray const & rfields = ref.getFields();
FieldConstPtrArray const & ufields = un.getFields();
size_t numRefFields = ref.getNumberFields();
size_t numUnFields = un.getNumberFields();
// Extra fields are OK
bool ok = numRefFields <= numUnFields;
size_t i = 0;
size_t N = std::min(numRefFields, numUnFields);
for (; i < N; ++i) {
string const & rname(rnames[i]);
string const & uname(unames[i]);
if (rname != uname) {
appendError(Validator::ErrorType::MISMATCHED_NAME, rname, uname);
ok = false;
continue;
}
path.push_back(rname);
ok = ok && validate(*rfields[i], *ufields[i]);
path.pop_back();
}
for (; i < numRefFields; ++i)
appendError(Validator::ErrorType::MISSING_FIELD, rnames[i]);
return ok;
}
bool Validator::Helper::validate(Structure const &ref, Structure const &struc)
{
if (&struc == &ref)
return true;
StringArray const &rnames = ref.getFieldNames();
StringArray const &snames = struc.getFieldNames();
// TODO: This is naive O(N^2), replace with better algorithm (cache?)
// TODO: This assumes getField doesn't fail (there's no Field::getFieldT (yet?))
bool ok = true;
StringArray::const_iterator ri;
for (ri = rnames.cbegin(); ri != rnames.cend(); ++ri) {
FieldConstPtr rfield(ref.getField(*ri));
if (std::find(snames.cbegin(), snames.cend(), *ri) == snames.end()) {
if (isOptional(*rfield))
continue;
appendError(Validator::ErrorType::MISSING_FIELD, *ri);
ok = false;
} else {
path.push_back(*ri);
if (!validate(*rfield, *struc.getField(*ri)))
{
appendError(Validator::ErrorType::INCORRECT_TYPE, *ri);
ok = false;
}
path.pop_back();
}
}
return ok;
}
bool Validator::Helper::validate(Field const &reference, Field const &field)
{
Type referenceType = reference.getType();
if (referenceType != field.getType())
return false;
switch (referenceType) {
case Type::scalar:
case Type::scalarArray:
return true;
case Type::structure:
return validate(
static_cast<Structure const &>(reference),
static_cast<Structure const &>(field));
case Type::structureArray:
return validate(
*static_cast<StructureArray const &>(reference).getStructure(),
*static_cast<StructureArray const &>(field).getStructure());
case Type::union_:
return validate(
static_cast<Union const &>(reference),
static_cast<Union const &>(field));
case Type::unionArray:
return validate(
*static_cast<UnionArray const &>(reference).getUnion(),
*static_cast<UnionArray const &>(field).getUnion());
default:
throw std::logic_error("Unknown Field type");
return false;
}
}
bool Validator::Helper::validate(Field const &field)
{
return validate(*validator.reference, field);
}
std::ostream& Validator::Error::dump(std::ostream& o) const {
switch (type) {
case ErrorType::MISSING_FIELD:
return o << "Missing field '" << ref_path << "'";
case ErrorType::INCORRECT_TYPE:
return o << "Field '" << ref_path << "' has incorrect type";
case ErrorType::MISMATCHED_NAME:
return o << "Expected field '" << ref_path
<< "' in Union, got '" << path << "'";
default:
return o << "Unknown error " << type << " in field '"
<< ref_path << "'";
}
}
Validator::Validator(FieldConstPtr const & reference)
: reference(reference) {
if (!reference)
throw std::logic_error("reference structure must not be NULL");
}
Validator::Validator(FieldConstPtr const & reference,
set<Field const *> const & optional)
: reference(reference), optional(optional) {
if (!reference)
throw std::logic_error("reference structure must not be NULL");
}
Validator::Result Validator::validate(Field const & field) const
{
Helper helper(*this);
helper.validate(field);
return helper.result;
}
bool Validator::isCompatible(Field const & field) const
{
return Helper(*this).validate(field);
}
std::ostream& operator<<(std::ostream& o, const Validator::Error& error)
{
return error.dump(o);
}
}}

View File

@ -20,61 +20,152 @@ namespace epics { namespace nt {
* @author bsm
*/
class Validator {
public:
enum ErrorType { MISSING_FIELD, INCORRECT_TYPE, MISMATCHED_NAME };
struct Helper;
struct Result {
struct Error {
std::string ref_path;
std::string path;
ErrorType type;
enum Type {
MissingField,
IncorrectType,
IncorrectId,
} type;
Error(std::string const & ref_path, ErrorType type)
: ref_path(ref_path), type(type) {}
Error(std::string const & ref_path, std::string const & path, ErrorType type)
: ref_path(ref_path), path(path), type(type) {}
Error(std::string const & ref_path, Type type)
: path(), type(type) {}
bool operator==(const Error& other) const {
return type == other.type &&
ref_path == other.ref_path &&
path == other.path;
return type == other.type && path == other.path;
}
std::ostream& dump(std::ostream&) const;
std::ostream& dump(std::ostream& os) const {
os << "Error(path=" << (path.empty() ? "<root>" : path) << ": ";
switch(type) {
case MissingField: os << "missing"; break;
case IncorrectType: os << "incorrect type"; break;
case IncorrectId: os << "incorrect ID"; break;
}
os << ")";
return os;
}
};
struct Result {
std::vector<Error> errors;
bool valid(void) const { return errors.empty(); }
std::ostream& dump(std::ostream&) const;
};
const epics::pvData::FieldConstPtr f;
const std::string path;
std::vector<Error> errors;
Validator(epics::pvData::FieldConstPtr const & reference);
enum result_t {
Pass,
Fail,
} result;
Validator(epics::pvData::FieldConstPtr const & reference,
std::set<epics::pvData::Field const *> const & optional);
explicit Result(const epics::pvData::FieldConstPtr& f, const std::string& path = std::string())
: f(f), path(path), errors(), result(Pass) {}
Result validate(epics::pvData::Field const & field) const;
Result& operator|=(const Result& other) {
result = std::max(result, other.result);
errors.insert(errors.end(), other.errors.begin(), other.errors.end());
return *this;
}
/**
* Checks whether a Field is compatible with a NT definition.
* @param definition the definition of the reference structure
* @param field the Field being tested for compatibility
* @return true if field is compatible, false otherwise
*/
bool isCompatible(epics::pvData::Field const & field) const;
bool valid(void) const {
return result == Pass;
}
template<typename T>
Result& is(void) {
if (!dynamic_cast<T const *>(f.get())) {
result = Fail;
errors.push_back(Error(path, Error::IncorrectType));
}
return *this;
}
template<typename T>
Result& is(const std::string& id) {
T const *s = dynamic_cast<T const *>(f.get());
if (!s) {
result = Fail;
errors.push_back(Error(path, Error::IncorrectType));
} else if (s->getID() != id) {
result = Fail;
errors.push_back(Error(path, Error::IncorrectId));
}
return *this;
}
template<Result& (*fn)(Result&)>
Result& has(const std::string& name) {
return has<epics::pvData::Field>(name, false, fn);
}
template<Result& (*fn)(Result&)>
Result& maybeHas(const std::string& name) {
return has<epics::pvData::Field>(name, true, fn);
}
template<typename T>
Result& has(const std::string& name) {
return has<T>(name, false, NULL);
}
template<typename T>
Result& maybeHas(const std::string& name) {
return has<T>(name, true, NULL);
}
std::ostream& dump(std::ostream& os) const {
os << "Result(valid=" << valid() << ", errors=[ ";
std::vector<Error>::const_iterator it;
for (it = errors.cbegin(); it != errors.cend(); ++it) {
(*it).dump(os);
os << " ";
}
os << "])";
return os;
}
private:
friend struct Helper;
epics::pvData::FieldConstPtr const reference;
std::set<epics::pvData::Field const *> optional;
template<typename T>
Result& has(const std::string& name, bool optional, Result& (*check)(Result&) = NULL) {
epics::pvData::FieldConstPtr field;
switch(f->getType()) {
case epics::pvData::Type::structure:
field = static_cast<epics::pvData::Structure const *>(f.get())->getField(name);
break;
case epics::pvData::Type::structureArray:
field = static_cast<epics::pvData::StructureArray const *>(f.get())->getStructure()->getField(name);
break;
case epics::pvData::Type::union_:
field = static_cast<epics::pvData::Union const *>(f.get())->getField(name);
break;
case epics::pvData::Type::unionArray:
field = static_cast<epics::pvData::UnionArray const *>(f.get())->getUnion()->getField(name);
break;
default:
// Expected a structure-like Field
result = Fail;
errors.push_back(Error(path, Error::Type::IncorrectType));
return *this;
}
if (!field) {
if (!optional) {
result = Fail;
errors.push_back(Error(path + "." + name, Error::Type::MissingField));
}
} else if (!dynamic_cast<T const *>(field.get())) {
result = Fail;
errors.push_back(Error(path + "." + name, Error::Type::IncorrectType));
} else if (check) {
Result r(field, path + "." + name);
*this |= check(r);
}
return *this;
}
};
epicsShareExtern std::ostream& operator<<(std::ostream& o, const Validator::Error& error);
}}
#endif /* VALIDATOR_H */
#endif

View File

@ -12,156 +12,311 @@
using namespace epics::nt;
using epics::pvData::StructureConstPtr;
using epics::pvData::UnionConstPtr;
using epics::pvData::Field;
using epics::pvData::ScalarType;
using epics::pvData::ScalarTypeFunc::name;
using epics::pvData::FieldConstPtr;
using epics::pvData::FieldBuilder;
using epics::pvData::FieldBuilderPtr;
using epics::pvData::Scalar;
using epics::pvData::ScalarArray;
using epics::pvData::Structure;
using epics::pvData::Union;
static epics::pvData::FieldCreatePtr FC;
void test_result()
void test_is()
{
testDiag("test_result");
testDiag("test_is");
Validator::Result result;
testOk(result.valid(), "Result with no errors means valid");
// Result::is<Scalar> must be valid for Scalars of any type
for(int i = ScalarType::pvBoolean; i <= ScalarType::pvString; ++i) {
ScalarType t = static_cast<ScalarType>(i);
testOk(Result(FC->createScalar(t)).is<Scalar>().valid(),
"Result(Scalar<%s>).is<Scalar>().valid()", name(t));
}
result.errors.push_back(Validator::Error("a.b.c", Validator::ErrorType::MISSING_FIELD));
testOk(!result.valid(), "Result with one error means invalid");
}
void test_scalar()
{
testDiag("test_scalar");
{
FieldConstPtr def(FC->createScalar(ScalarType::pvBoolean));
FieldConstPtr field(FC->createScalar(ScalarType::pvBoolean));
testOk(Validator(def).isCompatible(*field), "isCompatible(Scalar<bool>, Scalar<bool>)");
// Result::is<ScalarArray> must be valid for ScalarArray of any type
for(int i = ScalarType::pvBoolean; i <= ScalarType::pvString; ++i) {
ScalarType t = static_cast<ScalarType>(i);
testOk(Result(FC->createScalarArray(t)).is<ScalarArray>().valid(),
"Result(ScalarArray<%s>).is<ScalarArray>().valid()", name(t));
}
{
FieldConstPtr def(FC->createScalar(ScalarType::pvBoolean));
FieldConstPtr field(FC->createScalar(ScalarType::pvInt));
testOk(Validator(def).isCompatible(*field), "isCompatible(Scalar<bool>, Scalar<int>)");
// Result::is<Scalar> must be invalid for non-Scalar
Result result(FC->createScalarArray(ScalarType::pvInt));
result.is<Scalar>();
testOk(!result.valid(), "!Result(ScalarArray<pvInt>).is<Scalar>.valid()");
testOk1(result.errors.at(0) == Result::Error("", Result::Error::IncorrectType));
}
{
FieldConstPtr def(FC->createScalar(ScalarType::pvString));
FieldConstPtr field(FC->createScalar(ScalarType::pvFloat));
testOk(Validator(def).isCompatible(*field), "isCompatible(Scalar<string>, Scalar<float>)");
}
{
FieldConstPtr def(FC->createScalarArray(ScalarType::pvString));
FieldConstPtr field(FC->createScalarArray(ScalarType::pvFloat));
testOk(Validator(def).isCompatible(*field), "isCompatible(ScalarArray<string>, ScalarArray<float>)");
}
{
FieldConstPtr def(FC->createScalarArray(ScalarType::pvByte));
FieldConstPtr field(FC->createScalar(ScalarType::pvByte));
testOk(!Validator(def).isCompatible(*field), "!isCompatible(ScalarArray<byte>, Scalar<byte>)");
}
{
FieldConstPtr def(FC->createScalar(ScalarType::pvByte));
FieldConstPtr field(FC->createScalarArray(ScalarType::pvByte));
testOk(!Validator(def).isCompatible(*field), "!isCompatible(Scalar<byte>, ScalarArray<byte>)");
// Result::is<ScalarArray> must be invalid for non-ScalarArray
Result result(FC->createScalar(ScalarType::pvInt));
result.is<ScalarArray>();
testOk(!result.valid(), "!Result(ScalarArray<pvInt>).is<Scalar>.valid()");
testOk1(result.errors.at(0) == Result::Error("", Result::Error::IncorrectType));
}
}
void test_union()
void test_is_id()
{
testDiag("test_union");
testDiag("test_is_id");
FieldBuilderPtr FB(FieldBuilder::begin());
FieldConstPtr unionVar(FC->createVariantUnion());
FieldConstPtr unionABC(FB->
add("A", ScalarType::pvInt)->
add("B", ScalarType::pvInt)->
add("C", ScalarType::pvInt)->
createUnion());
FieldConstPtr unionABC2(FB->
add("A", ScalarType::pvFloat)->
add("B", ScalarType::pvDouble)->
add("C", ScalarType::pvString)->
createUnion());
FieldConstPtr unionBAC(FB->
add("B", ScalarType::pvInt)->
add("A", ScalarType::pvInt)->
add("C", ScalarType::pvInt)->
createUnion());
FieldConstPtr unionAB(FB->
add("A", ScalarType::pvInt)->
add("B", ScalarType::pvInt)->
createUnion());
FieldConstPtr unionNested1(FB->
add("A", unionABC)->
add("B", unionABC)->
createUnion());
FieldConstPtr unionNested2(FB->
add("A", unionABC)->
add("B", unionBAC)->
createUnion());
testOk(Validator(unionVar).isCompatible(*unionVar),
"isCompatible(VarUnion, VarUnion)");
testOk(!Validator(unionVar).isCompatible(*unionABC),
"!isCompatible(VarUnion, Union{A:int, B:int, C:int})");
testOk(!Validator(unionABC).isCompatible(*unionVar),
"!isCompatible(Union{A:int, B:int, C:int}, VarUnion)");
testOk(Validator(unionABC).isCompatible(*unionABC),
"isCompatible(Union{A:int, B:int, C:int}, Union{A:int, B:int, C:int})");
testOk(Validator(unionABC).isCompatible(*unionABC2),
"isCompatible(Union{A:int, B:int, C:int}, Union{A:float, B:double, C:string})");
testOk(Validator(unionABC2).isCompatible(*unionABC),
"isCompatible(Union{A:float, B:double, C:string}, Union{A:int, B:int, C:int})");
testOk(!Validator(unionABC).isCompatible(*unionBAC),
"!isCompatible(Union{A:int, B:int, C:int}, Union{B:int, A:int, C:int})");
testOk(Validator(unionAB).isCompatible(*unionABC), "Extra Union field");
testOk(!Validator(unionABC).isCompatible(*unionAB), "Missing Union field");
{
Validator::Result result = Validator(unionABC).validate(*unionAB);
testOk(result.errors.size() == 1 &&
result.errors[0] == Validator::Error("C", Validator::ErrorType::MISSING_FIELD),
"Missing Union field Error");
// Both type and ID match for Structure
Result result(FB->setId("TEST_ID")->createStructure());
result.is<Structure>("TEST_ID");
testOk(result.valid(), "Result(Structure['TEST_ID']).is<Structure>('TEST_ID').valid()");
}
testOk(!Validator(unionNested1).isCompatible(*unionNested2), "Nested Unions");
{
// Both type and ID match for Union
UnionConstPtr un(FB->
setId("TEST_ID")->
add("A", ScalarType::pvInt)->
add("B", ScalarType::pvString)->
createUnion()
);
Result result(un);
result.is<Union>("TEST_ID");
testOk(result.valid(), "Result(Union{A:int,B:string}['TEST_ID']).is<Union>('TEST_ID').valid()");
}
{
// Both type and ID match for Variant Union
Result result(FB-> createUnion());
result.is<Union>(Union::ANY_ID);
testOk(result.valid(), "Result(Union).is<Union>('%s').valid()", Union::ANY_ID.c_str());
}
{
// ID matches, type doesn't
Result result(FB->setId("TEST_ID")->createStructure());
result.is<Union>("TEST_ID");
testOk(!result.valid(), "!Result(Union['TEST_ID']).is<Structure>('TEST_ID').valid()");
testOk1(result.errors.at(0) == Result::Error("", Result::Error::IncorrectType));
}
{
// Type matches, ID doesn't
Result result(FB->setId("WRONG_ID")->createStructure());
result.is<Structure>("TEST_ID");
testOk(!result.valid(), "!Result(Structure['WRONG_ID']).is<Structure>('TEST_ID').valid()");
testOk1(result.errors.at(0) == Result::Error("", Result::Error::IncorrectId));
}
{
// Neither type nor ID match (ID is not even checked in this case since it doesn't exist)
Result result(FC->createScalar(ScalarType::pvDouble));
result.is<Structure>("SOME_ID");
testOk(!result.valid(), "!Result(Scalar).is<Structure>('SOME_ID').valid()");
testOk1(result.errors.at(0) == Result::Error("", Result::Error::IncorrectType));
}
}
void test_isCompatible()
void test_has()
{
testDiag("test_isCompatible");
testDiag("test_has");
std::set<Field const *> opt;
StructureConstPtr ref(NTNDArray::createBuilder()->addAlarm()->createStructure());
opt.insert(ref->getField("alarm").get());
FieldBuilderPtr FB(FieldBuilder::begin());
StructureConstPtr struc(NTNDArray::createBuilder()->createStructure());
StructureConstPtr struc(FB->
add("A", ScalarType::pvInt)->
add("B", ScalarType::pvString)->
createStructure()
);
testOk1(Validator(ref, opt).isCompatible(*struc));
std::string strucRepr("Structure{A:int,B:String}");
{
// Test that struc has both A and B, both being Scalars
Result result(struc);
result
.has<Scalar>("A")
.has<Scalar>("B");
testOk(result.valid(),
"Result(%s).has<Scalar>('A').has<Scalar>('B').valid()",
strucRepr.c_str());
}
{
// Test that struc does not have a field B of type ScalarArray
Result result(struc);
result
.has<Scalar>("A")
.has<ScalarArray>("B");
testOk(!result.valid(),
"!Result(%s).has<Scalar>('A').has<ScalarArray>('B').valid()",
strucRepr.c_str());
testOk1(result.errors.at(0) == Result::Error("B", Result::Error::IncorrectType));
}
{
// Test that struc does not have a field C
Result result(struc);
result
.has<Scalar>("A")
.has<Scalar>("C");
testOk(!result.valid(),
"!Result(%s).has<Scalar>('A').has<Scalar>('C').valid()",
strucRepr.c_str());
testOk1(result.errors.at(0) == Result::Error("C", Result::Error::MissingField));
}
{
// Test that 'has' fails for non-structure-like Fields
Result result(FC->createScalar(ScalarType::pvByte));
result.has<Scalar>("X");
testOk(!result.valid(), "!Result(Scalar<pvByte>).has<Scalar>('X').valid()");
testOk1(result.errors.at(0) == Result::Error("", Result::Error::IncorrectType));
}
}
void test_maybe_has()
{
testDiag("test_maybe_has");
FieldBuilderPtr FB(FieldBuilder::begin());
StructureConstPtr struc(FB->
add("A", ScalarType::pvInt)->
add("B", ScalarType::pvString)->
createStructure()
);
std::string strucRepr("Structure{A:int,B:String}");
{
// Test that struc maybe has A and B, both being Scalars
Result result(struc);
result
.maybeHas<Scalar>("A")
.maybeHas<Scalar>("B");
testOk(result.valid(),
"Result(%s).maybeHas<Scalar>('A').maybeHas<Scalar>('B').valid()",
strucRepr.c_str());
}
{
// Test that if struc has a field B, it must be of type ScalarArray
Result result(struc);
result
.maybeHas<Scalar>("A")
.maybeHas<ScalarArray>("B");
testOk(!result.valid(),
"!Result(%s).maybeHas<Scalar>('A').maybeHas<ScalarArray>('B').valid()",
strucRepr.c_str());
testOk1(result.errors.at(0) == Result::Error("B", Result::Error::IncorrectType));
}
{
// Test that struc maybe has A (which it does) and B (which it doesn't)
Result result(struc);
result
.maybeHas<Scalar>("A")
.maybeHas<Scalar>("C");
testOk(result.valid(),
"Result(%s).maybeHas<Scalar>('A').maybeHas<Scalar>('C').valid()",
strucRepr.c_str());
}
{
// Test that 'maybeHas' fails for non-structure-like Fields
Result result(FC->createScalar(ScalarType::pvByte));
result.maybeHas<Scalar>("X");
testOk(!result.valid(), "!Result(Scalar<pvByte>).maybeHas<Scalar>('X').valid()");
testOk1(result.errors.at(0) == Result::Error("", Result::Error::IncorrectType));
}
}
Result& isStructABC(Result& result)
{
return result
.is<Structure>("ABC")
.has<Scalar>("A")
.has<ScalarArray>("B")
.maybeHas<Scalar>("C");
}
void test_has_fn()
{
testDiag("test_has_fn");
FieldBuilderPtr FB(FieldBuilder::begin());
{
StructureConstPtr inner(FB->
setId("ABC")->
add("A", ScalarType::pvInt)->
addArray("B", ScalarType::pvDouble)->
add("C", ScalarType::pvString)->
createStructure()
);
Result result(FB->add("inner", inner)->createStructure());
result.has<&isStructABC>("inner");
testOk(result.valid(), "Result({inner:<valid structABC>}).has<&isStructAbc>('inner').valid()");
}
{
StructureConstPtr inner(FB->
setId("ABC")->
add("A", ScalarType::pvInt)->
addArray("B", ScalarType::pvDouble)->
createStructure()
);
Result result(FB->add("inner", inner)->createStructure());
result.has<&isStructABC>("inner");
testOk(result.valid(), "Result({inner:<valid structABC w/o C>}).has<&isStructAbc>('inner').valid()");
}
{
StructureConstPtr inner(FB->
setId("XYZ")->
add("A", ScalarType::pvInt)->
addArray("B", ScalarType::pvDouble)->
createStructure()
);
Result result(FB->add("inner", inner)->createStructure());
result.has<&isStructABC>("inner");
testOk(!result.valid(), "!Result({inner:<structABC wrong id>}).has<&isStructAbc>('inner').valid()");
testOk1(result.errors.at(0) == Result::Error("inner", Result::Error::IncorrectId));
}
{
StructureConstPtr inner(FB->
setId("XYZ")->
add("A", ScalarType::pvInt)->
add("B", ScalarType::pvDouble)->
createStructure()
);
Result result(FB->add("inner", inner)->createStructure());
result.has<&isStructABC>("inner");
testOk(!result.valid(), "!Result({inner:<structABC wrong id and fields>}).has<&isStructAbc>('inner').valid()");
testOk1(result.errors.size() == 2);
}
}
MAIN(testValidator) {
testPlan(0);
testPlan(56);
FC = epics::pvData::getFieldCreate();
test_result();
test_scalar();
test_union();
test_isCompatible();
test_is();
test_is_id();
test_has();
test_maybe_has();
test_has_fn();
return testDone();
}