Skip to main content

videre_api/
error.rs

1/// Errors returned by videre-api operations. Each consumer maps these to its
2/// own convention (axum -> StatusCode, other embedders -> their own error type).
3#[derive(Debug)]
4pub enum Error {
5    /// The target row/label does not exist.
6    NotFound,
7    /// Caller-supplied input was rejected (e.g. an empty label after sanitizing).
8    Invalid,
9    /// Underlying database failure.
10    Db(rusqlite::Error),
11    /// The library is momentarily unavailable: its root no longer names the
12    /// library the server bound to, or exclusive maintenance holds it. Distinct
13    /// from a permanent failure so the caller can map it to a retryable status
14    /// (503) rather than a 500.
15    Unavailable(String),
16    /// Any other failure surfaced as a plain message (e.g. from videre-core
17    /// functions that return anyhow::Error, like pipeline_runs).
18    Other(String),
19}
20
21impl From<rusqlite::Error> for Error {
22    fn from(e: rusqlite::Error) -> Self {
23        Error::Db(e)
24    }
25}
26
27impl From<anyhow::Error> for Error {
28    fn from(e: anyhow::Error) -> Self {
29        Error::Other(e.to_string())
30    }
31}
32
33impl std::fmt::Display for Error {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        match self {
36            Error::NotFound => write!(f, "not found"),
37            Error::Invalid => write!(f, "invalid input"),
38            Error::Db(e) => write!(f, "database error: {e}"),
39            Error::Unavailable(msg) => write!(f, "library unavailable: {msg}"),
40            Error::Other(msg) => write!(f, "{msg}"),
41        }
42    }
43}
44
45impl std::error::Error for Error {}
46
47pub type Result<T> = std::result::Result<T, Error>;
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52
53    #[test]
54    fn display_matches_each_variant() {
55        assert_eq!(Error::NotFound.to_string(), "not found");
56        assert_eq!(Error::Invalid.to_string(), "invalid input");
57        assert_eq!(
58            Error::Unavailable("root gone".to_string()).to_string(),
59            "library unavailable: root gone"
60        );
61        assert_eq!(Error::Other("boom".to_string()).to_string(), "boom");
62    }
63
64    #[test]
65    fn display_for_db_variant_includes_the_underlying_error() {
66        let e = Error::Db(rusqlite::Error::QueryReturnedNoRows);
67        assert!(e.to_string().starts_with("database error: "));
68        assert!(e.to_string().contains("Query returned no rows"));
69    }
70
71    #[test]
72    fn from_rusqlite_error_wraps_as_db_variant() {
73        let e: Error = rusqlite::Error::QueryReturnedNoRows.into();
74        assert!(matches!(e, Error::Db(_)));
75    }
76
77    #[test]
78    fn from_anyhow_error_wraps_message_as_other_variant() {
79        let e: Error = anyhow::anyhow!("something broke").into();
80        match e {
81            Error::Other(msg) => assert_eq!(msg, "something broke"),
82            other => panic!("expected Error::Other, got {other:?}"),
83        }
84    }
85}