cpp-validator

Validation LibraryC++Header-onlyVariable ValidationObject ValidationContainer Validation

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;
}