1use std::fmt;
4
5#[derive(Debug)]
7pub enum RepoError {
8 Pds(String),
10 NotFound,
12 InvalidAtUri(String),
14 BlobTooLarge {
16 size: usize,
18 max: usize,
20 },
21 InvalidTid(String),
23 Network(reqwest::Error),
25 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}