mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-24 22:27:55 -05:00
Add support for custom validation callbacks on fields
* Add support for custom validation callbacks on fields Introduce setCustomValidationCallback to PdmFieldHandle, allowing custom validation logic via user-provided lambdas or functions. The validate() method now invokes this callback if set, in addition to built-in checks. Includes comprehensive unit tests for custom and combined validation scenarios. Backward compatible: default behavior unchanged if no callback is set.
This commit is contained in:
1 parent
93bd0b9c9d
commit
cb8a069ccd
6 files changed
+141
-16
No files matched your search
+85
@@ -12,6 +12,7 @@
|
||||
#include "cafPdmReferenceHelper.h"
|
||||
#include "cafPdmValueField.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
class DemoPdmObject : public caf::PdmObjectHandle
|
||||
@@ -1008,3 +1009,87 @@ TEST( BaseTest, ObjectValidation )
|
||||
|
||||
delete customObj;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Test of custom validation callback
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( BaseTest, CustomValidationCallback )
|
||||
{
|
||||
class TestObject : public caf::PdmObjectHandle
|
||||
{
|
||||
public:
|
||||
TestObject()
|
||||
{
|
||||
this->addField( &m_value, "value" );
|
||||
this->addField( &m_rangedValue, "rangedValue" );
|
||||
}
|
||||
|
||||
caf::PdmDataValueField<int> m_value;
|
||||
caf::PdmDataValueField<int> m_rangedValue;
|
||||
};
|
||||
|
||||
auto obj = std::make_unique<TestObject>();
|
||||
|
||||
// A field with no callback is valid by default
|
||||
obj->m_value.setValue( 42 );
|
||||
EXPECT_TRUE( obj->m_value.isValid() );
|
||||
EXPECT_TRUE( obj->m_value.validate().isEmpty() );
|
||||
|
||||
// Registering a callback that returns an error string makes the field invalid
|
||||
obj->m_value.setCustomValidationCallback( []() -> QString { return "Custom error"; } );
|
||||
EXPECT_FALSE( obj->m_value.isValid() );
|
||||
EXPECT_EQ( QString( "Custom error" ), obj->m_value.validate() );
|
||||
|
||||
// Registering a callback that returns empty string keeps the field valid
|
||||
obj->m_value.setCustomValidationCallback( []() -> QString { return QString(); } );
|
||||
EXPECT_TRUE( obj->m_value.isValid() );
|
||||
EXPECT_TRUE( obj->m_value.validate().isEmpty() );
|
||||
|
||||
// The callback can access the field value for conditional validation
|
||||
obj->m_value.setCustomValidationCallback(
|
||||
[&obj]() -> QString
|
||||
{
|
||||
if ( obj->m_value.value() % 2 != 0 )
|
||||
{
|
||||
return "Value must be even";
|
||||
}
|
||||
return QString();
|
||||
} );
|
||||
|
||||
obj->m_value.setValue( 4 );
|
||||
EXPECT_TRUE( obj->m_value.isValid() );
|
||||
|
||||
obj->m_value.setValue( 7 );
|
||||
EXPECT_FALSE( obj->m_value.isValid() );
|
||||
EXPECT_EQ( QString( "Value must be even" ), obj->m_value.validate() );
|
||||
|
||||
// Custom callback works alongside range validation (both must pass)
|
||||
obj->m_rangedValue.setRange( 0, 100 );
|
||||
obj->m_rangedValue.setCustomValidationCallback(
|
||||
[&obj]() -> QString
|
||||
{
|
||||
if ( obj->m_rangedValue.value() % 2 != 0 )
|
||||
{
|
||||
return "Value must be even";
|
||||
}
|
||||
return QString();
|
||||
} );
|
||||
|
||||
// Both range and custom pass
|
||||
obj->m_rangedValue.setValue( 50 );
|
||||
EXPECT_TRUE( obj->m_rangedValue.isValid() );
|
||||
|
||||
// Range fails (custom would pass)
|
||||
obj->m_rangedValue.setValue( 150 );
|
||||
EXPECT_FALSE( obj->m_rangedValue.isValid() );
|
||||
EXPECT_TRUE( obj->m_rangedValue.validate().contains( "exceeds maximum" ) );
|
||||
|
||||
// Custom fails (range would pass)
|
||||
obj->m_rangedValue.setValue( 51 );
|
||||
EXPECT_FALSE( obj->m_rangedValue.isValid() );
|
||||
EXPECT_EQ( QString( "Value must be even" ), obj->m_rangedValue.validate() );
|
||||
|
||||
// Both pass
|
||||
obj->m_rangedValue.setValue( 50 );
|
||||
EXPECT_TRUE( obj->m_rangedValue.isValid() );
|
||||
}
|
||||
@@ -259,7 +259,7 @@ QString caf::PdmDataValueField<DataType>::validate() const
|
||||
return QString( "Value %1 exceeds maximum %2" ).arg( m_fieldValue ).arg( m_maxValue.value() );
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
return executeCustomValidation();
|
||||
}
|
||||
|
||||
} // End of namespace caf
|
||||
@@ -135,6 +135,26 @@ std::vector<QString> PdmFieldHandle::keywordAliases() const
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
QString PdmFieldHandle::validate() const
|
||||
{
|
||||
return executeCustomValidation();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void PdmFieldHandle::setCustomValidationCallback( std::function<QString()> callback )
|
||||
{
|
||||
m_customValidationCallback = std::move( callback );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
QString PdmFieldHandle::executeCustomValidation() const
|
||||
{
|
||||
if ( m_customValidationCallback )
|
||||
{
|
||||
return m_customValidationCallback();
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "cafPdmBase.h"
|
||||
#include <QString>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace caf
|
||||
@@ -61,9 +62,11 @@ public:
|
||||
// Validation
|
||||
virtual QString validate() const;
|
||||
bool isValid() const;
|
||||
void setCustomValidationCallback( std::function<QString()> callback );
|
||||
|
||||
protected:
|
||||
bool isInitializedByInitFieldMacro() const { return m_ownerObject != nullptr; }
|
||||
QString executeCustomValidation() const;
|
||||
bool isInitializedByInitFieldMacro() const { return m_ownerObject != nullptr; }
|
||||
|
||||
private:
|
||||
PDM_DISABLE_COPY_AND_ASSIGN( PdmFieldHandle );
|
||||
@@ -77,6 +80,8 @@ private:
|
||||
std::vector<QString> m_keywordAliases;
|
||||
|
||||
std::vector<std::pair<PdmFieldCapability*, bool>> m_capabilities;
|
||||
|
||||
std::function<QString()> m_customValidationCallback;
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -26,18 +26,36 @@ ValidationTestObject::ValidationTestObject()
|
||||
m_percentage.uiCapability()->setUiToolTip( "Valid range: 0 to 100%" );
|
||||
m_percentage.setRange( 0.0, 100.0 );
|
||||
|
||||
// Count field: minimum value only (>= 0)
|
||||
// Count field: minimum value only (>= 0) and must be even (custom callback)
|
||||
CAF_PDM_InitField( &m_count, "count", 0, "Count", "", "", "" );
|
||||
m_count.uiCapability()->setUiToolTip( "Must be non-negative" );
|
||||
m_count.uiCapability()->setUiToolTip( "Must be non-negative and even" );
|
||||
m_count.setMinValue( 0 );
|
||||
m_count.setCustomValidationCallback(
|
||||
[this]() -> QString
|
||||
{
|
||||
if ( m_count() % 2 != 0 )
|
||||
{
|
||||
return "Count must be an even number";
|
||||
}
|
||||
return {};
|
||||
} );
|
||||
|
||||
// Name field: no validation
|
||||
CAF_PDM_InitField( &m_name, "name", QString( "John Doe" ), "Name", "", "", "" );
|
||||
m_name.uiCapability()->setUiToolTip( "Free text field" );
|
||||
|
||||
// Email field: no validation (but could add custom validation in validate())
|
||||
// Email field: custom validation callback
|
||||
CAF_PDM_InitField( &m_email, "email", QString( "john@example.com" ), "Email", "", "", "" );
|
||||
m_email.uiCapability()->setUiToolTip( "Email address" );
|
||||
m_email.uiCapability()->setUiToolTip( "Email address (must contain @)" );
|
||||
m_email.setCustomValidationCallback(
|
||||
[this]() -> QString
|
||||
{
|
||||
if ( !m_email().isEmpty() && !m_email().contains( "@" ) )
|
||||
{
|
||||
return "Email must contain @ symbol";
|
||||
}
|
||||
return {};
|
||||
} );
|
||||
|
||||
// Range field: -100.0 to 100.0
|
||||
CAF_PDM_InitField( &m_rangeField, "rangeField", 0.0, "Range Field", "", "", "" );
|
||||
@@ -54,15 +72,7 @@ std::map<QString, QString> ValidationTestObject::validate( const QString& config
|
||||
auto errors = PdmObject::validate( configName );
|
||||
|
||||
// Add custom object-level validation
|
||||
|
||||
// Email validation (basic)
|
||||
if ( !m_email().isEmpty() )
|
||||
{
|
||||
if ( !m_email().contains( "@" ) )
|
||||
{
|
||||
errors["email"] = "Email must contain @ symbol";
|
||||
}
|
||||
}
|
||||
// Note: email validation is handled by a custom validation callback on the field itself
|
||||
|
||||
// Name validation
|
||||
if ( m_name().isEmpty() )
|
||||
@@ -135,6 +145,7 @@ void ValidationTestObject::performValidation()
|
||||
qDebug() << "Age valid:" << m_age.isValid();
|
||||
qDebug() << "Percentage valid:" << m_percentage.isValid();
|
||||
qDebug() << "Count valid:" << m_count.isValid();
|
||||
qDebug() << "Email valid:" << m_email.isValid();
|
||||
|
||||
// Show validation messages
|
||||
if ( !m_temperature.isValid() )
|
||||
@@ -153,4 +164,8 @@ void ValidationTestObject::performValidation()
|
||||
{
|
||||
qDebug() << " Count error:" << m_count.validate();
|
||||
}
|
||||
if ( !m_email.isValid() )
|
||||
{
|
||||
qDebug() << " Email error:" << m_email.validate();
|
||||
}
|
||||
}
|
||||
Vendored
+1
-1
Submodule ThirdParty/vcpkg updated: 04bec276db...61e1f600c1.
Reference in new issue
Block a user