Basic Examples

This section provides practical examples of common validation scenarios using Valid8r. Each example demonstrates a specific validation use case with complete code samples.

Basic Parsing

Converting strings to various data types is a common operation that Valid8r simplifies with its parsing functions. All parsing functions return a Maybe object that can be easily examined with pattern matching:

from valid8r import parsers
from valid8r.core.maybe import Success, Failure

# Parse an integer
result = parsers.parse_int("42")
match result:
    case Success(value):
        print(f"Parsed integer: {value}")  # Parsed integer: 42
    case Failure(error):
        print(f"Error: {error}")

# Parse a float
result = parsers.parse_float("3.14159")
match result:
    case Success(value):
        print(f"Parsed float: {value}")  # Parsed float: 3.14159
    case Failure(error):
        print(f"Error: {error}")

# Parse a boolean
result = parsers.parse_bool("yes")
match result:
    case Success(value):
        print(f"Parsed boolean: {value}")  # Parsed boolean: True
    case Failure(error):
        print(f"Error: {error}")

# Parse a date
result = parsers.parse_date("2023-04-15")
match result:
    case Success(value):
        print(f"Parsed date: {value}")  # Parsed date: 2023-04-15
    case Failure(error):
        print(f"Error: {error}")

# Parse a complex number
result = parsers.parse_complex("3+4j")
match result:
    case Success(value):
        print(f"Parsed complex: {value}")  # Parsed complex: (3+4j)
    case Failure(error):
        print(f"Error: {error}")

Basic Validation

Validating values against specific criteria is easy with Valid8r validators:

from valid8r import validators
from valid8r.core.maybe import Success, Failure

# Validate a positive number
result = validators.minimum(0)(42)
match result:
    case Success(value):
        print(f"Valid positive number: {value}")  # Valid positive number: 42
    case Failure(error):
        print(f"Error: {error}")

# Validate a number in range
result = validators.between(1, 100)(42)
match result:
    case Success(value):
        print(f"Valid number in range: {value}")  # Valid number in range: 42
    case Failure(error):
        print(f"Error: {error}")

# Validate string length
result = validators.length(3, 20)("hello")
match result:
    case Success(value):
        print(f"Valid string: {value}")  # Valid string: hello
    case Failure(error):
        print(f"Error: {error}")

# Validate with a custom predicate
is_even = validators.predicate(lambda x: x % 2 == 0, "Number must be even")
result = is_even(42)
match result:
    case Success(value):
        print(f"Valid even number: {value}")  # Valid even number: 42
    case Failure(error):
        print(f"Error: {error}")

Combining Parsing and Validation

Valid8r’s strength lies in chaining parsing and validation for complete input processing:

from valid8r import parsers, validators
from valid8r.core.maybe import Success, Failure

# Parse and validate a positive integer
input_str = "42"
result = parsers.parse_int(input_str).bind(
    lambda x: validators.minimum(0)(x)
)

match result:
    case Success(value):
        print(f"Valid positive integer: {value}")  # Valid positive integer: 42
    case Failure(error):
        print(f"Error: {error}")

# Parse and validate a date in the future
from datetime import date

today = date.today()
is_future = validators.predicate(
    lambda d: d > today,
    "Date must be in the future"
)

input_str = "2030-01-01"
result = parsers.parse_date(input_str).bind(is_future)

match result:
    case Success(value):
        print(f"Valid future date: {value}")  # Valid future date: 2030-01-01
    case Failure(error):
        print(f"Error: {error}")

User Input with Validation

Valid8r makes it simple to prompt for input with validation:

from valid8r import prompt, parsers, validators
from valid8r.core.maybe import Success, Failure

# Ask for a name (non-empty string)
name = prompt.ask(
    "Enter your name: ",
    validator=validators.length(1, 50),
    retry=True
)

match name:
    case Success(value):
        print(f"Name: {value}")
    case Failure(error):
        print(f"Error: {error}")

# Ask for an age (positive integer)
age = prompt.ask(
    "Enter your age: ",
    parser=parsers.parse_int,
    validator=validators.between(0, 120),
    retry=True
)

match age:
    case Success(value):
        print(f"Age: {value}")
    case Failure(error):
        print(f"Error: {error}")

# Ask for a score with a default value
score = prompt.ask(
    "Enter score (0-100): ",
    parser=parsers.parse_int,
    validator=validators.between(0, 100),
    default=50,
    retry=True
)

match score:
    case Success(value):
        print(f"Score: {value}")
    case Failure(error):
        print(f"Error: {error}")

# Ask for a yes/no answer
confirm = prompt.ask(
    "Proceed? (yes/no): ",
    parser=parsers.parse_bool,
    retry=True
)

match confirm:
    case Success(value) if value:
        print("Proceeding...")
    case Success(_):
        print("Operation cancelled")
    case Failure(error):
        print(f"Error: {error}")

Form Validation

Valid8r excels at validating form-like data structures:

from valid8r import Maybe, validators
from valid8r.core.maybe import Success, Failure
import re

# Define validators
validators_map = {
    "username": validators.length(3, 20) & validators.predicate(
        lambda s: s.isalnum(),
        "Username must be alphanumeric"
    ),
    "email": validators.predicate(
        lambda s: bool(re.match(r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$", s)),
        "Invalid email format"
    ),
    "age": validators.between(18, 120),
}

# Validate form data
def validate_form(form_data):
    results = {}
    errors = {}

    for field, validator in validators_map.items():
        if field in form_data:
            result = validator(form_data[field])
            match result:
                case Success(value):
                    results[field] = value
                case Failure(error):
                    errors[field] = error
        else:
            errors[field] = f"Missing required field: {field}"

    if errors:
        return (False, errors)
    return (True, results)

# Process validation results
def process_form(form_data):
    is_valid, data = validate_form(form_data)

    if is_valid:
        print("Form is valid!")
        print(f"Username: {data['username']}")
        print(f"Email: {data['email']}")
        print(f"Age: {data['age']}")
        return True
    else:
        print("Form has errors:")
        for field, error in data.items():
            print(f"  - {field}: {error}")
        return False

# Test with valid data
valid_form = {
    "username": "john_doe",
    "email": "john@example.com",
    "age": 30
}

process_form(valid_form)

# Test with invalid data
invalid_form = {
    "username": "john_doe@",  # Contains invalid character
    "email": "not-an-email",
    "age": 15  # Below minimum
}

process_form(invalid_form)

Configuration Validation

Validating configuration settings is another common use case for Valid8r:

from valid8r import validators
from valid8r.core.maybe import Success, Failure

# Define validators for configuration
config_validators = {
    "port": validators.between(1024, 65535),
    "host": validators.predicate(
        lambda s: s == "localhost" or all(part.isdigit() and 0 <= int(part) <= 255
                                       for part in s.split(".")),
        "Host must be 'localhost' or a valid IP address"
    ),
    "debug": validators.predicate(
        lambda b: isinstance(b, bool),
        "Debug must be a boolean"
    ),
    "timeout": validators.minimum(0),
    "max_connections": validators.between(1, 1000),
}

# Validate config
def validate_config(config):
    results = {}
    errors = {}

    for key, validator in config_validators.items():
        if key in config:
            result = validator(config[key])
            match result:
                case Success(value):
                    results[key] = value
                case Failure(error):
                    errors[key] = error

    if errors:
        return (False, errors)
    return (True, results)

# Test with pattern matching
def apply_config(config):
    is_valid, data = validate_config(config)

    if is_valid:
        print("Configuration valid! Applying settings:")
        for key, value in data.items():
            print(f"  Setting {key} = {value}")
        return True
    else:
        print("Configuration invalid:")
        for key, error in data.items():
            print(f"  - {key}: {error}")
        return False

# Test config
config = {
    "port": 8080,
    "host": "localhost",
    "debug": True,
    "timeout": 30,
    "max_connections": 100
}

apply_config(config)

Data Structure Validation

Valid8r also handles validation of complex data structures:

from valid8r import Maybe, validators
from valid8r.core.maybe import Success, Failure

# Validate a list of items
def validate_list(items, item_validator):
    results = []
    errors = []

    for i, item in enumerate(items):
        result = item_validator(item)
        match result:
            case Success(value):
                results.append(value)
            case Failure(error):
                errors.append(f"Item {i}: {error}")

    if errors:
        return Maybe.failure(errors)
    return Maybe.success(results)

# Validate a dictionary
def validate_dict(data, key_validators):
    results = {}
    errors = {}

    for key, validator in key_validators.items():
        if key in data:
            result = validator(data[key])
            match result:
                case Success(value):
                    results[key] = value
                case Failure(error):
                    errors[key] = error
        else:
            errors[key] = f"Missing required key: {key}"

    if errors:
        return Maybe.failure(errors)
    return Maybe.success(results)

# Example usage with pattern matching
def process_numbers(numbers):
    is_positive = validators.minimum(0)
    result = validate_list(numbers, is_positive)

    match result:
        case Success(valid_numbers):
            total = sum(valid_numbers)
            average = total / len(valid_numbers) if valid_numbers else 0
            print(f"All numbers are valid!")
            print(f"Total: {total}")
            print(f"Average: {average:.2f}")
        case Failure(errors):
            print("Validation failed:")
            for error in errors:
                print(f"  {error}")

# Test with valid data
numbers = [1, 2, 3, 4, 5]
process_numbers(numbers)

# Test with invalid data
numbers_with_errors = [1, -2, 3, -4, 5]
process_numbers(numbers_with_errors)

# Validate user data with pattern matching
def process_user(user):
    user_validators = {
        "name": validators.length(1, 100),
        "age": validators.between(0, 120),
        "email": validators.predicate(
            lambda s: "@" in s,
            "Invalid email format"
        )
    }

    result = validate_dict(user, user_validators)
    match result:
        case Success(valid_user):
            print(f"User data is valid for {valid_user['name']}!")
            return valid_user
        case Failure(errors):
            print("User data has errors:")
            for key, error in errors.items():
                print(f"  - {key}: {error}")
            return None

# Test with user data
user = {
    "name": "John Doe",
    "age": 30,
    "email": "john@example.com"
}

process_user(user)

IP Address parsing

from valid8r.core.maybe import Success, Failure
from valid8r import parsers

# IPv4
match parsers.parse_ipv4("8.8.8.8"):
    case Success(addr):
        print(addr)
    case Failure(err):
        print("Error:", err)

# IPv6
match parsers.parse_ipv6("2001:db8::1"):
    case Success(addr):
        print(addr)
    case Failure(err):
        print("Error:", err)

# CIDR (non-strict)
match parsers.parse_cidr("10.0.0.1/24", strict=False):
    case Success(net):
        print(net)  # 10.0.0.0/24
    case Failure(err):
        print("Error:", err)

These examples provide a solid foundation for understanding how to use Valid8r effectively in your applications. In the next sections, we’ll explore more advanced usage patterns.