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 (e.g. rename of an unknown person).
6    NotFound,
7    /// The requested change collides with existing state (e.g. rename onto an
8    /// existing person).
9    Conflict,
10    /// Caller-supplied input was rejected (e.g. an empty label after sanitizing).
11    Invalid,
12    /// Underlying database failure.
13    Db(rusqlite::Error),
14    /// Any other failure surfaced as a plain message (e.g. from videre-core
15    /// functions that return anyhow::Error, like pipeline_runs).
16    Other(String),
17}
18
19impl From<rusqlite::Error> for Error {
20    fn from(e: rusqlite::Error) -> Self {
21        Error::Db(e)
22    }
23}
24
25impl From<anyhow::Error> for Error {
26    fn from(e: anyhow::Error) -> Self {
27        Error::Other(e.to_string())
28    }
29}
30
31impl std::fmt::Display for Error {
32    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33        match self {
34            Error::NotFound => write!(f, "not found"),
35            Error::Conflict => write!(f, "conflict"),
36            Error::Invalid => write!(f, "invalid input"),
37            Error::Db(e) => write!(f, "database error: {e}"),
38            Error::Other(msg) => write!(f, "{msg}"),
39        }
40    }
41}
42
43impl std::error::Error for Error {}
44
45pub type Result<T> = std::result::Result<T, Error>;
46
47#[cfg(test)]
48mod tests {
49    use super::*;
50
51    #[test]
52    fn display_matches_each_variant() {
53        assert_eq!(Error::NotFound.to_string(), "not found");
54        assert_eq!(Error::Conflict.to_string(), "conflict");
55        assert_eq!(Error::Invalid.to_string(), "invalid input");
56        assert_eq!(Error::Other("boom".to_string()).to_string(), "boom");
57    }
58
59    #[test]
60    fn display_for_db_variant_includes_the_underlying_error() {
61        let e = Error::Db(rusqlite::Error::QueryReturnedNoRows);
62        assert!(e.to_string().starts_with("database error: "));
63        assert!(e.to_string().contains("Query returned no rows"));
64    }
65
66    #[test]
67    fn from_rusqlite_error_wraps_as_db_variant() {
68        let e: Error = rusqlite::Error::QueryReturnedNoRows.into();
69        assert!(matches!(e, Error::Db(_)));
70    }
71
72    #[test]
73    fn from_anyhow_error_wraps_message_as_other_variant() {
74        let e: Error = anyhow::anyhow!("something broke").into();
75        match e {
76            Error::Other(msg) => assert_eq!(msg, "something broke"),
77            other => panic!("expected Error::Other, got {other:?}"),
78        }
79    }
80}