Skip to main content

ai_hwaccel/
error.rs

1//! Detection error types.
2//!
3//! Detection is best-effort: errors are collected as warnings rather than
4//! aborting. Callers can inspect them to understand why a backend was skipped.
5
6use std::fmt;
7
8use serde::{Deserialize, Serialize};
9
10/// An error encountered during hardware detection.
11///
12/// These are non-fatal — detection continues even when individual backends
13/// fail. The registry collects them as [`crate::AcceleratorRegistry::warnings`].
14///
15/// # Examples
16///
17/// ```rust
18/// use ai_hwaccel::DetectionError;
19///
20/// let err = DetectionError::ToolNotFound { tool: "nvidia-smi".into() };
21/// assert!(err.to_string().contains("nvidia-smi"));
22///
23/// let err = DetectionError::Timeout { tool: "hl-smi".into(), timeout_secs: 5.0 };
24/// assert!(err.to_string().contains("timed out"));
25/// ```
26#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
27#[non_exhaustive]
28pub enum DetectionError {
29    /// A required CLI tool was not found on `$PATH`.
30    ToolNotFound { tool: String },
31
32    /// A CLI tool was found but exited with a non-zero status.
33    ToolFailed {
34        tool: String,
35        exit_code: Option<i32>,
36        stderr: String,
37    },
38
39    /// A CLI tool did not exit within the allowed timeout.
40    ///
41    /// The process was killed. This is distinct from [`ToolFailed`](Self::ToolFailed)
42    /// to allow callers to implement retry logic for transient slowness.
43    Timeout { tool: String, timeout_secs: f64 },
44
45    /// Output from a CLI tool or sysfs file could not be parsed.
46    ParseError { backend: String, message: String },
47
48    /// A sysfs or procfs path could not be read.
49    SysfsReadError { path: String, message: String },
50}
51
52impl fmt::Display for DetectionError {
53    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54        match self {
55            Self::ToolNotFound { tool } => {
56                write!(f, "{}: tool not found on $PATH", tool)
57            }
58            Self::ToolFailed {
59                tool,
60                exit_code,
61                stderr,
62            } => {
63                write!(
64                    f,
65                    "{}: exited with code {} — {}",
66                    tool,
67                    exit_code
68                        .map(|c| c.to_string())
69                        .unwrap_or_else(|| "signal".into()),
70                    stderr.lines().next().unwrap_or("(no output)")
71                )
72            }
73            Self::Timeout { tool, timeout_secs } => {
74                write!(f, "{}: timed out after {:.1}s", tool, timeout_secs)
75            }
76            Self::ParseError { backend, message } => {
77                write!(f, "{}: parse error — {}", backend, message)
78            }
79            Self::SysfsReadError { path, message } => {
80                write!(f, "sysfs {}: {}", path, message)
81            }
82        }
83    }
84}
85
86impl std::error::Error for DetectionError {}