From f22b5d5b7b43389219542f39eec46325915266d5 Mon Sep 17 00:00:00 2001 From: Bruno Martins Date: Thu, 23 May 2019 12:30:50 -0400 Subject: [PATCH] Completely rework Validator API --- src/Makefile | 1 - src/ntfield.cpp | 246 ++++++++------------------ src/ntndarray.cpp | 91 ++++++---- src/pv/ntfield.h | 12 ++ src/validator.cpp | 272 ----------------------------- src/validator.h | 171 +++++++++++++----- test/validatorTest.cpp | 385 +++++++++++++++++++++++++++++------------ 7 files changed, 547 insertions(+), 631 deletions(-) delete mode 100644 src/validator.cpp diff --git a/src/Makefile b/src/Makefile index 3334d51..bd83c05 100644 --- a/src/Makefile +++ b/src/Makefile @@ -41,7 +41,6 @@ LIBSRCS += ntcontinuum.cpp LIBSRCS += nthistogram.cpp LIBSRCS += nturi.cpp LIBSRCS += ntndarrayAttribute.cpp -LIBSRCS += validator.cpp LIBRARY = nt diff --git a/src/ntfield.cpp b/src/ntfield.cpp index 7ec9972..97a7067 100644 --- a/src/ntfield.cpp +++ b/src/ntfield.cpp @@ -5,6 +5,7 @@ */ #include +#include "validator.h" #define epicsExportSharedSymbols #include @@ -31,198 +32,103 @@ NTField::NTField() { } +Result& NTField::isEnumerated(Result& result) +{ + return result + .is() + .has("index") + .has("choices"); +} + bool NTField::isEnumerated(FieldConstPtr const & field) { - if(field->getType()!=structure) return false; - StructureConstPtr structurePtr = static_pointer_cast(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(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(f); - if(sa->getScalarType()!=pvString) return false; - return true; + Result result(field); + return isEnumerated(result).valid(); +} + +Result& NTField::isTimeStamp(Result& result) +{ + return result + .is() + .has("secondsPastEpoch") + .has("nanoseconds") + .has("userTag"); } bool NTField::isTimeStamp(FieldConstPtr const & field) { - if(field->getType()!=structure) return false; - StructureConstPtr structurePtr = static_pointer_cast(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(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(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(f); - if(s->getScalarType()!=pvInt) return false; - return true; + Result result(field); + return isTimeStamp(result).valid(); +} + +Result& NTField::isAlarm(Result& result) +{ + return result + .is() + .has("severity") + .has("status") + .has("message"); } bool NTField::isAlarm(FieldConstPtr const & field) { - if(field->getType()!=structure) return false; - StructureConstPtr structurePtr = static_pointer_cast(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(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(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(f); - if(s->getScalarType()!=pvString) return false; - return true; + Result result(field); + return isAlarm(result).valid(); +} + +Result& NTField::isDisplay(Result& result) +{ + return result + .is() + .has("limitLow") + .has("limitHigh") + .has("description") + .has("format") + .has("units"); + } bool NTField::isDisplay(FieldConstPtr const & field) { - if(field->getType()!=structure) return false; - StructureConstPtr structurePtr = static_pointer_cast(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(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(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(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(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(f); - if(s->getScalarType()!=pvString) return false; - return true; + Result result(field); + return isDisplay(result).valid(); +} + +Result& NTField::isAlarmLimit(Result& result) +{ + return result + .is() + .has("active") + .has("lowAlarmLimit") + .has("lowWarningLimit") + .has("highWarningLimit") + .has("highAlarmLimit") + .has("lowAlarmSeverity") + .has("lowWarningSeverity") + .has("highWarningSeverity") + .has("highAlarmSeverity") + .has("hysteresis"); } bool NTField::isAlarmLimit(FieldConstPtr const & field) { - if(field->getType()!=structure) return false; - StructureConstPtr structurePtr = static_pointer_cast(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(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(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(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(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(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(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(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(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(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() + .has("limitLow") + .has("limitHigh") + .has("minStep"); } bool NTField::isControl(FieldConstPtr const & field) { - if(field->getType()!=structure) return false; - StructureConstPtr structurePtr = static_pointer_cast(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(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(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(f); - if(s->getScalarType()!=pvDouble) return false; - return true; + Result result(field); + return isControl(result).valid(); } StructureConstPtr NTField::createEnumerated() diff --git a/src/ntndarray.cpp b/src/ntndarray.cpp index 6f1917e..a94e6f0 100644 --- a/src/ntndarray.cpp +++ b/src/ntndarray.cpp @@ -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::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(i); + string name(ScalarTypeFunc::name(type)); + result.has(name + "Value"); + } - std::set 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("codec_t") + .has("name") + .has("parameters"); + } - // TODO: the following should come from a helper in NTNDArrayAttribute - StructureConstPtr attribute( - std::static_pointer_cast( - structure->getField("attribute") - )->getStructure() - ); + Result& isDimension(Result& result) + { + return result + .is("dimension_t[]") + .has("size") + .has("offset") + .has("fullSize") + .has("binning") + .has("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() + .has("name") + .has("value") + .maybeHas("tags") + .has("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() + .has<&isValue>("value") + .has<&isCodec>("codec") + .has("compressedSize") + .has("uncompressedSize") + .has<&isDimension>("dimension") + .has("uniqueId") + .has<&NTField::isTimeStamp>("dataTimeStamp") + .has<&isAttribute>("attribute") + .maybeHas("descriptor") + .maybeHas<&NTField::isAlarm>("alarm") + .maybeHas<&NTField::isTimeStamp>("timeStamp") + .maybeHas<&NTField::isDisplay>("display") + .valid(); } diff --git a/src/pv/ntfield.h b/src/pv/ntfield.h index da13d35..f1f6456 100644 --- a/src/pv/ntfield.h +++ b/src/pv/ntfield.h @@ -30,6 +30,8 @@ namespace epics { namespace nt { +class Result; + class NTField; typedef std::tr1::shared_ptr 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; }; /** diff --git a/src/validator.cpp b/src/validator.cpp deleted file mode 100644 index 9bcb7a0..0000000 --- a/src/validator.cpp +++ /dev/null @@ -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 -#include -#include - -#include "validator.h" - -#define epicsExportSharedSymbols -#include - -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 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::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(reference), - static_cast(field)); - - case Type::structureArray: - return validate( - *static_cast(reference).getStructure(), - *static_cast(field).getStructure()); - - case Type::union_: - return validate( - static_cast(reference), - static_cast(field)); - - case Type::unionArray: - return validate( - *static_cast(reference).getUnion(), - *static_cast(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 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); -} - -}} diff --git a/src/validator.h b/src/validator.h index d0148c2..ba523ec 100644 --- a/src/validator.h +++ b/src/validator.h @@ -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() ? "" : 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 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 errors; - Validator(epics::pvData::FieldConstPtr const & reference); + enum result_t { + Pass, + Fail, + } result; - Validator(epics::pvData::FieldConstPtr const & reference, - std::set 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 + Result& is(void) { + if (!dynamic_cast(f.get())) { + result = Fail; + errors.push_back(Error(path, Error::IncorrectType)); + } + return *this; + } + + template + Result& is(const std::string& id) { + T const *s = dynamic_cast(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& has(const std::string& name) { + return has(name, false, fn); + } + + template + Result& maybeHas(const std::string& name) { + return has(name, true, fn); + } + + template + Result& has(const std::string& name) { + return has(name, false, NULL); + } + + template + Result& maybeHas(const std::string& name) { + return has(name, true, NULL); + } + + std::ostream& dump(std::ostream& os) const { + os << "Result(valid=" << valid() << ", errors=[ "; + + std::vector::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 optional; + template + 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(f.get())->getField(name); + break; + case epics::pvData::Type::structureArray: + field = static_cast(f.get())->getStructure()->getField(name); + break; + case epics::pvData::Type::union_: + field = static_cast(f.get())->getField(name); + break; + case epics::pvData::Type::unionArray: + field = static_cast(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(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 \ No newline at end of file diff --git a/test/validatorTest.cpp b/test/validatorTest.cpp index 3d31f67..287b35d 100644 --- a/test/validatorTest.cpp +++ b/test/validatorTest.cpp @@ -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 must be valid for Scalars of any type + for(int i = ScalarType::pvBoolean; i <= ScalarType::pvString; ++i) { + ScalarType t = static_cast(i); + testOk(Result(FC->createScalar(t)).is().valid(), + "Result(Scalar<%s>).is().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, Scalar)"); + // Result::is must be valid for ScalarArray of any type + for(int i = ScalarType::pvBoolean; i <= ScalarType::pvString; ++i) { + ScalarType t = static_cast(i); + testOk(Result(FC->createScalarArray(t)).is().valid(), + "Result(ScalarArray<%s>).is().valid()", name(t)); } { - FieldConstPtr def(FC->createScalar(ScalarType::pvBoolean)); - FieldConstPtr field(FC->createScalar(ScalarType::pvInt)); - testOk(Validator(def).isCompatible(*field), "isCompatible(Scalar, Scalar)"); + // Result::is must be invalid for non-Scalar + Result result(FC->createScalarArray(ScalarType::pvInt)); + result.is(); + testOk(!result.valid(), "!Result(ScalarArray).is.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, Scalar)"); - } - - { - FieldConstPtr def(FC->createScalarArray(ScalarType::pvString)); - FieldConstPtr field(FC->createScalarArray(ScalarType::pvFloat)); - testOk(Validator(def).isCompatible(*field), "isCompatible(ScalarArray, ScalarArray)"); - } - - { - FieldConstPtr def(FC->createScalarArray(ScalarType::pvByte)); - FieldConstPtr field(FC->createScalar(ScalarType::pvByte)); - testOk(!Validator(def).isCompatible(*field), "!isCompatible(ScalarArray, Scalar)"); - } - - { - FieldConstPtr def(FC->createScalar(ScalarType::pvByte)); - FieldConstPtr field(FC->createScalarArray(ScalarType::pvByte)); - testOk(!Validator(def).isCompatible(*field), "!isCompatible(Scalar, ScalarArray)"); + // Result::is must be invalid for non-ScalarArray + Result result(FC->createScalar(ScalarType::pvInt)); + result.is(); + testOk(!result.valid(), "!Result(ScalarArray).is.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("TEST_ID"); + testOk(result.valid(), "Result(Structure['TEST_ID']).is('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("TEST_ID"); + testOk(result.valid(), "Result(Union{A:int,B:string}['TEST_ID']).is('TEST_ID').valid()"); + } + + { + // Both type and ID match for Variant Union + Result result(FB-> createUnion()); + result.is(Union::ANY_ID); + testOk(result.valid(), "Result(Union).is('%s').valid()", Union::ANY_ID.c_str()); + } + + { + // ID matches, type doesn't + Result result(FB->setId("TEST_ID")->createStructure()); + result.is("TEST_ID"); + testOk(!result.valid(), "!Result(Union['TEST_ID']).is('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("TEST_ID"); + testOk(!result.valid(), "!Result(Structure['WRONG_ID']).is('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("SOME_ID"); + testOk(!result.valid(), "!Result(Scalar).is('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 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("A") + .has("B"); + + testOk(result.valid(), + "Result(%s).has('A').has('B').valid()", + strucRepr.c_str()); + } + + { + // Test that struc does not have a field B of type ScalarArray + Result result(struc); + result + .has("A") + .has("B"); + testOk(!result.valid(), + "!Result(%s).has('A').has('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("A") + .has("C"); + testOk(!result.valid(), + "!Result(%s).has('A').has('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("X"); + testOk(!result.valid(), "!Result(Scalar).has('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("A") + .maybeHas("B"); + + testOk(result.valid(), + "Result(%s).maybeHas('A').maybeHas('B').valid()", + strucRepr.c_str()); + } + + { + // Test that if struc has a field B, it must be of type ScalarArray + Result result(struc); + result + .maybeHas("A") + .maybeHas("B"); + testOk(!result.valid(), + "!Result(%s).maybeHas('A').maybeHas('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("A") + .maybeHas("C"); + testOk(result.valid(), + "Result(%s).maybeHas('A').maybeHas('C').valid()", + strucRepr.c_str()); + } + + { + // Test that 'maybeHas' fails for non-structure-like Fields + Result result(FC->createScalar(ScalarType::pvByte)); + result.maybeHas("X"); + testOk(!result.valid(), "!Result(Scalar).maybeHas('X').valid()"); + testOk1(result.errors.at(0) == Result::Error("", Result::Error::IncorrectType)); + } +} + +Result& isStructABC(Result& result) +{ + return result + .is("ABC") + .has("A") + .has("B") + .maybeHas("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:}).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:}).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:}).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:}).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(); } - -