1use nodedb_types::NodeDbError;
10use thiserror::Error;
11
12pub type ArrayResult<T> = std::result::Result<T, ArrayError>;
14
15#[derive(Debug, Error)]
21pub enum ArrayError {
22 #[error("schema validation failed for '{array}': {detail}")]
23 InvalidSchema { array: String, detail: String },
24
25 #[error("dimension '{dim}' rejected on '{array}': {detail}")]
26 InvalidDim {
27 array: String,
28 dim: String,
29 detail: String,
30 },
31
32 #[error("attribute '{attr}' rejected on '{array}': {detail}")]
33 InvalidAttr {
34 array: String,
35 attr: String,
36 detail: String,
37 },
38
39 #[error("tile-extent vector rejected on '{array}': {detail}")]
40 InvalidTileExtents { array: String, detail: String },
41
42 #[error("coordinate arity {got} does not match schema arity {expected} on '{array}'")]
43 CoordArityMismatch {
44 array: String,
45 expected: usize,
46 got: usize,
47 },
48
49 #[error("coordinate out of domain on '{array}' dim '{dim}': {detail}")]
50 CoordOutOfDomain {
51 array: String,
52 dim: String,
53 detail: String,
54 },
55
56 #[error("cell-value type mismatch on '{array}' attr '{attr}': {detail}")]
57 CellTypeMismatch {
58 array: String,
59 attr: String,
60 detail: String,
61 },
62
63 #[error("segment corruption: {detail}")]
64 SegmentCorruption { detail: String },
65
66 #[error("unsupported WAL format version: {version}")]
67 UnsupportedFormat { version: u8 },
68
69 #[error("unsupported segment format version: {version}")]
70 UnsupportedSegmentFormat { version: u16 },
71
72 #[error("invalid replica_id: {detail}")]
74 InvalidReplicaId { detail: String },
75
76 #[error("invalid HLC: {detail}")]
78 InvalidHlc { detail: String },
79
80 #[error("HLC generator lock poisoned")]
82 HlcLockPoisoned,
83
84 #[error("invalid array op: {detail}")]
86 InvalidOp { detail: String },
87
88 #[error("loro error: {detail}")]
90 LoroError { detail: String },
91
92 #[error("encrypted segment requires a KEK but none was configured")]
94 MissingKek,
95
96 #[error("plaintext segment rejected — encryption is enforced by the configured KEK")]
99 KekRequired,
100
101 #[error("segment encryption failed: {detail}")]
103 EncryptionFailed { detail: String },
104
105 #[error("segment decryption failed: {detail}")]
107 DecryptionFailed { detail: String },
108
109 #[error("loro snapshot version mismatch: expected {expected}, got {got}")]
114 LoroSnapshotVersionMismatch { expected: u8, got: u8 },
115}
116
117impl ArrayError {
118 pub fn array_name(&self) -> &str {
120 match self {
121 ArrayError::InvalidSchema { array, .. }
122 | ArrayError::InvalidDim { array, .. }
123 | ArrayError::InvalidAttr { array, .. }
124 | ArrayError::InvalidTileExtents { array, .. }
125 | ArrayError::CoordArityMismatch { array, .. }
126 | ArrayError::CoordOutOfDomain { array, .. }
127 | ArrayError::CellTypeMismatch { array, .. } => array,
128 ArrayError::SegmentCorruption { .. }
129 | ArrayError::UnsupportedFormat { .. }
130 | ArrayError::UnsupportedSegmentFormat { .. }
131 | ArrayError::InvalidReplicaId { .. }
132 | ArrayError::InvalidHlc { .. }
133 | ArrayError::HlcLockPoisoned
134 | ArrayError::InvalidOp { .. }
135 | ArrayError::LoroError { .. }
136 | ArrayError::LoroSnapshotVersionMismatch { .. }
137 | ArrayError::MissingKek
138 | ArrayError::KekRequired
139 | ArrayError::EncryptionFailed { .. }
140 | ArrayError::DecryptionFailed { .. } => "",
141 }
142 }
143}
144
145impl From<ArrayError> for NodeDbError {
146 fn from(e: ArrayError) -> Self {
147 let array = e.array_name().to_string();
148 NodeDbError::array(array, e)
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn array_error_carries_name() {
158 let e = ArrayError::CoordArityMismatch {
159 array: "genome".into(),
160 expected: 4,
161 got: 3,
162 };
163 assert_eq!(e.array_name(), "genome");
164 }
165
166 #[test]
167 fn array_error_converts_to_nodedb_error() {
168 let e = ArrayError::InvalidSchema {
169 array: "vcf".into(),
170 detail: "no dimensions".into(),
171 };
172 let n: NodeDbError = e.into();
173 assert!(n.to_string().contains("NDB-1300"));
174 assert!(n.to_string().contains("vcf"));
175 }
176
177 #[test]
178 fn array_error_round_trips_through_details() {
179 let e = ArrayError::InvalidDim {
180 array: "raster".into(),
181 dim: "lat".into(),
182 detail: "lo > hi".into(),
183 };
184 let n: NodeDbError = e.into();
185 let json = serde_json::to_value(&n).unwrap();
186 assert_eq!(json["details"]["kind"], "array");
187 assert_eq!(json["details"]["array"], "raster");
188 }
189}