[AIR-3][AIS-3][BPC-3][RES-3]
Error Handling in Anya Core¶
Overview¶
Add a brief overview of this document here.
This document outlines the error handling strategy and patterns used throughout the Anya Core codebase.
Table of Contents¶
- Error Types
- Error Handling Patterns
- Error Propagation
- Logging and Monitoring
- Best Practices
- Common Error Scenarios
Error Types¶
1. Domain Errors¶
Errors that represent business logic failures.
#[derive(Debug, thiserror::Error)]
pub enum DomainError {
#[error("Invalid input: {0}")]
ValidationError(String),
#[error("Resource not found: {0}")]
NotFound(String),
#[error("Conflict: {0}")]
Conflict(String),
#[error("Authentication failed")]
AuthenticationError,
#[error("Authorization failed: {0}")]
AuthorizationError(String),
}
2. Infrastructure Errors¶
Errors from external systems and infrastructure.
#[derive(Debug, thiserror::Error)]
pub enum InfrastructureError {
#[error("Database error: {0}")]
DatabaseError(#[from] sqlx::Error),
#[error("IO error: {0}")]
IoError(#[from] std::io::Error),
#[error("Network error: {0}")]
NetworkError(String),
#[error("External service error: {0}")]
ExternalServiceError(String),
}
3. Application Errors¶
Errors specific to application logic and use cases.
#[derive(Debug, thiserror::Error)]
pub enum ApplicationError {
#[error("Invalid operation: {0}")]
InvalidOperation(String),
#[error("Rate limit exceeded")]
RateLimitExceeded,
#[error("Request timeout")]
Timeout,
#[error("Configuration error: {0}")]
ConfigurationError(String),
}
Error Handling Patterns¶
1. Using thiserror
for Error Types¶
use thiserror::Error;
#[derive(Error, Debug)]
pub enum MyError {
#[error("Invalid input: {0}")]
Validation(String),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Database error: {0}")]
Database(#[from] sqlx::Error),
}
2. Result Type Alias¶
pub type Result<T> = std::result::Result<T, MyError>;
fn process_data(data: &[u8]) -> Result<ProcessedData> {
// Function implementation
}
3. Error Conversion¶
impl From<reqwest::Error> for MyError {
fn from(err: reqwest::Error) -> Self {
MyError::NetworkError(err.to_string())
}
}
Error Propagation¶
Using the ?
Operator¶
fn process_file(path: &str) -> Result<Data> {
let content = std::fs::read_to_string(path)?; // Automatically converts io::Error to MyError
let data: Data = serde_json::from_str(&content)?; // Automatically handles serde_json::Error
Ok(data)
}
Contextual Errors¶
use anyhow::{Context, Result};
fn process_config() -> Result<()> {
let config = std::fs::read_to_string("config.toml")
.context("Failed to read config file")?;
let _settings: Settings = toml::from_str(&config)
.context("Failed to parse config file")?;
Ok(())
}
Logging and Monitoring¶
Structured Logging¶
use tracing::{error, info, warn};
async fn process_request(request: Request) -> Result<Response> {
info!(request_id = %request.id, "Processing request");
let result = some_operation().await
.map_err(|e| {
error!(
error = %e,
request_id = %request.id,
"Failed to process request"
);
e
})?;
Ok(Response::new(result))
}
Metrics¶
use metrics::{counter, histogram};
pub async fn process_item(item: Item) -> Result<()> {
let start = std::time::Instant::now();
let result = process(item).await;
let elapsed = start.elapsed();
histogram!("process_item_duration_seconds", elapsed.as_secs_f64());
match &result {
Ok(_) => counter!("process_item_success_total", 1),
Err(e) => {
counter!("process_item_error_total", 1);
error!("Failed to process item: {}", e);
}
}
result
}
Best Practices¶
1. Use Descriptive Error Messages¶
// Bad
#[error("Error")]
// Good
#[error("Failed to parse configuration file: {path}")]
2. Include Context¶
// Bad
read_file(path).await?;
// Good
read_file(path).await
.with_context(|| format!("Failed to read file: {}", path))?;
3. Handle Errors Appropriately¶
match some_operation().await {
Ok(result) => process(result),
Err(MyError::NotFound(_)) => handle_not_found(),
Err(MyError::RateLimitExceeded) => retry_after_delay().await,
Err(e) => return Err(e.into()),
}
Common Error Scenarios¶
1. Database Errors¶
pub async fn get_user(user_id: Uuid) -> Result<User> {
sqlx::query_as!(
User,
"SELECT * FROM users WHERE id = $1",
user_id
)
.fetch_optional(&pool)
.await?
.ok_or_else(|| DomainError::NotFound(format!("User {} not found", user_id)))
}
2. Network Requests¶
pub async fn fetch_data(url: &str) -> Result<Data> {
let response = reqwest::get(url)
.await
.map_err(|e| InfrastructureError::NetworkError(e.to_string()))?;
if !response.status().is_success() {
return Err(InfrastructureError::NetworkError(
format!("Request failed with status: {}", response.status())
));
}
response.json().await
.map_err(|e| InfrastructureError::NetworkError(e.to_string()))
}
3. Input Validation¶
pub struct Email(String);
impl Email {
pub fn new(email: &str) -> Result<Self> {
if !email.contains('@') {
return Err(DomainError::ValidationError(
"Invalid email format".to_string()
));
}
Ok(Self(email.to_string()))
}
}
Testing Error Handling¶
Unit Tests¶
#[cfg(test)]
mod tests {
use super::*;
use assert_matches::assert_matches;
#[test]
fn test_invalid_email() {
let result = Email::new("invalid-email");
assert_matches!(result, Err(DomainError::ValidationError(_)));
}
#[tokio::test]
async fn test_user_not_found() {
let result = get_user(Uuid::new_v4()).await;
assert_matches!(result, Err(DomainError::NotFound(_)));
}
}
Integration Tests¶
#[tokio::test]
async fn test_network_failure() {
let server = mockito::Server::new();
let _m = server
.mock("GET", "/data")
.with_status(500)
.create();
let result = fetch_data(&server.url()).await;
assert_matches!(result, Err(InfrastructureError::NetworkError(_)));
}
Monitoring and Alerting¶
Log Analysis¶
# Search for errors in logs
journalctl -u anya-core --since "1 hour ago" | grep -i error
# Count errors by type
cat app.log | grep ERROR | awk '{print $5}' | sort | uniq -c | sort -nr
Alerting Rules¶
groups:
- name: anya-core-errors
rules:
- alert: HighErrorRate
expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.1
for: 10m
labels:
severity: critical
annotations:
summary: "High error rate on {{ $labels.instance }}"
description: "{{ $value }}% of requests are failing"
Conclusion¶
Proper error handling is crucial for building reliable and maintainable applications. By following these patterns and best practices, we can ensure that Anya Core handles errors gracefully and provides meaningful feedback to users and developers.