cpp-validator
Library
cpp-validator
Overview
cpp-validator is a modern header-only validation library for C++14/17. It specializes in validation of variables, objects, and containers, providing a simple and readable API. It supports both pre-validation and post-validation, with automatic error message generation and locale support, making it suitable for internationalized application development. It has been tested with Clang, GCC, and MSVC, and can be easily integrated into modern C++ projects.
Details
cpp-validator is a library for declaratively defining data constraints and applying them as needed. It features a flexible design where validation rules can be defined at specific code points and applied on-demand elsewhere. Error messages are automatically generated considering the user's locale, and it supports complex conditional validations and nested object validation. It provides both exception-based and error report-based error handling patterns, allowing developers to choose according to their preferences.
Key Features
- Header-only: Easy integration and distribution
- Declarative API: Readable and maintainable validation descriptions
- Diverse Validation Targets: Comprehensive validation of variables, objects, and containers
- Automatic Error Messages: Locale-aware automatic message generation
- Flexible Error Handling: Exception-based and report-based approaches
- Modern C++: Design leveraging C++14/17 features
Pros and Cons
Pros
- Header-only library with easy integration
- Simple and intuitive API design
- Automatic error message generation with locale support
- Supports both pre-validation and post-validation
- Handles nested objects and complex conditional validation
- Rich customization options
Cons
- Relatively new library with limited adoption history
- Limited integration examples with large library ecosystems
- Does not provide advanced schema validation features
- Performance optimization unclear for performance-critical applications
- Less documentation compared to other major libraries
References
Code Examples
Installation and Basic Setup
# Install using Conan
conan install cpp-validator/1.0@evgeniums/stable
# Install using vcpkg
vcpkg install cpp-validator
# Manually download header files
wget https://github.com/evgeniums/cpp-validator/archive/refs/heads/master.zip
unzip master.zip
# Place include/ in appropriate directory
Basic Variable and Object Validation
#include <cpp-validator/validator.hpp>
#include <cpp-validator/validate.hpp>
#include <cpp-validator/adapters/reporting.hpp>
#include <iostream>
#include <string>
namespace cv = cpp_validator;
int main() {
// Basic numeric validation
{
// Validator for values greater than 100
auto v = cv::validator(cv::gt, 100);
int value1 = 90;
if (!v.apply(value1)) {
std::cout << "Value " << value1 << " is not greater than 100" << std::endl;
}
int value2 = 150;
if (v.apply(value2)) {
std::cout << "Value " << value2 << " is valid" << std::endl;
}
}
// String validation
{
// Validator for string value and length
auto v = cv::validator(
cv::value(cv::gte, "sample string"), // Lexicographically >= "sample string"
cv::size(cv::lt, 15) // Length < 15 characters
);
std::string str1 = "sample";
if (!v.apply(str1)) {
std::cout << "String \"" << str1 << "\" does not meet conditions" << std::endl;
}
std::string str2 = "sample string+";
if (v.apply(str2)) {
std::cout << "String \"" << str2 << "\" is valid" << std::endl;
}
std::string str3 = "too long sample string";
if (!v.apply(str3)) {
std::cout << "String \"" << str3 << "\" is too long" << std::endl;
}
}
// Range check
{
auto v = cv::validator(cv::in, cv::interval(95, 100));
size_t val = 90;
if (!v.apply(val)) {
std::cout << "Value " << val << " is not in range 95-100" << std::endl;
}
size_t val2 = 97;
if (v.apply(val2)) {
std::cout << "Value " << val2 << " is within valid range" << std::endl;
}
}
return 0;
}
Error Reporting and Message Generation
#include <cpp-validator/validator.hpp>
#include <cpp-validator/validate.hpp>
#include <cpp-validator/adapters/reporting.hpp>
#include <iostream>
#include <string>
namespace cv = cpp_validator;
int main() {
// Validation using error reports
{
auto v = cv::validator(cv::in, cv::interval(95, 100));
cv::error_report err;
size_t val = 90;
cv::validate(val, v, err);
if (err) {
std::cout << "Error: " << err.message() << std::endl;
// Output: "must be in interval [95,100]"
}
}
// Multi-condition validation
{
auto v = cv::validator(
cv::value(cv::ne, "UNKNOWN"), // Not equal to "UNKNOWN"
cv::size(cv::lte, 32) // Length <= 32
);
cv::error_report err;
std::string val = "UNKNOWN";
cv::validate(val, v, err);
if (err) {
std::cout << "String validation error: " << err.message() << std::endl;
}
}
// Detailed numeric validation
{
auto v = cv::validator(
cv::gte(0), // >= 0
cv::lte(1000), // <= 1000
cv::ne(500) // != 500
);
cv::error_report err;
int val = 500;
cv::validate(val, v, err);
if (err) {
std::cout << "Numeric validation error: " << err.message() << std::endl;
}
}
// Custom error message configuration
{
auto v = cv::validator(
cv::gt(0, "Value must be positive"),
cv::lt(100, "Value must be less than 100")
);
cv::error_report err;
int val = -5;
cv::validate(val, v, err);
if (err) {
std::cout << "Custom error: " << err.message() << std::endl;
}
}
return 0;
}
Exception-based Validation and Complex Object Validation
#include <cpp-validator/validator.hpp>
#include <cpp-validator/validate.hpp>
#include <cpp-validator/adapters/reporting.hpp>
#include <iostream>
#include <string>
#include <vector>
#include <map>
namespace cv = cpp_validator;
// Complex data structure example
struct User {
std::string name;
int age;
std::string email;
std::vector<std::string> tags;
std::map<std::string, std::string> metadata;
};
int main() {
// Exception-based validation
{
auto v = cv::validator(cv::gt, 18);
try {
cv::validate(25, v); // Success
std::cout << "Age validation successful" << std::endl;
cv::validate(15, v); // Throws exception
} catch (const cv::validation_error& err) {
std::cout << "Age validation error: " << err.what() << std::endl;
}
}
// Nested object validation
{
// User information validation rules
auto userValidator = cv::validator(
// Name validation
cv::_(cv::key("name"))(
cv::value(cv::ne, ""), // Not empty
cv::size(cv::gte, 2), // >= 2 characters
cv::size(cv::lte, 50) // <= 50 characters
),
// Age validation
cv::_(cv::key("age"))(
cv::gte(0), // >= 0
cv::lte(120) // <= 120
),
// Email validation
cv::_(cv::key("email"))(
cv::size(cv::gte, 5), // >= 5 characters
cv::contains("@") // Contains @
)
);
// Test user data
std::map<std::string, std::string> user_data = {
{"name", "John Doe"},
{"age", "25"},
{"email", "[email protected]"}
};
try {
cv::validate(user_data, userValidator);
std::cout << "User data validation successful" << std::endl;
} catch (const cv::validation_error& err) {
std::cout << "User data validation error: " << err.what() << std::endl;
}
}
// Container validation
{
// Validator for numeric vector
auto vectorValidator = cv::validator(
cv::size(cv::gte, 1), // At least 1 element
cv::size(cv::lte, 10), // At most 10 elements
cv::each(cv::gte(0)) // Each element >= 0
);
std::vector<int> numbers = {1, 2, 3, 4, 5};
try {
cv::validate(numbers, vectorValidator);
std::cout << "Numeric array validation successful" << std::endl;
} catch (const cv::validation_error& err) {
std::cout << "Numeric array validation error: " << err.what() << std::endl;
}
// Test with invalid data
std::vector<int> invalid_numbers = {1, -2, 3};
try {
cv::validate(invalid_numbers, vectorValidator);
} catch (const cv::validation_error& err) {
std::cout << "Invalid numeric array: " << err.what() << std::endl;
}
}
return 0;
}
Advanced Validation Features and Customization
#include <cpp-validator/validator.hpp>
#include <cpp-validator/validate.hpp>
#include <cpp-validator/adapters/reporting.hpp>
#include <iostream>
#include <string>
#include <regex>
#include <functional>
namespace cv = cpp_validator;
// Custom validation functions
bool isValidEmail(const std::string& email) {
static const std::regex email_pattern(
R"(^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$)"
);
return std::regex_match(email, email_pattern);
}
bool isValidPhoneNumber(const std::string& phone) {
static const std::regex phone_pattern(
R"(^[\+]?[1-9][\d]{0,15}$)"
);
return std::regex_match(phone, phone_pattern);
}
// Complex data structure
struct ContactInfo {
std::string name;
std::string email;
std::string phone;
int age;
std::string country;
bool newsletter_subscription;
};
// Custom validator class
class ContactValidator {
private:
std::vector<std::string> valid_countries = {
"Japan", "USA", "UK", "Germany", "France", "Canada"
};
public:
bool validateCountry(const std::string& country) {
return std::find(valid_countries.begin(), valid_countries.end(), country)
!= valid_countries.end();
}
cv::error_report validateContact(const ContactInfo& contact) {
cv::error_report report;
// Name validation
auto nameValidator = cv::validator(
cv::size(cv::gte, 2, "Name must be at least 2 characters"),
cv::size(cv::lte, 50, "Name must be at most 50 characters")
);
cv::validate(contact.name, nameValidator, report);
// Email validation
if (!isValidEmail(contact.email)) {
report.add_error("Invalid email address format");
}
// Phone number validation
if (!contact.phone.empty() && !isValidPhoneNumber(contact.phone)) {
report.add_error("Invalid phone number format");
}
// Age validation
auto ageValidator = cv::validator(
cv::gte(13, "Age must be at least 13"),
cv::lte(120, "Age must be at most 120")
);
cv::validate(contact.age, ageValidator, report);
// Country validation
if (!validateCountry(contact.country)) {
report.add_error("Unsupported country");
}
return report;
}
// Conditional validation
cv::error_report validateContactWithConditions(const ContactInfo& contact) {
cv::error_report report;
// Basic validation
auto basic_report = validateContact(contact);
if (basic_report.has_errors()) {
report.merge(basic_report);
}
// Conditional validation
if (contact.newsletter_subscription) {
// Email required for newsletter subscribers
if (contact.email.empty()) {
report.add_error("Email address required for newsletter subscription");
}
}
// Special restrictions for minors
if (contact.age < 18) {
if (contact.country != "Japan") {
report.add_error("Only Japan residents can register as minors");
}
}
return report;
}
};
// Batch validation
class BatchValidator {
public:
struct ValidationResult {
bool is_valid;
std::vector<std::string> errors;
size_t valid_count;
size_t invalid_count;
ValidationResult() : is_valid(true), valid_count(0), invalid_count(0) {}
};
ValidationResult validateContactList(const std::vector<ContactInfo>& contacts) {
ValidationResult result;
ContactValidator validator;
for (size_t i = 0; i < contacts.size(); ++i) {
const auto& contact = contacts[i];
auto report = validator.validateContactWithConditions(contact);
if (report.has_errors()) {
result.invalid_count++;
result.is_valid = false;
for (const auto& error : report.errors()) {
result.errors.push_back(
"Contact[" + std::to_string(i) + "] " + contact.name +
": " + error
);
}
} else {
result.valid_count++;
}
}
return result;
}
};
int main() {
// Single contact validation
{
ContactInfo contact = {
"John Doe",
"[email protected]",
"+1234567890",
25,
"USA",
true
};
ContactValidator validator;
auto report = validator.validateContactWithConditions(contact);
if (report.has_errors()) {
std::cout << "Contact validation errors:" << std::endl;
for (const auto& error : report.errors()) {
std::cout << " - " << error << std::endl;
}
} else {
std::cout << "Contact validation successful: " << contact.name << std::endl;
}
}
// Batch validation of multiple contacts
{
std::vector<ContactInfo> contacts = {
{"Alice Smith", "[email protected]", "+1987654321", 30, "Canada", false},
{"Bob Johnson", "invalid-email", "+1555123456", 16, "USA", true},
{"Charlie Brown", "[email protected]", "", 25, "Germany", true},
{"", "[email protected]", "+1444555666", 22, "InvalidCountry", false}
};
BatchValidator batchValidator;
auto result = batchValidator.validateContactList(contacts);
std::cout << "\nBatch validation results:" << std::endl;
std::cout << "Valid contacts: " << result.valid_count << std::endl;
std::cout << "Invalid contacts: " << result.invalid_count << std::endl;
if (!result.is_valid) {
std::cout << "Error details:" << std::endl;
for (const auto& error : result.errors) {
std::cout << " - " << error << std::endl;
}
}
}
return 0;
}
Performance and Scalability
#include <cpp-validator/validator.hpp>
#include <cpp-validator/validate.hpp>
#include <iostream>
#include <chrono>
#include <vector>
#include <random>
#include <string>
namespace cv = cpp_validator;
// Test data generation for performance testing
class TestDataGenerator {
private:
std::random_device rd;
std::mt19937 gen{rd()};
std::uniform_int_distribution<> age_dist{18, 80};
std::uniform_int_distribution<> name_len_dist{5, 20};
std::uniform_int_distribution<> char_dist{'a', 'z'};
public:
struct TestRecord {
std::string name;
int age;
double score;
std::vector<int> values;
};
TestRecord generateRecord() {
TestRecord record;
// Generate random name
int name_len = name_len_dist(gen);
record.name.reserve(name_len);
for (int i = 0; i < name_len; ++i) {
record.name += static_cast<char>(char_dist(gen));
}
record.age = age_dist(gen);
record.score = std::uniform_real_distribution<>{0.0, 100.0}(gen);
// Generate random value array
int value_count = std::uniform_int_distribution<>{1, 10}(gen);
record.values.reserve(value_count);
for (int i = 0; i < value_count; ++i) {
record.values.push_back(std::uniform_int_distribution<>{0, 1000}(gen));
}
return record;
}
std::vector<TestRecord> generateRecords(size_t count) {
std::vector<TestRecord> records;
records.reserve(count);
for (size_t i = 0; i < count; ++i) {
records.push_back(generateRecord());
}
return records;
}
};
// High-performance validation class
class FastValidator {
private:
// Pre-compiled validators
static inline const auto name_validator = cv::validator(
cv::size(cv::gte, 2),
cv::size(cv::lte, 50)
);
static inline const auto age_validator = cv::validator(
cv::gte(18),
cv::lte(120)
);
static inline const auto score_validator = cv::validator(
cv::gte(0.0),
cv::lte(100.0)
);
static inline const auto values_validator = cv::validator(
cv::size(cv::gte, 1),
cv::size(cv::lte, 20),
cv::each(cv::gte(0))
);
public:
bool validateRecord(const TestDataGenerator::TestRecord& record) {
return name_validator.apply(record.name) &&
age_validator.apply(record.age) &&
score_validator.apply(record.score) &&
values_validator.apply(record.values);
}
struct ValidationStats {
size_t total_records;
size_t valid_records;
size_t invalid_records;
double validation_time_ms;
double records_per_second;
};
ValidationStats validateRecords(const std::vector<TestDataGenerator::TestRecord>& records) {
ValidationStats stats;
stats.total_records = records.size();
stats.valid_records = 0;
stats.invalid_records = 0;
auto start = std::chrono::high_resolution_clock::now();
for (const auto& record : records) {
if (validateRecord(record)) {
stats.valid_records++;
} else {
stats.invalid_records++;
}
}
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
stats.validation_time_ms = duration.count() / 1000.0;
stats.records_per_second = (stats.total_records * 1000000.0) / duration.count();
return stats;
}
};
int main() {
// Performance test execution
{
TestDataGenerator generator;
FastValidator validator;
// Small-scale test
std::cout << "Small-scale test (1,000 records):" << std::endl;
auto small_records = generator.generateRecords(1000);
auto small_stats = validator.validateRecords(small_records);
std::cout << " Total records: " << small_stats.total_records << std::endl;
std::cout << " Valid records: " << small_stats.valid_records << std::endl;
std::cout << " Invalid records: " << small_stats.invalid_records << std::endl;
std::cout << " Processing time: " << small_stats.validation_time_ms << " ms" << std::endl;
std::cout << " Processing speed: " << small_stats.records_per_second << " records/sec" << std::endl;
// Large-scale test
std::cout << "\nLarge-scale test (100,000 records):" << std::endl;
auto large_records = generator.generateRecords(100000);
auto large_stats = validator.validateRecords(large_records);
std::cout << " Total records: " << large_stats.total_records << std::endl;
std::cout << " Valid records: " << large_stats.valid_records << std::endl;
std::cout << " Invalid records: " << large_stats.invalid_records << std::endl;
std::cout << " Processing time: " << large_stats.validation_time_ms << " ms" << std::endl;
std::cout << " Processing speed: " << large_stats.records_per_second << " records/sec" << std::endl;
// Memory usage optimization example
std::cout << "\nMemory-efficient processing (streaming):" << std::endl;
auto start = std::chrono::high_resolution_clock::now();
size_t batch_size = 1000;
size_t total_processed = 0;
size_t total_valid = 0;
for (size_t i = 0; i < 10; ++i) {
auto batch = generator.generateRecords(batch_size);
auto stats = validator.validateRecords(batch);
total_processed += stats.total_records;
total_valid += stats.valid_records;
// Release memory after batch processing
batch.clear();
batch.shrink_to_fit();
}
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
std::cout << " Total processed records: " << total_processed << std::endl;
std::cout << " Valid records: " << total_valid << std::endl;
std::cout << " Total processing time: " << duration.count() / 1000.0 << " ms" << std::endl;
std::cout << " Average processing speed: " << (total_processed * 1000000.0) / duration.count() << " records/sec" << std::endl;
}
return 0;
}