reinhardt-core 0.1.3

Core components for Reinhardt framework
Documentation

reinhardt-core

Core components for Reinhardt framework

Overview

reinhardt-core provides the fundamental building blocks for the Reinhardt framework. It contains essential types, traits, error handling, signals, security primitives, validators, serializers, parsers, pagination, and content-negotiation utilities that other crates depend on.

This crate serves as the foundation for the entire Reinhardt ecosystem, providing core abstractions and utilities used throughout the framework.

Features

Implemented ✓

This crate provides the following modules:

  • Types: Core type definitions

    • Page types for component rendering (Page, PageElement, Head, etc.)
    • Note: HTTP types (Handler, Middleware, MiddlewareChain) have moved to reinhardt-http
  • Exception: Exception handling and error types

    • Django-style exception hierarchy
    • HTTP status code exceptions (401, 403, 404, 500, etc.)
    • Validation error handling
    • Database exception types
    • Custom error types (ImproperlyConfigured, ParseError, etc.)
  • Signals: Event-driven hooks for lifecycle events

    • Type-safe signal system for decoupled communication
    • Lifecycle signals for models, migrations, requests
    • Async and sync signal dispatch patterns
    • Signal composition and middleware
    • Performance monitoring
  • Macros: Procedural macros for code generation

    • #[api_view] macro for API view definitions
    • HTTP method macros: #[get], #[post], #[put], #[patch], #[delete]
    • #[action] macro for admin action definitions
    • #[permission_required] macro for access control
    • Note: #[injectable] is provided by reinhardt-di
  • Security: Security primitives and utilities

    • CSRF protection
    • XSS prevention
    • Security headers and HSTS helpers
    • IP filtering, redirect validation, and resource limits
  • Validators: Data validation utilities

    • Email validation
    • URL validation
    • Length validators
    • Range validators
    • Custom validator support
  • Serializers: Serialization and deserialization

    • Django REST Framework-inspired field types
    • Validation system with field and object validators
    • Recursive serialization with circular reference detection
    • Arena allocation for high-performance serialization
  • Messages: Flash messages and user notifications

    • Message levels (Debug, Info, Success, Warning, Error)
    • Storage backends (Memory, Session, Cookie, Fallback)
    • Middleware integration
  • Pagination: Pagination strategies

    • PageNumberPagination for page-based pagination
    • LimitOffsetPagination for SQL-style pagination
    • CursorPagination for efficient large dataset pagination
    • Database cursor pagination with O(k) performance
  • Parsers: Request body parsing

    • JSON, XML, YAML, Form, MultiPart parsers
    • File upload handling
    • Content-type negotiation
  • Negotiation: Content negotiation

    • Media type selection based on Accept headers
    • Language negotiation (Accept-Language)
    • Encoding negotiation (Accept-Encoding)
  • Dependency Injection: Provided by the separate reinhardt-di crate

    • See reinhardt-di for automatic dependency resolution, parameter injection, and cache control

Installation

Add this to your Cargo.toml:

[dependencies]
reinhardt-core = "0.1.3"

Optional Features

Enable specific modules based on your needs:

[dependencies]
reinhardt-core = { version = "0.1.3", features = ["signals", "macros", "security"] }

Available features:

  • types (default): Core type definitions
  • exception (default): Error handling
  • signals (default): Event system
  • macros (default): Procedural macros
  • security (default): Security primitives
  • validators (default): Data validation
  • serializers (default): Serialization utilities
  • messages: Flash messaging system
  • negotiation: Content negotiation
  • parsers: Request body parsers
  • pagination: Pagination strategies
  • page: Page types (requires types)
  • reactive: Reactive types

Usage

Handler and Middleware

// Handler and Middleware have been moved to reinhardt-http
use reinhardt_http::{Handler, Middleware, Request, Response};
use reinhardt_core::exception::Result;
use async_trait::async_trait;

// Define a handler
async fn my_handler(req: Request) -> Result<Response> {
    Response::ok().with_body("Hello, world!")
}

// Define middleware
struct LoggingMiddleware;

#[async_trait]
impl Middleware for LoggingMiddleware {
    async fn process_request(&self, req: Request) -> Result<Request> {
        println!("Processing request: {:?}", req.uri());
        Ok(req)
    }
}

Error Handling

use reinhardt::core::exception::{Error, Result};

fn validate_user(authenticated: bool, authorized: bool) -> Result<()> {
    if !authenticated {
        return Err(Error::Authentication("Invalid credentials".into()));
    }
    if !authorized {
        return Err(Error::Authorization("Permission denied".into()));
    }
    Ok(())
}

Signals

use reinhardt::core::signals::{Signal, SignalDispatcher, SignalName};
use std::sync::Arc;

#[derive(Debug, Clone)]
struct User {
    name: String,
}

// Connect a receiver to the signal
async fn setup_signal() {
    let signal = Signal::<User>::new(SignalName::custom("user.created"));

    signal.connect(|user: Arc<User>| async move {
        println!("User created: {}", user.name);
        Ok(())
    });

    // Send signal
    let user = User { name: "Alice".to_string() };
    signal.send(user).await.unwrap();
}

Module Organization

`reinhardt-core` is organized into the following modules:

Core Modules

  • `types` - Core type definitions (page types; Handler/Middleware moved to reinhardt-http)
  • `exception` - Error handling and exception types
  • `signals` - Event-driven hooks for lifecycle events
  • `macros` - Procedural macros for code generation

Utility Modules

  • `security` - CSRF, XSS prevention, security headers, HSTS, IP filtering, redirect validation, and resource limits
  • `validators` - Data validation utilities
  • `serializers` - Serialization and deserialization
  • `messages` - Flash messages and user notifications
  • `pagination` - Pagination strategies
  • `parsers` - Request body parsing
  • `negotiation` - Content negotiation

Using Modules

use reinhardt_http::{Handler, Middleware};
use reinhardt_core::exception::Result;
use reinhardt_core::signals::Signal;

Note: `reinhardt-di` and `reinhardt-http` are separate workspace-level crates that provide dependency injection and HTTP utilities. They can be used independently or alongside `reinhardt-core`.

exception

Features

Implemented ✓

  • Django-style exception hierarchy - Comprehensive Error enum with categorized error types
  • HTTP status code exceptions - Http, Authentication (401), Authorization (403), NotFound (404), Internal (500), etc.
  • Validation error handling - Validation variant with field-level error support
  • Database exception types - Database variant for DB-related errors
  • Custom error types - ImproperlyConfigured, BodyAlreadyConsumed, ParseError, etc.
  • Error serialization - All errors implement Display and can be converted to HTTP responses via status_code() method
  • thiserror integration - Full integration with thiserror for derived error impl
  • anyhow integration - Other variant wraps any anyhow::Error for compatibility
  • Error categorization - ErrorKind enum for categorical classification
  • Standard conversions - From implementations for serde_json::Error, std::io::Error, http::Error, String, &str, reinhardt_core::validators::ValidationErrors
  • Parameter validation context - ParamErrorContext struct with detailed parameter extraction error information
  • Parameter type enumeration - ParamType enum (Json, Query, Path, Form, Header, Cookie, Body)
  • Additional error types - TemplateNotFound (404), MissingContentType (400), MethodNotAllowed (405), Conflict (409)
  • Pagination error types - InvalidPage, InvalidCursor, InvalidLimit variants for pagination validation
  • URL parameter errors - MissingParameter variant for URL reverse operations
  • Helper utilities - extract_field_from_serde_error and extract_field_from_urlencoded_error functions
  • Error kind classification - kind() method returns ErrorKind for categorical error analysis

messages

Features

Implemented ✓

Core Message System

  • Message Levels: 5 predefined levels (Debug, Info, Success, Warning, Error) with numeric priority values (10, 20, 25, 30, 40)
  • Custom Levels: Support for user-defined message levels with custom numeric values
  • Message Tags: Level-based tags and extra custom tags for styling and filtering
  • Message Creation: Convenience methods for creating messages (Message::debug(), Message::info(), etc.)
  • Message Configuration: MessageConfig for customizing level tags globally

Storage Backends

  • MemoryStorage: In-memory storage using thread-safe Arc<Mutex<VecDeque>> for testing and temporary messages
  • SessionStorage: Session-based persistent storage with JSON serialization
    • Customizable session key (default: "_messages")
    • Session availability validation
    • Serialization/deserialization for session integration
  • CookieStorage: Cookie-based storage with automatic size management
    • Configurable cookie name and size limit (default: 4KB)
    • Automatic message truncation using binary search when exceeding size limits
    • Drops oldest messages first when size limit is exceeded
  • FallbackStorage: Intelligent fallback between Cookie and Session storage
    • Attempts cookie storage first for better performance
    • Automatically falls back to session storage when cookie size is exceeded
    • Tracks which storage backend(s) were used
    • Supports flushing messages from both backends

Utilities

  • Binary Search Algorithms: Efficient size-limited message management
    • bisect_keep_left(): Keep maximum messages from the beginning within size limit
    • bisect_keep_right(): Keep maximum messages from the end within size limit
  • SafeData: HTML-safe string wrapper for rendering pre-sanitized HTML content
    • Prevents double-escaping of HTML in messages
    • Serializable with serde support

Storage Trait

  • MessageStorage Trait: Unified interface for all storage backends
    • add(): Add a message to storage
    • get_all(): Retrieve and clear all messages
    • peek(): View messages without clearing
    • clear(): Remove all messages

Middleware Integration

  • MessagesMiddleware: Request/response middleware for automatic message handling
    • Automatic message retrieval and storage during request lifecycle
    • Thread-safe message container with Arc-based sharing
  • MessagesContainer: Container for messages during request processing
    • add(): Add messages during request
    • get_messages(): Retrieve all messages
    • add_from_storage(): Load messages from storage backend

Context Processor

  • MessagesContext: Context for template rendering integration
    • get_messages(): Retrieve messages for rendering
    • add_message(): Add messages to context
  • get_messages_context(): Helper function to create messages context
  • add_message(): Convenience function to add messages to context

Message Filtering

  • filter_by_level(): Filter messages by exact level match
  • filter_by_min_level(): Filter messages above or equal to minimum level
  • filter_by_max_level(): Filter messages below or equal to maximum level
  • filter_by_level_range(): Filter messages within a level range (inclusive)
  • filter_by_tag(): Filter messages by tag match

security

Features

Implemented ✓

CSRF Protection

  • Token Generation & Validation:
    • get_secret_bytes(): Generate cryptographically secure 32-byte secret for HMAC
    • generate_token_hmac(): Generate HMAC-SHA256 token from secret and message
    • get_token_hmac(): High-level token generation using secret and session ID
    • verify_token_hmac(): Constant-time HMAC verification
    • check_token_hmac(): Token validation with detailed error reporting
  • Token Rotation Support:
    • generate_token_with_timestamp(): Generate token with timestamp for rotation tracking
    • verify_token_with_timestamp(): Verify timestamped token and extract timestamp
    • get_token_timestamp(): Get current Unix timestamp for rotation logic
    • should_rotate_token(): Determine if token rotation is due based on interval
    • Configurable via CsrfConfig::with_token_rotation(interval)
  • Origin/Referer Checking: check_origin() and check_referer() validate request sources
  • Domain Validation: is_same_domain() for cross-domain request protection
  • Configurable Cookie Settings: Full control over SameSite, Secure, HttpOnly, Domain, Path, and Max-Age
  • Production-Ready Config: CsrfConfig::production() with security hardening (includes token rotation)
  • Middleware: CsrfMiddleware with customizable configuration
  • Error Handling: Detailed rejection reasons for debugging (bad origin, bad referer, missing token, etc.)
  • Constants: CSRF_TOKEN_LENGTH, CSRF_SECRET_LENGTH, CSRF_SESSION_KEY, rejection reason constants

XSS Prevention

  • HTML Escaping:
    • escape_html(): Escapes dangerous characters (<, >, &, ", ')
    • escape_html_attr(): Escapes HTML attributes including newlines and control characters
  • JavaScript Context Escaping: escape_javascript() for safe embedding in JavaScript strings
  • URL Encoding: escape_url() for URL encoding to prevent injection
  • HTML Sanitization: sanitize_html() for basic HTML input sanitization
  • XSS Pattern Detection: detect_xss_patterns() detects dangerous patterns (script tags, event handlers, etc.)
  • URL Validation: is_safe_url() validates URLs and allows only safe protocols (http, https, mailto, ftp)
  • Safe Output: Prevents script injection in user-generated content across multiple contexts

Security Headers

  • Content Security Policy (CSP): Configurable CSP with granular control over:
    • default-src, script-src, style-src, img-src
    • connect-src, font-src, object-src, media-src, frame-src
    • CSP Reporting: report-uri and report-to for violation reporting via with_report_uri() and with_report_to()
    • Nonce Generation: generate_nonce() for inline script/style nonces
    • Auto Nonce: Automatic nonce injection with with_auto_nonce()
  • Security Headers Middleware: SecurityHeadersMiddleware with comprehensive defaults
  • Configurable Headers:
    • X-Content-Type-Options: nosniff
    • X-Frame-Options: DENY (clickjacking protection)
    • X-XSS-Protection: 1; mode=block
    • Strict-Transport-Security (HSTS)
    • Referrer-Policy: strict-origin-when-cross-origin
    • Permissions-Policy (optional)
    • Cross-Origin Policies:
      • Cross-Origin-Embedder-Policy: require-corp
      • Cross-Origin-Opener-Policy: same-origin
      • Cross-Origin-Resource-Policy: same-origin
  • Environment Presets:
    • SecurityHeadersConfig::production(): Strict security headers for production
    • SecurityHeadersConfig::development(): Relaxed headers for development (no HSTS, no CSP)

HSTS (HTTP Strict Transport Security)

  • HSTS Configuration: HstsConfig with builder pattern
  • Configurable Options:
    • max_age: Configurable duration in seconds
    • includeSubDomains: Optional subdomain protection
    • preload: HSTS preload list support
  • Header Generation: build_header() for automatic header value construction
  • Secure Defaults: 1-year max-age default configuration

Security Utilities

The utils module provides internal security utilities:

  • Secure Token Generation: generate_token() creates cryptographically random tokens (internal use)
  • SHA-256 Hashing: hash_sha256() for secure string hashing (internal use)
  • Random Number Generation: Built on rand crate for security

Note: These utilities are available through the utils module but are not re-exported at the crate root. They are primarily used internally by CSRF and other security features.

Error Handling

  • Comprehensive Error Types: SecurityError enum with specific variants
  • CSRF Validation Errors: Detailed error messages for debugging
  • XSS Detection: Error type for potential XSS attempts
  • Configuration Errors: Validation for security configurations

IP Filtering

  • Whitelist/Blacklist Modes: IpFilterMode enum for configurable filtering strategy
    • Whitelist: Only allow IPs in the allowed list
    • Blacklist: Deny IPs in the blocked list (default)
  • IP Range Support: Add individual IPs or CIDR ranges (e.g., 192.168.1.0/24)
  • IPv4 and IPv6: Full support for both IP versions
  • Flexible Configuration: IpFilterConfig with builder-style methods
    • new(mode): Create with specified mode
    • whitelist(): Create with whitelist mode
    • blacklist(): Create with blacklist mode
    • add_allowed_ip(ip_or_range): Add IP addresses or ranges to whitelist
    • add_blocked_ip(ip_or_range): Add IP addresses or ranges to blacklist
    • is_allowed(&ip): Check if an IP address is permitted
  • Blacklist Override: Blocked IPs take precedence over allowed IPs
  • Middleware: IpFilterMiddleware for request filtering based on IP address

types

Features

Implemented ✓

  • Handler trait - Core abstraction for async request processing
    • async fn handle(&self, request: Request) -> Result<Response>
    • Blanket implementation for Arc<T> to enable Arc<dyn Handler>
  • Middleware trait - Request/response pipeline processing
    • async fn process(&self, request: Request, next: Arc<dyn Handler>) -> Result<Response>
    • fn should_continue(&self, request: &Request) -> bool - Conditional execution
  • MiddlewareChain - Composable middleware system with automatic chaining
    • Builder pattern: with_middleware() for method chaining
    • Mutable API: add_middleware() for imperative style
    • Performance optimizations:
      • O(k) complexity where k ≤ n (skips unnecessary middleware)
      • Short-circuiting with Response::with_stop_chain(true)
  • Type aliases - Request and Response are available in reinhardt-http
  • Async trait support - Full async/await support via async_trait
  • Zero-cost abstractions - All traits compile to efficient code with no runtime overhead

validators

Features

Implemented ✓

Core Validation Framework

  • Validator Trait: Generic validation interface Validator<T> for implementing custom validators
  • OrmValidator Trait: Extension trait for ORM validators with custom error messages
  • SettingsValidator Trait: Extension trait for validating configuration settings
  • ValidationError: Comprehensive error types with descriptive messages
  • ValidationResult: Type-safe result type for validation operations
  • Prelude Module: Convenient re-exports of all validators and error types

String Validators

  • MinLengthValidator: Validates minimum string length
    • Works with both String and &str types
    • Provides detailed error messages with actual and expected lengths
    • Unicode-aware length checking
  • MaxLengthValidator: Validates maximum string length
    • Works with both String and &str types
    • Provides detailed error messages with actual and expected lengths
    • Unicode-aware length checking
  • RegexValidator: Pattern matching with regular expressions
    • Custom error message support via with_message()
    • Full regex syntax support
    • Works with both String and &str types

Numeric Validators

  • MinValueValidator: Validates minimum numeric values
    • Generic over any PartialOrd + Display + Clone type
    • Supports integers (i8, i16, i32, i64, isize, u8, u16, u32, u64, usize)
    • Supports floating-point numbers (f32, f64)
    • Provides detailed error messages with actual and expected values
  • MaxValueValidator: Validates maximum numeric values
    • Generic over any PartialOrd + Display + Clone type
    • Supports all integer and floating-point types
    • Provides detailed error messages with actual and expected values
  • RangeValidator: Validates values within a range (inclusive)
    • Generic over any PartialOrd + Display + Clone type
    • Supports all numeric types
    • Reports whether value is too small or too large

Email Validator

  • EmailValidator: RFC 5322 compliant email validation
    • Case-insensitive validation
    • Local part validation (max 64 characters)
      • Allows alphanumeric characters, dots, underscores, percent signs, plus and minus signs
      • Prevents consecutive dots
      • Prevents leading/trailing dots
    • Domain part validation (max 255 characters)
      • Supports subdomains
      • Each label max 63 characters
      • TLD minimum 2 characters
      • Prevents leading/trailing hyphens in domain labels
    • Total length limit (max 320 characters)
    • Works with both String and &str types

URL Validator

  • UrlValidator: HTTP/HTTPS URL validation
    • Scheme validation (http, https)
    • Port number support (1-5 digits)
    • Path validation
    • Query string support
    • Fragment identifier support
    • Subdomain support
    • Hyphen support in domain names (not at start/end of labels)
    • Works with both String and &str types

Error Types

  • InvalidEmail(String): Invalid email address format
  • InvalidUrl(String): Invalid URL format
  • TooSmall { value: String, min: String }: Value below minimum
  • TooLarge { value: String, max: String }: Value above maximum
  • TooShort { length: usize, min: usize }: String shorter than minimum
  • TooLong { length: usize, max: usize }: String longer than maximum
  • PatternMismatch(String): Regex pattern did not match
  • Custom(String): Custom validation error

Additional Validators (Implemented ✓)

  • SlugValidator: Validate URL-safe slugs
  • UUIDValidator: Validate UUID formats (v1-v5)
  • IPAddressValidator: Validate IPv4/IPv6 addresses
  • DateValidator: Validate date formats
  • TimeValidator: Validate time formats
  • DateTimeValidator: Validate datetime formats
  • JSONValidator: Validate JSON structure
  • ColorValidator: Validate color codes (hex, rgb, rgba, etc.)
  • PhoneNumberValidator: Validate phone numbers (E.164 format)
  • CreditCardValidator: Validate credit card numbers (Luhn algorithm)
  • IBANValidator: Validate international bank account numbers
  • CustomRegexValidator: User-defined regex pattern validation

File Validators (Implemented ✓)

  • FileTypeValidator: Comprehensive file type validation
    • Extension validation: FileTypeValidator::with_extensions()
      • Case-insensitive extension matching
      • Multiple extensions support
      • Whitelist-based filtering
    • MIME type validation: FileTypeValidator::with_mime_types()
      • Validates file MIME types
      • Multiple MIME types support
    • Preset validators:
      • FileTypeValidator::images_only(): Supports JPEG, PNG, GIF, WebP, SVG, BMP, TIFF, ICO, AVIF
      • FileTypeValidator::documents_only(): Supports PDF, DOC, DOCX, XLS, XLSX, PPT, PPTX, TXT

Example:

use reinhardt_validators::{FileTypeValidator, Validator};

// Extension validation
let validator = FileTypeValidator::with_extensions(vec![
    "jpg".to_string(),
    "png".to_string(),
]);
assert!(validator.validate_filename("photo.jpg").is_ok());
assert!(validator.validate_filename("photo.JPG").is_ok()); // Case-insensitive
assert!(validator.validate_filename("document.pdf").is_err());

// Preset validator
let image_validator = FileTypeValidator::images_only();
assert!(image_validator.validate_filename("photo.png").is_ok());

Async Validators (Implemented ✓)

  • ExistsValidator: Asynchronous foreign key existence validation

    • Custom async check function support
    • Database table reference validation
    • Validates that referenced records exist in the database
  • UniqueValidator: Asynchronous uniqueness constraint validation

    • Prevent duplicate entries
    • Instance exclusion during updates with exclude_id parameter
    • Custom async uniqueness check function

Example:

use reinhardt_validators::{ExistsValidator, UniqueValidator, Validator};

// Foreign key existence check
let exists_validator = ExistsValidator::new(
    "user_id",
    "users",
    Box::new(|value| Box::pin(async move {
        // Database check logic here
        // Return true if record exists, false otherwise
        true
    }))
);

// Async validation
let result = exists_validator.validate_async("123").await;
assert!(result.is_ok());

// Uniqueness check with instance exclusion
let unique_validator = UniqueValidator::new(
    "email",
    Box::new(|value, exclude_id| Box::pin(async move {
        // Check if email is unique, excluding the given ID
        true
    }))
);

// Validate new record (no exclusion)
let result = unique_validator.validate_async("new@example.com").await;
assert!(result.is_ok());

Database Identifier Validators (Implemented ✓)

  • TableName: Compile-time and runtime validated table names

    • SQL reserved word checking via is_sql_reserved_word()
    • Snake_case format validation
    • Length validation (max 63 characters for PostgreSQL compatibility)
    • Compile-time validation with new_const() constant function
    • Runtime validation with new() method
  • FieldName: SQL-safe field/column name validation

    • Same validation rules as TableName
    • Prevents SQL injection through identifier validation
  • ConstraintName: SQL-safe constraint name validation

    • Validates constraint identifiers for CREATE/ALTER statements

Example:

use reinhardt_validators::{TableName, FieldName};

// Runtime validation
let table = TableName::new("users")?;
assert!(TableName::new("select").is_err()); // SQL reserved word
assert!(TableName::new("User-Table").is_err()); // Not snake_case

// Compile-time validation
const VALID_TABLE: TableName = TableName::new_const("users");
const VALID_FIELD: FieldName = FieldName::new_const("email_address");

Custom Error Messages (Partial Implementation ✓)

Currently supported by:

  • RegexValidator: .with_message("Custom message")
  • CustomRegexValidator: Built-in custom message support

Planned Extension: Extend to all validators (see lib.rs for planned features)

Example:

use reinhardt_validators::{RegexValidator, Validator};

let validator = RegexValidator::new(r"^\d{3}-\d{4}$")
    .unwrap()
    .with_message("Phone number must be in format XXX-XXXX");

match validator.validate("invalid") {
    Err(e) => assert_eq!(e.to_string(), "Phone number must be in format XXX-XXXX"),
    Ok(_) => panic!("Expected validation error"),
}

File Size Validator (Implemented ✓)

  • FileSizeValidator: Validate file sizes with minimum, maximum, or range constraints
    • Min size validation: FileSizeValidator::min(bytes)
    • Max size validation: FileSizeValidator::max(bytes)
    • Range validation: FileSizeValidator::range(min_bytes, max_bytes)
    • Helper methods for unit conversion:
      • FileSizeValidator::from_kb(kb): Convert KB to bytes
      • FileSizeValidator::from_mb(mb): Convert MB to bytes
      • FileSizeValidator::from_gb(gb): Convert GB to bytes
    • Integrates well with FileTypeValidator for comprehensive file validation
    • Generic over u64 type for file size values

Example:

use reinhardt_validators::{FileSizeValidator, Validator};

// Validate minimum file size
let min_validator = FileSizeValidator::min(FileSizeValidator::from_kb(100)); // 100 KB minimum
assert!(min_validator.validate(&(150 * 1024)).is_ok()); // 150 KB passes
assert!(min_validator.validate(&(50 * 1024)).is_err()); // 50 KB fails

// Validate maximum file size
let max_validator = FileSizeValidator::max(FileSizeValidator::from_mb(5)); // 5 MB maximum
assert!(max_validator.validate(&(3 * 1024 * 1024)).is_ok()); // 3 MB passes
assert!(max_validator.validate(&(10 * 1024 * 1024)).is_err()); // 10 MB fails

// Validate file size range
let range_validator = FileSizeValidator::range(
    FileSizeValidator::from_kb(100),  // 100 KB minimum
    FileSizeValidator::from_mb(10),   // 10 MB maximum
);
assert!(range_validator.validate(&(5 * 1024 * 1024)).is_ok()); // 5 MB passes

Validator Composition (Implemented ✓)

  • AndValidator: Combine multiple validators with AND logic

    • All contained validators must pass for validation to succeed
    • Short-circuits on first failure for better performance
    • Supports nested composition (AND within OR, etc.)
    • Generic over any type T that validators can validate
  • OrValidator: Combine multiple validators with OR logic

    • At least one contained validator must pass
    • Optional error collection from all validators when all fail
    • Supports nested composition (OR within AND, etc.)
    • Generic over any type T that validators can validate

Example:

use reinhardt_validators::{AndValidator, OrValidator, MinLengthValidator, MaxLengthValidator, EmailValidator, UrlValidator, Validator};

// AND composition - Username must be 3-20 characters
let username_validator = AndValidator::new(vec![
    Box::new(MinLengthValidator::new(3)),
    Box::new(MaxLengthValidator::new(20)),
]);
assert!(username_validator.validate("john").is_ok());
assert!(username_validator.validate("jo").is_err()); // Too short
assert!(username_validator.validate("verylongusernamethatexceedslimit").is_err()); // Too long

// OR composition - Contact must be either email OR URL
let contact_validator = OrValidator::new(vec![
    Box::new(EmailValidator::new()),
    Box::new(UrlValidator::new()),
]);
assert!(contact_validator.validate("user@example.com").is_ok()); // Valid email
assert!(contact_validator.validate("http://example.com").is_ok()); // Valid URL
assert!(contact_validator.validate("invalid").is_err()); // Neither email nor URL

// Nested composition - Complex validation logic
let complex_validator = OrValidator::new(vec![
    Box::new(AndValidator::new(vec![
        Box::new(MinLengthValidator::new(3)),
        Box::new(MaxLengthValidator::new(10)),
    ])),
    Box::new(MinLengthValidator::new(20)), // Or very long string
]);
assert!(complex_validator.validate("hello").is_ok()); // Passes first (3-10 chars)
assert!(complex_validator.validate("verylongusernameexceeds20chars").is_ok()); // Passes second (20+ chars)
assert!(complex_validator.validate("hi").is_err()); // Fails both

// Error collection with OrValidator
let collecting_validator = OrValidator::new(vec![
    Box::new(MinLengthValidator::new(10)),
    Box::new(MinLengthValidator::new(20)),
])
.with_error_collection(true);

match collecting_validator.validate("short") {
    Err(e) => println!("All validators failed: {}", e), // Shows all error messages
    Ok(_) => unreachable!(),
}

Postal Code Validator (Implemented ✓)

  • PostalCodeValidator: Country-specific postal code format validation
    • Supported countries: US, UK, JP, CA, DE (5 countries)
    • Country restriction: with_countries(vec![Country::US, Country::JP])
    • Single country: for_country(Country::US)
    • Country detection: validate_with_country() returns detected country
    • Case-insensitive validation: Automatically handles uppercase/lowercase
    • Whitespace trimming: Handles leading/trailing spaces
    • Priority-based pattern matching: Resolves ambiguous formats correctly

Supported Formats:

  • US: ZIP (12345) and ZIP+4 (12345-6789) formats
  • UK: Complex alphanumeric format (SW1A 1AA, M1 1AE, etc.)
  • JP: 7-digit with hyphen (123-4567)
  • CA: Alphanumeric format (K1A 0B1, M5W 1E6)
  • DE: 5-digit format (10115, 80331)

Example:

use reinhardt_validators::{PostalCodeValidator, Country, Validator};

// Validate with country restriction
let validator = PostalCodeValidator::with_countries(vec![
    Country::US,
    Country::JP,
]);
assert!(validator.validate("12345").is_ok()); // US ZIP
assert!(validator.validate("12345-6789").is_ok()); // US ZIP+4
assert!(validator.validate("123-4567").is_ok()); // Japan
assert!(validator.validate("SW1A 1AA").is_err()); // UK not allowed

// Single country validation
let us_validator = PostalCodeValidator::for_country(Country::US);
assert!(us_validator.validate("90210").is_ok());
assert!(us_validator.validate("123-4567").is_err()); // Not US format

// Country detection
let detector = PostalCodeValidator::new(); // Accepts all countries
let country = detector.validate_with_country("12345-6789").unwrap();
assert_eq!(country, Country::US);

let country = detector.validate_with_country("SW1A 1AA").unwrap();
assert_eq!(country, Country::UK);

// Case-insensitive and whitespace handling
assert!(detector.validate("  sw1a 1aa  ").is_ok()); // UK lowercase with spaces
assert!(detector.validate("k1a 0b1").is_ok()); // Canada lowercase

Image Dimension Validator (Implemented ✓)

  • ImageDimensionValidator: Validate image width/height dimensions
    • Min/max width constraints: min_width(), max_width()
    • Min/max height constraints: min_height(), max_height()
    • Aspect ratio validation: aspect_ratio() with configurable tolerance
    • File validation: validate_file() for file paths
    • Bytes validation: validate_bytes() for in-memory images
    • Supported formats: JPEG, PNG, GIF, WebP, BMP, TIFF, ICO, and more via image crate

Example:

use reinhardt::validators::ImageDimensionValidator;

// Basic dimension constraints
let validator = ImageDimensionValidator::new()
	.with_min_width(100)
	.with_max_width(1920)
	.with_min_height(100)
	.with_max_height(1080);

// With aspect ratio validation (16:9 with 1% tolerance)
let hd_validator = ImageDimensionValidator::new()
	.with_min_width(1280)
	.with_min_height(720)
	.with_aspect_ratio(16, 9)
	.with_aspect_ratio_tolerance(0.01);

// Validate from file path
let result = validator.validate_file("image.jpg");

// Validate from bytes (in a function context)
# fn example() -> Result<(), Box<dyn std::error::Error>> {
let image_bytes: Vec<u8> = std::fs::read("image.png")?;
let result = validator.validate_bytes(&image_bytes);
# Ok(())
# }

Conditional Validation (Implemented ✓)

  • ConditionalValidator: Apply validators based on runtime conditions
    • when condition: Apply validator only when condition is true
    • unless condition: Apply validator only when condition is false
    • Closure-based conditions: Use custom logic for condition evaluation
    • Chainable API: Combine with other validators

Example:

use reinhardt::validators::{ConditionalValidator, MinLengthValidator, Validator};

// Apply validation only when condition is true
// Condition receives &T parameter, validator is boxed
let validator = ConditionalValidator::when(
	|value: &str| value.starts_with("admin_"), // Fn(&T) -> bool
	Box::new(MinLengthValidator::new(10)),      // Box<dyn Validator<T>>
);

// Validate admin username (must be at least 10 chars)
assert!(validator.validate("admin_john").is_ok());
assert!(validator.validate("admin_j").is_err()); // Too short

// Regular username (no validation applied)
assert!(validator.validate("john").is_ok());

// Apply validation unless condition is true
let validator = ConditionalValidator::unless(
	|value: &str| value.is_empty(),
	Box::new(MinLengthValidator::new(5)),
);

License

Licensed under the BSD 3-Clause License.