Skip to main content

boatramp_core/
error.rs

1//! Error types shared across storage backends, the KV layer, and deploys.
2
3// Config parsing/compiling errors live in the shared `boatramp-types` crate;
4// re-exported so `boatramp_core::error::ConfigError` is unchanged.
5pub use boatramp_types::error::ConfigError;
6
7/// Errors that can occur while interacting with a [`crate::Storage`] backend.
8#[derive(Debug, thiserror::Error)]
9pub enum StorageError {
10    /// The requested key does not exist.
11    #[error("object not found: {0}")]
12    NotFound(String),
13
14    /// The key was rejected (e.g. a path-traversal attempt).
15    #[error("invalid key: {0}")]
16    InvalidKey(String),
17
18    /// The backend does not support this operation yet.
19    #[error("unsupported operation: {0}")]
20    Unsupported(String),
21
22    /// An underlying I/O error.
23    #[error("i/o error: {0}")]
24    Io(#[from] std::io::Error),
25
26    /// A backend-specific error.
27    #[error("backend error: {0}")]
28    Backend(String),
29}
30
31impl StorageError {
32    /// Convenience constructor for [`StorageError::Unsupported`].
33    pub fn unsupported(msg: impl Into<String>) -> Self {
34        Self::Unsupported(msg.into())
35    }
36
37    /// Convenience constructor for [`StorageError::Backend`].
38    pub fn backend(msg: impl Into<String>) -> Self {
39        Self::Backend(msg.into())
40    }
41}
42
43/// Errors from a [`crate::kv::KvStore`] backend.
44#[derive(Debug, thiserror::Error)]
45pub enum KvError {
46    /// An underlying I/O error.
47    #[error("kv i/o error: {0}")]
48    Io(#[from] std::io::Error),
49
50    /// A backend-specific error.
51    #[error("kv backend error: {0}")]
52    Backend(String),
53}
54
55impl KvError {
56    /// Convenience constructor for [`KvError::Backend`].
57    pub fn backend(msg: impl Into<String>) -> Self {
58        Self::Backend(msg.into())
59    }
60}
61
62/// Errors from the content-addressed deploy layer ([`crate::deploy`]).
63#[derive(Debug, thiserror::Error)]
64pub enum DeployError {
65    /// A blob-storage error.
66    #[error(transparent)]
67    Storage(#[from] StorageError),
68
69    /// A KV (manifest/pointer) error.
70    #[error(transparent)]
71    Kv(#[from] KvError),
72
73    /// (De)serialization of a manifest failed.
74    #[error("manifest serialization error: {0}")]
75    Serde(String),
76
77    /// A referenced deployment, site, or path was not found.
78    #[error("not found: {0}")]
79    NotFound(String),
80
81    /// An uploaded blob did not hash to the key it was stored under.
82    #[error("blob hash mismatch: expected {expected}, got {actual}")]
83    HashMismatch {
84        /// The hash the blob was supposed to have.
85        expected: String,
86        /// The hash actually computed from the bytes.
87        actual: String,
88    },
89
90    /// A deployment cannot be activated because some blobs are missing.
91    #[error("deployment incomplete: {} blob(s) still missing", .0.len())]
92    Incomplete(Vec<String>),
93
94    /// A deployment-id prefix matched more than one deployment.
95    #[error("ambiguous deployment id prefix: {0}")]
96    Ambiguous(String),
97
98    /// A host is already claimed by a different site — refusing to overwrite the
99    /// routing index would let one site hijack another's domain. Surfaced as a
100    /// `409 Conflict`.
101    #[error("conflict: {0}")]
102    Conflict(String),
103}
104
105impl From<serde_json::Error> for DeployError {
106    fn from(err: serde_json::Error) -> Self {
107        Self::Serde(err.to_string())
108    }
109}
110
111impl From<ConfigError> for DeployError {
112    fn from(err: ConfigError) -> Self {
113        Self::Serde(err.to_string())
114    }
115}