use thiserror::Error;
#[derive(Debug, Error)]
pub enum ArtifactError {
#[error("artifact not found: {0}")]
NotFound(String),
#[error("invalid artifact name {name:?}: {reason}")]
InvalidName {
name: String,
reason: &'static str,
},
#[error("invalid storage key {key:?}: {reason}")]
InvalidKey {
key: String,
reason: &'static str,
},
#[error("artifact exceeds the {limit_bytes} byte limit")]
TooLarge {
limit_bytes: u64,
},
#[error("artifact storage io error: {0}")]
Io(String),
}
impl From<std::io::Error> for ArtifactError {
fn from(err: std::io::Error) -> Self {
ArtifactError::Io(err.to_string())
}
}
#[cfg(test)]
mod tests {
use std::io::{Error as IoError, ErrorKind};
use super::*;
#[test]
fn not_found_display() {
let err = ArtifactError::NotFound("a/b".to_string());
assert_eq!(err.to_string(), "artifact not found: a/b");
}
#[test]
fn invalid_name_display_quotes_the_name() {
let err = ArtifactError::InvalidName {
name: "../etc/passwd".to_string(),
reason: "contains a forbidden character",
};
assert!(err.to_string().contains("\"../etc/passwd\""));
assert!(err.to_string().contains("forbidden character"));
}
#[test]
fn invalid_key_display() {
let err = ArtifactError::InvalidKey {
key: "/abs".to_string(),
reason: "must be relative",
};
assert!(err.to_string().contains("must be relative"));
}
#[test]
fn too_large_display_shows_the_limit() {
let err = ArtifactError::TooLarge { limit_bytes: 1024 };
assert!(err.to_string().contains("1024"));
}
#[test]
fn io_error_converts() {
let io = IoError::new(ErrorKind::PermissionDenied, "nope");
let err = ArtifactError::from(io);
assert!(matches!(err, ArtifactError::Io(_)));
assert!(err.to_string().contains("nope"));
}
}