199 lines
5.2 KiB
Python
199 lines
5.2 KiB
Python
"""
|
|
Validators for API data validation.
|
|
Provides reusable validation functions for domain models data.
|
|
"""
|
|
import re
|
|
import uuid
|
|
from datetime import datetime, date
|
|
from typing import Any, Dict, List, Optional, Union
|
|
|
|
|
|
def is_valid_uuid(value: Any) -> bool:
|
|
"""
|
|
Check if a value is a valid UUID.
|
|
|
|
Args:
|
|
value: The value to check.
|
|
|
|
Returns:
|
|
bool: True if the value is a valid UUID, False otherwise.
|
|
"""
|
|
if isinstance(value, uuid.UUID):
|
|
return True
|
|
|
|
if not isinstance(value, str):
|
|
return False
|
|
|
|
try:
|
|
uuid.UUID(value)
|
|
return True
|
|
except (ValueError, AttributeError, TypeError):
|
|
return False
|
|
|
|
|
|
def is_valid_email(email: str) -> bool:
|
|
"""
|
|
Validate an email address format.
|
|
|
|
Args:
|
|
email: The email address to validate.
|
|
|
|
Returns:
|
|
bool: True if the email format is valid, False otherwise.
|
|
"""
|
|
if not email or not isinstance(email, str):
|
|
return False
|
|
|
|
# Simple regex for email validation
|
|
email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
|
|
return bool(re.match(email_pattern, email))
|
|
|
|
|
|
def is_valid_phone(phone: str) -> bool:
|
|
"""
|
|
Validate a phone number format.
|
|
|
|
Args:
|
|
phone: The phone number to validate.
|
|
|
|
Returns:
|
|
bool: True if the phone format is valid, False otherwise.
|
|
"""
|
|
if not phone or not isinstance(phone, str):
|
|
return False
|
|
|
|
# Remove common formatting characters
|
|
cleaned = re.sub(r'[\s\-\(\)\.]+', '', phone)
|
|
|
|
# Check if it's a valid format (allows international format)
|
|
phone_pattern = r'^(\+\d{1,3})?(\d{10,15})$'
|
|
return bool(re.match(phone_pattern, cleaned))
|
|
|
|
|
|
def is_valid_date(date_val: Any) -> bool:
|
|
"""
|
|
Validate a date string in ISO format (YYYY-MM-DD).
|
|
|
|
Args:
|
|
date_val: The date to validate.
|
|
|
|
Returns:
|
|
bool: True if the date is valid, False otherwise.
|
|
"""
|
|
return isinstance(date_val, date)
|
|
|
|
|
|
def is_valid_datetime(datetime_str: str) -> bool:
|
|
"""
|
|
Validate a datetime string in ISO format.
|
|
|
|
Args:
|
|
datetime_str: The datetime string to validate.
|
|
|
|
Returns:
|
|
bool: True if the datetime format is valid, False otherwise.
|
|
"""
|
|
if not datetime_str or not isinstance(datetime_str, str):
|
|
return False
|
|
|
|
try:
|
|
datetime.fromisoformat(datetime_str)
|
|
return True
|
|
except ValueError:
|
|
return False
|
|
|
|
|
|
def validate_required_fields(data: Dict[str, Any], required_fields: List[str]) -> List[str]:
|
|
"""
|
|
Validate that all required fields are present and not empty in the data.
|
|
|
|
Args:
|
|
data: Dictionary containing the data to validate.
|
|
required_fields: List of field names that are required.
|
|
|
|
Returns:
|
|
List[str]: List of missing field names. Empty if all required fields are present.
|
|
"""
|
|
missing_fields = []
|
|
|
|
for field in required_fields:
|
|
if field not in data or data[field] is None or (isinstance(data[field], str) and not data[field].strip()):
|
|
missing_fields.append(field)
|
|
|
|
return missing_fields
|
|
|
|
|
|
def validate_model_exists(model_id: str, model_type: str,
|
|
repository_method: callable, error_message: Optional[str] = None) -> Dict[str, Any]:
|
|
"""
|
|
Validate that a model with the given ID exists.
|
|
|
|
Args:
|
|
model_id: The ID of the model to check.
|
|
model_type: The type of model (e.g., 'customer', 'account').
|
|
repository_method: Repository method to fetch the model.
|
|
error_message: Custom error message. If None, a default message is used.
|
|
|
|
Returns:
|
|
Dict[str, Any]: Dictionary with 'valid' and 'error' keys.
|
|
"""
|
|
if not is_valid_uuid(model_id):
|
|
return {
|
|
'valid': False,
|
|
'error': f"Invalid {model_type} ID format."
|
|
}
|
|
|
|
model = repository_method(model_id)
|
|
if not model:
|
|
return {
|
|
'valid': False,
|
|
'error': error_message or f"{model_type.capitalize()} with ID {model_id} not found."
|
|
}
|
|
|
|
return {
|
|
'valid': True,
|
|
'model': model,
|
|
'error': None
|
|
}
|
|
|
|
|
|
def validate_decimal_amount(amount: Union[float, str, int], field_name: str = 'amount') -> Dict[str, Any]:
|
|
"""
|
|
Validate a decimal amount (e.g., for money).
|
|
|
|
Args:
|
|
amount: The amount to validate.
|
|
field_name: The name of the field being validated.
|
|
|
|
Returns:
|
|
Dict[str, Any]: Dictionary with 'valid' and 'error' keys.
|
|
"""
|
|
try:
|
|
# Convert to float if it's a string
|
|
if isinstance(amount, str):
|
|
amount = float(amount)
|
|
|
|
# Check if it's a number
|
|
if not isinstance(amount, (int, float)):
|
|
return {
|
|
'valid': False,
|
|
'error': f"{field_name} must be a number."
|
|
}
|
|
|
|
# Check if it's non-negative
|
|
if amount < 0:
|
|
return {
|
|
'valid': False,
|
|
'error': f"{field_name} cannot be negative."
|
|
}
|
|
|
|
return {
|
|
'valid': True,
|
|
'amount': float(amount),
|
|
'error': None
|
|
}
|
|
except ValueError:
|
|
return {
|
|
'valid': False,
|
|
'error': f"Invalid {field_name} format."
|
|
} |