Module error

Source
Expand description

Error types and handling for the client-core library

This module defines all error types that can occur during client operations and provides guidance on how to handle them.

§Error Categories

Errors are categorized to help with recovery strategies:

  • Configuration Errors - Invalid settings, can’t recover without fixing config
  • Network Errors - Temporary network issues, usually recoverable with retry
  • Protocol Errors - SIP protocol violations, may need different approach
  • Media Errors - Audio/RTP issues, might need codec renegotiation
  • State Errors - Invalid operation for current state, check state first

§Error Handling Guide

§Basic Pattern

match client.make_call(
    "sip:alice@example.com".to_string(),
    "sip:bob@example.com".to_string(),
    None
).await {
    Ok(call_id) => {
        println!("Call started: {}", call_id);
    }
    Err(ClientError::NetworkError { reason }) => {
        eprintln!("Network problem: {}", reason);
        // Retry after checking network connectivity
    }
    Err(ClientError::InvalidConfiguration { field, reason }) => {
        eprintln!("Config error in {}: {}", field, reason);
        // Fix configuration before retrying
    }
    Err(e) => {
        eprintln!("Unexpected error: {}", e);
        // Log and notify user
    }
}

§Recovery Strategies

§Network Errors

Network errors are often temporary. Implement exponential backoff:

async fn with_retry<T, F, Fut>(
    mut operation: F,
    max_attempts: u32,
) -> Result<T, ClientError>
where
    F: FnMut() -> Fut,
    Fut: std::future::Future<Output = Result<T, ClientError>>,
{
    let mut attempt = 0;
    let mut delay = Duration::from_millis(100);
     
    loop {
        match operation().await {
            Ok(result) => return Ok(result),
            Err(e) if e.is_recoverable() && attempt < max_attempts => {
                attempt += 1;
                tokio::time::sleep(delay).await;
                delay *= 2; // Exponential backoff
            }
            Err(e) => return Err(e),
        }
    }
}
 
// Use with any operation
let call_id = with_retry(|| async {
    client.make_call(
        "sip:alice@example.com".to_string(),
        "sip:bob@example.com".to_string(),
        None
    ).await
}, 3).await?;

§Registration Errors

Handle authentication and server errors:

match client.register_simple(
    "sip:alice@example.com",
    &"127.0.0.1:5060".parse().unwrap(),
    Duration::from_secs(3600)
).await {
    Ok(()) => {
        println!("Registered successfully");
    }
    Err(e) if e.is_auth_error() => {
        // Prompt user for credentials
        println!("Please check your username and password");
    }
    Err(ClientError::RegistrationFailed { reason }) => {
        if reason.contains("timeout") {
            // Server might be down
            println!("Server not responding, try again later");
        } else if reason.contains("forbidden") {
            // Account might be disabled
            println!("Registration forbidden, contact support");
        }
    }
    Err(e) => eprintln!("Registration error: {}", e),
}

§Call State Errors

Check state before operations:

// Safe call control with state checking
async fn safe_hold_call(client: &Arc<Client>, call_id: &CallId) -> Result<(), ClientError> {
    // Get call info first
    let info = client.get_call(call_id).await?;
     
    // Check if we can hold
    match info.state {
        rvoip_client_core::call::CallState::Connected => {
            client.hold_call(call_id).await
        }
        rvoip_client_core::call::CallState::Failed => {
            // Call failed, cannot hold
            Err(ClientError::InvalidCallStateGeneric {
                expected: "Connected".to_string(),
                actual: "Failed".to_string(),
            })
        }
        _ => {
            Err(ClientError::InvalidCallStateGeneric {
                expected: "Connected".to_string(),
                actual: format!("{:?}", info.state),
            })
        }
    }
}
 
safe_hold_call(&client, &call_id).await?;

§Media Errors

Handle codec and port allocation issues:

match client.establish_media(&call_id, "remote.example.com:30000").await {
    Ok(_) => println!("Media established"),
    Err(ClientError::MediaNegotiationFailed { reason }) => {
        if reason.contains("codec") {
            // Try with different codec
            println!("Codec mismatch, trying fallback");
        } else if reason.contains("port") {
            // Port allocation failed
            println!("No available media ports");
        }
    }
    Err(e) => eprintln!("Media setup failed: {}", e),
}

§Error Context

Always log errors with context for debugging:

use tracing::{error, warn, info};
 
match client.answer_call(&call_id).await {
    Ok(_) => info!(call_id = %call_id, "Call answered successfully"),
    Err(e) => {
        error!(
            call_id = %call_id,
            error = %e,
            error_type = ?e,
            category = e.category(),
            "Failed to answer call"
        );
         
        // Take appropriate action based on error type
        match e {
            ClientError::CallNotFound { .. } => {
                // Call might have been cancelled
            }
            ClientError::MediaNegotiationFailed { .. } => {
                // Try to recover media session
            }
            _ => {
                // Generic error handling
            }
        }
    }
}

§Error Categories Helper

Use the category() method to group errors for metrics:

let mut error_counts: HashMap<&'static str, usize> = HashMap::new();
 
for error in errors {
    *error_counts.entry(error.category()).or_insert(0) += 1;
}
 
// Report metrics
for (category, count) in error_counts {
    println!("{}: {} errors", category, count);
}

Enums§

ClientError
Comprehensive error types for SIP client operations

Type Aliases§

ClientResult
Result type alias for client-core operations