1use core::fmt;
14
15pub type Result<T, E = PixelsError> = core::result::Result<T, E>;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
24#[non_exhaustive]
25pub enum ErrorCode {
26 Io,
28 Malformed,
30 Unsupported,
32 LimitExceeded,
34 InvalidArgument,
36 Graph,
38}
39
40impl ErrorCode {
41 #[must_use]
45 pub const fn as_str(self) -> &'static str {
46 match self {
47 Self::Io => "io",
48 Self::Malformed => "malformed",
49 Self::Unsupported => "unsupported",
50 Self::LimitExceeded => "limit_exceeded",
51 Self::InvalidArgument => "invalid_argument",
52 Self::Graph => "graph",
53 }
54 }
55}
56
57impl fmt::Display for ErrorCode {
58 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59 f.write_str(self.as_str())
60 }
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
65#[non_exhaustive]
66pub enum Limit {
67 MaxPixels,
71 Dimension,
73}
74
75impl Limit {
76 #[must_use]
78 pub const fn as_str(self) -> &'static str {
79 match self {
80 Self::MaxPixels => "max_pixels",
81 Self::Dimension => "dimension",
82 }
83 }
84}
85
86impl fmt::Display for Limit {
87 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88 f.write_str(self.as_str())
89 }
90}
91
92#[derive(Debug)]
98#[non_exhaustive]
99pub enum PixelsError {
100 Io {
102 context: &'static str,
104 source: std::io::Error,
106 },
107 Malformed {
111 format: &'static str,
113 detail: String,
115 },
116 Unsupported {
118 detail: String,
120 },
121 LimitExceeded {
123 limit: Limit,
125 requested: u64,
127 allowed: u64,
129 },
130 InvalidArgument {
132 parameter: &'static str,
134 detail: String,
136 },
137 Graph {
139 detail: String,
141 },
142}
143
144impl PixelsError {
145 #[must_use]
147 pub const fn code(&self) -> ErrorCode {
148 match self {
149 Self::Io { .. } => ErrorCode::Io,
150 Self::Malformed { .. } => ErrorCode::Malformed,
151 Self::Unsupported { .. } => ErrorCode::Unsupported,
152 Self::LimitExceeded { .. } => ErrorCode::LimitExceeded,
153 Self::InvalidArgument { .. } => ErrorCode::InvalidArgument,
154 Self::Graph { .. } => ErrorCode::Graph,
155 }
156 }
157
158 #[must_use]
160 pub fn io(context: &'static str, source: std::io::Error) -> Self {
161 Self::Io { context, source }
162 }
163
164 #[must_use]
166 pub fn malformed(format: &'static str, detail: impl Into<String>) -> Self {
167 Self::Malformed {
168 format,
169 detail: detail.into(),
170 }
171 }
172
173 #[must_use]
175 pub fn unsupported(detail: impl Into<String>) -> Self {
176 Self::Unsupported {
177 detail: detail.into(),
178 }
179 }
180
181 #[must_use]
183 pub fn invalid_argument(parameter: &'static str, detail: impl Into<String>) -> Self {
184 Self::InvalidArgument {
185 parameter,
186 detail: detail.into(),
187 }
188 }
189
190 #[must_use]
192 pub fn graph(detail: impl Into<String>) -> Self {
193 Self::Graph {
194 detail: detail.into(),
195 }
196 }
197
198 #[must_use]
200 pub const fn limit_exceeded(limit: Limit, requested: u64, allowed: u64) -> Self {
201 Self::LimitExceeded {
202 limit,
203 requested,
204 allowed,
205 }
206 }
207}
208
209impl fmt::Display for PixelsError {
210 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211 match self {
212 Self::Io { context, source } => write!(f, "i/o error while {context}: {source}"),
213 Self::Malformed { format, detail } => {
214 write!(f, "malformed {format} input: {detail}")
215 }
216 Self::Unsupported { detail } => write!(f, "unsupported: {detail}"),
217 Self::LimitExceeded {
218 limit,
219 requested,
220 allowed,
221 } => write!(
222 f,
223 "limit `{limit}` exceeded: requested {requested}, allowed {allowed}"
224 ),
225 Self::InvalidArgument { parameter, detail } => {
226 write!(f, "invalid argument `{parameter}`: {detail}")
227 }
228 Self::Graph { detail } => write!(f, "invalid graph: {detail}"),
229 }
230 }
231}
232
233impl std::error::Error for PixelsError {
234 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
235 match self {
236 Self::Io { source, .. } => Some(source),
237 _ => None,
238 }
239 }
240}
241
242#[cfg(test)]
243#[allow(
244 clippy::unwrap_used,
245 clippy::indexing_slicing,
246 reason = "tests operate on known-good values and assert shapes directly"
247)]
248mod tests {
249 use super::*;
250
251 #[test]
252 fn code_is_stable_per_variant() {
253 assert_eq!(
254 PixelsError::malformed("raw", "x").code(),
255 ErrorCode::Malformed
256 );
257 assert_eq!(PixelsError::unsupported("x").code(), ErrorCode::Unsupported);
258 assert_eq!(PixelsError::graph("x").code(), ErrorCode::Graph);
259 assert_eq!(
260 PixelsError::invalid_argument("w", "x").code(),
261 ErrorCode::InvalidArgument
262 );
263 assert_eq!(
264 PixelsError::limit_exceeded(Limit::MaxPixels, 5, 4).code(),
265 ErrorCode::LimitExceeded
266 );
267 let io = PixelsError::io("reading", std::io::Error::other("boom"));
268 assert_eq!(io.code(), ErrorCode::Io);
269 }
270
271 #[test]
272 fn code_strings_are_stable() {
273 assert_eq!(ErrorCode::Io.as_str(), "io");
274 assert_eq!(ErrorCode::Malformed.as_str(), "malformed");
275 assert_eq!(ErrorCode::Unsupported.as_str(), "unsupported");
276 assert_eq!(ErrorCode::LimitExceeded.as_str(), "limit_exceeded");
277 assert_eq!(ErrorCode::InvalidArgument.as_str(), "invalid_argument");
278 assert_eq!(ErrorCode::Graph.as_str(), "graph");
279 assert_eq!(Limit::MaxPixels.as_str(), "max_pixels");
280 assert_eq!(Limit::Dimension.as_str(), "dimension");
281 }
282
283 #[test]
284 fn io_errors_expose_their_source() {
285 use std::error::Error as _;
286 let err = PixelsError::io("reading header", std::io::Error::other("boom"));
287 assert!(err.source().is_some());
288 assert!(PixelsError::graph("x").source().is_none());
289 assert!(err.to_string().contains("reading header"));
290 }
291
292 #[test]
293 fn limit_display_names_the_limit() {
294 let err = PixelsError::limit_exceeded(Limit::MaxPixels, 300, 268);
295 let text = err.to_string();
296 assert!(text.contains("max_pixels"), "{text}");
297 assert!(text.contains("300"), "{text}");
298 assert!(text.contains("268"), "{text}");
299 }
300}