Skip to content

Error Handling

OptimLLM uses the thiserror crate for comprehensive, ergonomic error handling.

OptillmError Enum

Rust
pub enum OptillmError {
    #[error("Invalid configuration: {0}")]
    InvalidConfiguration(String),

    #[error("Client error: {0}")]
    ClientError(String),

    #[error("Parsing error: {0}")]
    ParsingError(String),

    #[error("Timeout: {0}")]
    Timeout(String),

    // ... more variants specific to MARS
}

Result Type Alias

The standard return type for all optillm operations:

Rust
pub type Result<T> = std::result::Result<T, OptillmError>;

Error Creation

Returning Errors

Rust
pub fn validate_config(config: &MyConfig) -> Result<()> {
    if config.temperature > 2.0 {
        return Err(OptillmError::InvalidConfiguration(
            "Temperature must be <= 2.0".to_string()
        ));
    }
    Ok(())
}

Using the ? Operator

Rust
pub async fn my_optimizer(
    query: &str,
    client: &dyn ModelClient,
) -> Result<Solution> {
    // Errors automatically convert and propagate
    let result = client.stream(&prompt);

    Ok(solution)
}

Converting from Other Errors

Rust
// From serde_json errors
let solution: Solution = serde_json::from_str(json)
    .map_err(|e| OptillmError::ParsingError(e.to_string()))?;

// From I/O errors
let content = std::fs::read_to_string("config.toml")
    .map_err(|e| OptillmError::ClientError(e.to_string()))?;

Error Handling in Async Code

With ? operator

Rust
async fn process(client: &dyn ModelClient) -> Result<String> {
    let stream = client.stream(&prompt);
    let result = stream.next().await?; // Propagates error

    Ok(result)
}

With Match

Rust
async fn process(client: &dyn ModelClient) -> Result<String> {
    match client.stream(&prompt).next().await {
        Some(Ok(event)) => {
            // Handle event
            Ok(String::new())
        }
        Some(Err(e)) => Err(e),
        None => Err(OptillmError::ClientError(
            "Stream ended unexpectedly".to_string()
        )),
    }
}

With map_err

Rust
async fn process(client: &dyn ModelClient) -> Result<String> {
    client.stream(&prompt)
        .next()
        .await
        .ok_or(OptillmError::ClientError(
            "No response".to_string()
        ))?;

    Ok(String::new())
}

Error Context

Add context when converting errors:

Rust
pub async fn call_llm(client: &dyn ModelClient) -> Result<String> {
    client.stream(&prompt)
        .next()
        .await
        .ok_or_else(|| OptillmError::ClientError(
            format!(
                "LLM call failed for query: '{}' with model: '{}'",
                query, model_id
            )
        ))?;

    Ok(String::new())
}

Best Practices

1. Provide Context

Rust
// Good - explains what failed
Err(OptillmError::ClientError(
    format!("Failed to connect to {}: {}", base_url, error)
))

// Bad - no context
Err(OptillmError::ClientError("Error".to_string()))

2. Fail Fast

Rust
// Good - exit early on error
pub async fn process(config: &Config) -> Result<()> {
    validate_config(config)?;
    let client = create_client(config)?;
    let solution = solve(query, &client).await?;
    Ok(())
}

3. Avoid Unwrap/Panic

Rust
// Bad - panics on error
let solution = my_optimizer.optimize(q, c).await.unwrap();

// Good - returns error
let solution = my_optimizer.optimize(q, c).await?;

4. Match on Error Types

Rust
match my_function().await {
    Ok(result) => println!("Success: {}", result),
    Err(OptillmError::InvalidConfiguration(msg)) => {
        eprintln!("Bad config: {}", msg);
    }
    Err(OptillmError::ClientError(msg)) => {
        eprintln!("Client error: {}", msg);
    }
    Err(e) => {
        eprintln!("Other error: {}", e);
    }
}

5. Custom Error Types for Libraries

If creating a library on top of optillm-rs:

Rust
use thiserror::Error;

#[derive(Error, Debug)]
pub enum MyError {
    #[error("OptimLLM error: {0}")]
    OptimLLM(#[from] OptillmError),

    #[error("Custom error: {0}")]
    Custom(String),
}

pub type Result<T> = std::result::Result<T, MyError>;

Testing Error Cases

Rust
#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_invalid_config_error() {
        let config = invalid_config();
        let result = validate_config(&config);

        match result {
            Err(OptillmError::InvalidConfiguration(msg)) => {
                assert!(msg.contains("Temperature"));
            }
            other => panic!("Unexpected result: {:?}", other),
        }
    }

    #[test]
    fn test_error_message() {
        let error = OptillmError::InvalidConfiguration(
            "test error".to_string()
        );
        assert_eq!(
            error.to_string(),
            "Invalid configuration: test error"
        );
    }
}

Error Display

OptillmError implements Display via thiserror:

Rust
let error = OptillmError::InvalidConfiguration("foo".to_string());
println!("{}", error);  // "Invalid configuration: foo"

Common Error Scenarios

Scenario Error Type Cause
Bad config value InvalidConfiguration User provides invalid setting
Network failure ClientError Cannot reach LLM provider
Invalid response ParsingError Provider returns unexpected format
Takes too long Timeout Operation exceeds time limit

See OptimLLM API for complete error documentation.