use core::fmt;
pub type Result<T, E = PixelsError> = core::result::Result<T, E>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[non_exhaustive]
pub enum ErrorCode {
Io,
Malformed,
Unsupported,
LimitExceeded,
InvalidArgument,
Graph,
}
impl ErrorCode {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Io => "io",
Self::Malformed => "malformed",
Self::Unsupported => "unsupported",
Self::LimitExceeded => "limit_exceeded",
Self::InvalidArgument => "invalid_argument",
Self::Graph => "graph",
}
}
}
impl fmt::Display for ErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Limit {
MaxPixels,
Dimension,
}
impl Limit {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::MaxPixels => "max_pixels",
Self::Dimension => "dimension",
}
}
}
impl fmt::Display for Limit {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum PixelsError {
Io {
context: &'static str,
source: std::io::Error,
},
Malformed {
format: &'static str,
detail: String,
},
Unsupported {
detail: String,
},
LimitExceeded {
limit: Limit,
requested: u64,
allowed: u64,
},
InvalidArgument {
parameter: &'static str,
detail: String,
},
Graph {
detail: String,
},
}
impl PixelsError {
#[must_use]
pub const fn code(&self) -> ErrorCode {
match self {
Self::Io { .. } => ErrorCode::Io,
Self::Malformed { .. } => ErrorCode::Malformed,
Self::Unsupported { .. } => ErrorCode::Unsupported,
Self::LimitExceeded { .. } => ErrorCode::LimitExceeded,
Self::InvalidArgument { .. } => ErrorCode::InvalidArgument,
Self::Graph { .. } => ErrorCode::Graph,
}
}
#[must_use]
pub fn io(context: &'static str, source: std::io::Error) -> Self {
Self::Io { context, source }
}
#[must_use]
pub fn malformed(format: &'static str, detail: impl Into<String>) -> Self {
Self::Malformed {
format,
detail: detail.into(),
}
}
#[must_use]
pub fn unsupported(detail: impl Into<String>) -> Self {
Self::Unsupported {
detail: detail.into(),
}
}
#[must_use]
pub fn invalid_argument(parameter: &'static str, detail: impl Into<String>) -> Self {
Self::InvalidArgument {
parameter,
detail: detail.into(),
}
}
#[must_use]
pub fn graph(detail: impl Into<String>) -> Self {
Self::Graph {
detail: detail.into(),
}
}
#[must_use]
pub const fn limit_exceeded(limit: Limit, requested: u64, allowed: u64) -> Self {
Self::LimitExceeded {
limit,
requested,
allowed,
}
}
}
impl fmt::Display for PixelsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Io { context, source } => write!(f, "i/o error while {context}: {source}"),
Self::Malformed { format, detail } => {
write!(f, "malformed {format} input: {detail}")
}
Self::Unsupported { detail } => write!(f, "unsupported: {detail}"),
Self::LimitExceeded {
limit,
requested,
allowed,
} => write!(
f,
"limit `{limit}` exceeded: requested {requested}, allowed {allowed}"
),
Self::InvalidArgument { parameter, detail } => {
write!(f, "invalid argument `{parameter}`: {detail}")
}
Self::Graph { detail } => write!(f, "invalid graph: {detail}"),
}
}
}
impl std::error::Error for PixelsError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Io { source, .. } => Some(source),
_ => None,
}
}
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::indexing_slicing,
reason = "tests operate on known-good values and assert shapes directly"
)]
mod tests {
use super::*;
#[test]
fn code_is_stable_per_variant() {
assert_eq!(
PixelsError::malformed("raw", "x").code(),
ErrorCode::Malformed
);
assert_eq!(PixelsError::unsupported("x").code(), ErrorCode::Unsupported);
assert_eq!(PixelsError::graph("x").code(), ErrorCode::Graph);
assert_eq!(
PixelsError::invalid_argument("w", "x").code(),
ErrorCode::InvalidArgument
);
assert_eq!(
PixelsError::limit_exceeded(Limit::MaxPixels, 5, 4).code(),
ErrorCode::LimitExceeded
);
let io = PixelsError::io("reading", std::io::Error::other("boom"));
assert_eq!(io.code(), ErrorCode::Io);
}
#[test]
fn code_strings_are_stable() {
assert_eq!(ErrorCode::Io.as_str(), "io");
assert_eq!(ErrorCode::Malformed.as_str(), "malformed");
assert_eq!(ErrorCode::Unsupported.as_str(), "unsupported");
assert_eq!(ErrorCode::LimitExceeded.as_str(), "limit_exceeded");
assert_eq!(ErrorCode::InvalidArgument.as_str(), "invalid_argument");
assert_eq!(ErrorCode::Graph.as_str(), "graph");
assert_eq!(Limit::MaxPixels.as_str(), "max_pixels");
assert_eq!(Limit::Dimension.as_str(), "dimension");
}
#[test]
fn io_errors_expose_their_source() {
use std::error::Error as _;
let err = PixelsError::io("reading header", std::io::Error::other("boom"));
assert!(err.source().is_some());
assert!(PixelsError::graph("x").source().is_none());
assert!(err.to_string().contains("reading header"));
}
#[test]
fn limit_display_names_the_limit() {
let err = PixelsError::limit_exceeded(Limit::MaxPixels, 300, 268);
let text = err.to_string();
assert!(text.contains("max_pixels"), "{text}");
assert!(text.contains("300"), "{text}");
assert!(text.contains("268"), "{text}");
}
}