Skip to main content

ironflow_artifacts/
error.rs

1//! Error type for blob storage operations.
2
3use thiserror::Error;
4
5/// Errors produced by [`BlobStore`](crate::blob_store::BlobStore) operations.
6///
7/// # Examples
8///
9/// ```
10/// use ironflow_artifacts::error::ArtifactError;
11///
12/// let err = ArtifactError::NotFound("artifacts/a/b/c".to_string());
13/// assert!(err.to_string().contains("not found"));
14/// ```
15#[derive(Debug, Error)]
16pub enum ArtifactError {
17    /// No blob is stored under this key.
18    #[error("artifact not found: {0}")]
19    NotFound(String),
20
21    /// The artifact name violates the naming rules.
22    ///
23    /// See [`validate_artifact_name`](crate::name::validate_artifact_name).
24    #[error("invalid artifact name {name:?}: {reason}")]
25    InvalidName {
26        /// The rejected name.
27        name: String,
28        /// Why it was rejected.
29        reason: &'static str,
30    },
31
32    /// The storage key is not usable by this backend.
33    ///
34    /// Keys are generated from UUIDs, so this signals a programming error
35    /// rather than bad user input.
36    #[error("invalid storage key {key:?}: {reason}")]
37    InvalidKey {
38        /// The rejected key.
39        key: String,
40        /// Why it was rejected.
41        reason: &'static str,
42    },
43
44    /// The payload exceeded the configured size limit.
45    #[error("artifact exceeds the {limit_bytes} byte limit")]
46    TooLarge {
47        /// The configured limit, in bytes.
48        limit_bytes: u64,
49    },
50
51    /// An I/O error from the backing storage.
52    #[error("artifact storage io error: {0}")]
53    Io(String),
54}
55
56impl From<std::io::Error> for ArtifactError {
57    fn from(err: std::io::Error) -> Self {
58        ArtifactError::Io(err.to_string())
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use std::io::{Error as IoError, ErrorKind};
65
66    use super::*;
67
68    #[test]
69    fn not_found_display() {
70        let err = ArtifactError::NotFound("a/b".to_string());
71        assert_eq!(err.to_string(), "artifact not found: a/b");
72    }
73
74    #[test]
75    fn invalid_name_display_quotes_the_name() {
76        let err = ArtifactError::InvalidName {
77            name: "../etc/passwd".to_string(),
78            reason: "contains a forbidden character",
79        };
80        assert!(err.to_string().contains("\"../etc/passwd\""));
81        assert!(err.to_string().contains("forbidden character"));
82    }
83
84    #[test]
85    fn invalid_key_display() {
86        let err = ArtifactError::InvalidKey {
87            key: "/abs".to_string(),
88            reason: "must be relative",
89        };
90        assert!(err.to_string().contains("must be relative"));
91    }
92
93    #[test]
94    fn too_large_display_shows_the_limit() {
95        let err = ArtifactError::TooLarge { limit_bytes: 1024 };
96        assert!(err.to_string().contains("1024"));
97    }
98
99    #[test]
100    fn io_error_converts() {
101        let io = IoError::new(ErrorKind::PermissionDenied, "nope");
102        let err = ArtifactError::from(io);
103        assert!(matches!(err, ArtifactError::Io(_)));
104        assert!(err.to_string().contains("nope"));
105    }
106}