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 /// A submitted config failed a content-validation check before storage (e.g. a project
105 /// tenancy schema with an empty public predicate that would defeat the target-read
106 /// confinement). Surfaced as a `400 Bad Request` — the write is refused, not stored.
107 #[error("invalid config: {0}")]
108 Invalid(String),
109}
110
111impl From<serde_json::Error> for DeployError {
112 fn from(err: serde_json::Error) -> Self {
113 Self::Serde(err.to_string())
114 }
115}
116
117impl From<ConfigError> for DeployError {
118 fn from(err: ConfigError) -> Self {
119 Self::Serde(err.to_string())
120 }
121}