use std::time::Duration;
use rust_expect::prelude::*;
#[tokio::main]
async fn main() -> Result<()> {
println!("rust-expect Error Handling Example");
println!("===================================\n");
println!("1. Handling timeout errors...");
demonstrate_timeout_handling().await?;
println!("\n2. Pattern matching on error types...");
demonstrate_error_pattern_matching();
println!("\n3. Error recovery with retries...");
demonstrate_retry_logic().await?;
println!("\n4. Extracting diagnostic information...");
demonstrate_error_diagnostics().await?;
println!("\n5. Handling spawn errors...");
demonstrate_spawn_error_handling().await;
println!("\n6. Using error context...");
demonstrate_error_context();
println!("\n7. Graceful degradation strategies...");
demonstrate_graceful_degradation().await?;
println!("\nError handling examples completed successfully!");
Ok(())
}
async fn demonstrate_timeout_handling() -> Result<()> {
let mut session = Session::spawn("/bin/sh", &[]).await?;
session
.expect_timeout(Pattern::shell_prompt(), Duration::from_secs(2))
.await?;
session.send_line("echo 'output'").await?;
match session
.expect_timeout("nonexistent pattern", Duration::from_millis(500))
.await
{
Ok(_) => println!(" Pattern found (unexpected)"),
Err(ExpectError::Timeout {
duration, buffer, ..
}) => {
println!(" Timeout after {duration:?} as expected");
println!(" Buffer had {} bytes of data", buffer.len());
println!(" Recovery: Will wait for actual prompt instead");
session.clear_buffer();
session
.expect_timeout(Pattern::shell_prompt(), Duration::from_secs(2))
.await?;
println!(" Recovered successfully");
}
Err(e) => println!(" Unexpected error: {e}"),
}
session.send_line("exit").await?;
let _ = session.wait().await;
Ok(())
}
fn demonstrate_error_pattern_matching() {
let errors: Vec<ExpectError> = vec![
ExpectError::timeout(Duration::from_secs(5), "password:", "Enter username:"),
ExpectError::pattern_not_found("expected_output", "actual output here"),
ExpectError::eof("last output before EOF"),
ExpectError::SessionClosed,
ExpectError::invalid_pattern("regex error in [pattern"),
];
for err in errors {
let recovery = match &err {
ExpectError::Timeout {
duration, pattern, ..
} => {
format!("Retry with longer timeout (was {duration:?} for pattern '{pattern}')")
}
ExpectError::PatternNotFound { pattern, .. } => {
format!("Verify pattern '{pattern}' is correct, or wait longer")
}
ExpectError::Eof { .. } => "Process terminated - restart session".to_string(),
ExpectError::SessionClosed => "Create new session".to_string(),
ExpectError::InvalidPattern { message } => {
format!("Fix pattern syntax: {message}")
}
ExpectError::ProcessExited { exit_status, .. } => {
format!("Process exited with {exit_status:?} - check command")
}
ExpectError::Io(io_err) => {
format!("I/O error ({}): check system resources", io_err.kind())
}
_ => "Investigate error".to_string(),
};
println!(" {:20} -> {}", error_type_name(&err), recovery);
}
}
const fn error_type_name(err: &ExpectError) -> &'static str {
match err {
ExpectError::Timeout { .. } => "Timeout",
ExpectError::PatternNotFound { .. } => "PatternNotFound",
ExpectError::Eof { .. } => "Eof",
ExpectError::SessionClosed => "SessionClosed",
ExpectError::InvalidPattern { .. } => "InvalidPattern",
ExpectError::ProcessExited { .. } => "ProcessExited",
ExpectError::Io(_) => "Io",
ExpectError::Spawn(_) => "Spawn",
ExpectError::Regex(_) => "Regex",
_ => "Other",
}
}
async fn demonstrate_retry_logic() -> Result<()> {
let mut session = Session::spawn("/bin/sh", &[]).await?;
session
.expect_timeout(Pattern::shell_prompt(), Duration::from_secs(2))
.await?;
session.send_line("echo 'ready after delay'").await?;
let max_attempts = 3u32;
let base_delay = Duration::from_millis(100);
let mut last_error = None;
for attempt in 1..=max_attempts {
match session
.expect_timeout("ready", Duration::from_millis(500))
.await
{
Ok(m) => {
if attempt > 1 {
println!(" Succeeded on attempt {attempt}");
}
println!(" Success: '{}'", m.matched.trim());
last_error = None;
break;
}
Err(e) if attempt < max_attempts && e.is_timeout() => {
let delay = base_delay * 2u32.pow(attempt - 1);
println!(" Attempt {attempt} failed, retrying in {delay:?}...");
tokio::time::sleep(delay).await;
last_error = Some(e);
}
Err(e) => {
last_error = Some(e);
break;
}
}
}
if let Some(e) = last_error {
println!(" All retries failed: {}", error_type_name(&e));
}
session.send_line("exit").await?;
let _ = session.wait().await;
Ok(())
}
async fn demonstrate_error_diagnostics() -> Result<()> {
let mut session = Session::spawn("/bin/sh", &[]).await?;
session
.expect_timeout(Pattern::shell_prompt(), Duration::from_secs(2))
.await?;
session
.send_line("echo 'line1'; echo 'line2'; echo 'line3'")
.await?;
let err = session
.expect_timeout("will_not_match", Duration::from_millis(200))
.await
.unwrap_err();
println!(" Error type: {:?}", error_type_name(&err));
println!(" Is timeout: {}", err.is_timeout());
println!(" Is EOF: {}", err.is_eof());
if let Some(buffer) = err.buffer() {
let lines: Vec<&str> = buffer.lines().collect();
println!(" Buffer lines: {}", lines.len());
println!(" Last line: {:?}", lines.last().unwrap_or(&"(empty)"));
}
let msg = err.to_string();
println!(" Error includes tips: {}", msg.contains("Tip:"));
session.send_line("exit").await?;
let _ = session.wait().await;
Ok(())
}
async fn demonstrate_spawn_error_handling() {
let result = Session::spawn("/nonexistent/command", &[]).await;
match result {
Ok(_) => println!(" Session created (unexpected)"),
Err(ExpectError::Spawn(spawn_err)) => {
println!(" Spawn error: {spawn_err}");
match &spawn_err {
SpawnError::CommandNotFound { command } => {
println!(" Recovery: Check if '{command}' is installed");
}
SpawnError::PermissionDenied { path } => {
println!(" Recovery: Check permissions for '{path}'");
}
SpawnError::PtyAllocation { reason } => {
println!(" Recovery: PTY issue - {reason}");
}
SpawnError::Io(io_err) => {
println!(" Recovery: I/O error - {}", io_err.kind());
}
_ => println!(" Recovery: Investigate spawn failure"),
}
}
Err(e) => println!(" Other error: {e}"),
}
}
fn demonstrate_error_context() {
let io_result: std::io::Result<()> = Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"configuration file missing",
));
match ExpectError::with_io_context(io_result, "loading session config") {
Ok(()) => println!(" Config loaded"),
Err(ExpectError::IoWithContext { context, source }) => {
println!(" Context: {context}");
println!(" Cause: {source}");
println!(" Error kind: {:?}", source.kind());
}
Err(e) => println!(" Other error: {e}"),
}
let contextualized = ExpectError::io_context(
"writing transcript",
std::io::Error::new(std::io::ErrorKind::PermissionDenied, "read-only filesystem"),
);
println!(" Contextualized error: {contextualized}");
}
async fn demonstrate_graceful_degradation() -> Result<()> {
let result = try_with_fallback(
"Primary shell",
Session::spawn("/bin/bash", &[]),
|_| async {
try_with_fallback(
"Fallback shell",
Session::spawn("/bin/sh", &[]),
|_| async {
Err(ExpectError::config("No shell available"))
},
)
.await
},
)
.await;
match result {
Ok(mut session) => {
println!(" Session established");
session.send_line("exit").await?;
let _ = session.wait().await;
}
Err(e) => {
println!(" All fallbacks failed: {e}");
}
}
println!("\n Pattern: Optional feature detection");
let mut session = Session::spawn("/bin/sh", &[]).await?;
session
.expect_timeout(Pattern::shell_prompt(), Duration::from_secs(2))
.await?;
session
.send_line("which jq 2>/dev/null || echo 'NOT_FOUND'")
.await?;
let m = session
.expect_timeout(Pattern::shell_prompt(), Duration::from_secs(2))
.await?;
if m.before.contains("NOT_FOUND") {
println!(" jq not found - using fallback JSON parsing");
} else {
println!(" jq available - using native JSON parsing");
}
session.send_line("exit").await?;
let _ = session.wait().await;
Ok(())
}
async fn try_with_fallback<T, F, Fut>(
name: &str,
primary: impl std::future::Future<Output = Result<T>>,
fallback: F,
) -> Result<T>
where
F: FnOnce(ExpectError) -> Fut,
Fut: std::future::Future<Output = Result<T>>,
{
match primary.await {
Ok(value) => {
println!(" {name}: success");
Ok(value)
}
Err(e) => {
println!(" {name}: failed ({e}), trying fallback...");
fallback(e).await
}
}
}