Skip to main content

atrg_repo/
error.rs

1//! Error types for AT Protocol repository operations.
2
3use std::fmt;
4
5/// Errors that can occur during repository operations.
6#[derive(Debug)]
7pub enum RepoError {
8    /// The PDS returned an error response.
9    Pds(String),
10    /// The requested record was not found.
11    NotFound,
12    /// The provided AT-URI is malformed.
13    InvalidAtUri(String),
14    /// The blob exceeds the maximum allowed size.
15    BlobTooLarge {
16        /// Actual size in bytes.
17        size: usize,
18        /// Maximum allowed size in bytes.
19        max: usize,
20    },
21    /// The provided TID is malformed.
22    InvalidTid(String),
23    /// A network error occurred communicating with the PDS.
24    Network(reqwest::Error),
25    /// An internal error occurred.
26    Internal(anyhow::Error),
27}
28
29impl fmt::Display for RepoError {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        match self {
32            Self::Pds(msg) => write!(f, "PDS error: {msg}"),
33            Self::NotFound => write!(f, "record not found"),
34            Self::InvalidAtUri(uri) => write!(f, "invalid AT-URI: {uri}"),
35            Self::BlobTooLarge { size, max } => {
36                write!(f, "blob too large: {size} bytes exceeds max {max} bytes")
37            }
38            Self::InvalidTid(msg) => write!(f, "invalid TID: {msg}"),
39            Self::Network(err) => write!(f, "network error: {err}"),
40            Self::Internal(err) => write!(f, "internal error: {err}"),
41        }
42    }
43}
44
45impl std::error::Error for RepoError {
46    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
47        match self {
48            Self::Network(err) => Some(err),
49            Self::Internal(err) => Some(err.as_ref()),
50            _ => None,
51        }
52    }
53}
54
55impl From<reqwest::Error> for RepoError {
56    fn from(err: reqwest::Error) -> Self {
57        Self::Network(err)
58    }
59}
60
61impl From<anyhow::Error> for RepoError {
62    fn from(err: anyhow::Error) -> Self {
63        Self::Internal(err)
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn display_pds_error() {
73        let err = RepoError::Pds("invalid token".to_string());
74        assert_eq!(err.to_string(), "PDS error: invalid token");
75    }
76
77    #[test]
78    fn display_not_found() {
79        let err = RepoError::NotFound;
80        assert_eq!(err.to_string(), "record not found");
81    }
82
83    #[test]
84    fn display_invalid_at_uri() {
85        let err = RepoError::InvalidAtUri("bad://uri".to_string());
86        assert_eq!(err.to_string(), "invalid AT-URI: bad://uri");
87    }
88
89    #[test]
90    fn display_blob_too_large() {
91        let err = RepoError::BlobTooLarge {
92            size: 2_000_000,
93            max: 1_000_000,
94        };
95        assert_eq!(
96            err.to_string(),
97            "blob too large: 2000000 bytes exceeds max 1000000 bytes"
98        );
99    }
100
101    #[test]
102    fn display_invalid_tid() {
103        let err = RepoError::InvalidTid("too short".to_string());
104        assert_eq!(err.to_string(), "invalid TID: too short");
105    }
106
107    #[test]
108    fn display_internal_error() {
109        let err = RepoError::Internal(anyhow::anyhow!("something broke"));
110        assert_eq!(err.to_string(), "internal error: something broke");
111    }
112
113    #[test]
114    fn from_anyhow() {
115        let err: RepoError = anyhow::anyhow!("test").into();
116        assert!(matches!(err, RepoError::Internal(_)));
117    }
118
119    #[test]
120    fn test_source_for_internal() {
121        use std::error::Error;
122        let err = RepoError::Internal(anyhow::anyhow!("x"));
123        assert!(err.source().is_some());
124    }
125
126    #[test]
127    fn test_source_for_not_found() {
128        use std::error::Error;
129        let err = RepoError::NotFound;
130        assert!(err.source().is_none());
131    }
132
133    #[test]
134    fn test_source_for_pds() {
135        use std::error::Error;
136        let err = RepoError::Pds("fail".to_string());
137        assert!(err.source().is_none());
138    }
139
140    #[test]
141    fn test_source_for_invalid_at_uri() {
142        use std::error::Error;
143        let err = RepoError::InvalidAtUri("bad".to_string());
144        assert!(err.source().is_none());
145    }
146
147    #[test]
148    fn test_source_for_blob_too_large() {
149        use std::error::Error;
150        let err = RepoError::BlobTooLarge { size: 100, max: 50 };
151        assert!(err.source().is_none());
152    }
153
154    #[test]
155    fn test_source_for_invalid_tid() {
156        use std::error::Error;
157        let err = RepoError::InvalidTid("bad".to_string());
158        assert!(err.source().is_none());
159    }
160}