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 #[error("loro schema snapshot exceeds byte limit: {actual} > {limit}")]
118 LoroSnapshotTooLarge { limit: usize, actual: usize },
119
120 #[error("loro schema snapshot metadata is malformed: {detail}")]
122 LoroSnapshotMalformed { detail: String },
123
124 #[error("loro schema snapshot has too many operations: {actual} > {limit}")]
126 LoroSnapshotOperationLimitExceeded { limit: usize, actual: usize },
127}
128
129impl ArrayError {
130 pub fn array_name(&self) -> &str {
132 match self {
133 ArrayError::InvalidSchema { array, .. }
134 | ArrayError::InvalidDim { array, .. }
135 | ArrayError::InvalidAttr { array, .. }
136 | ArrayError::InvalidTileExtents { array, .. }
137 | ArrayError::CoordArityMismatch { array, .. }
138 | ArrayError::CoordOutOfDomain { array, .. }
139 | ArrayError::CellTypeMismatch { array, .. } => array,
140 ArrayError::SegmentCorruption { .. }
141 | ArrayError::UnsupportedFormat { .. }
142 | ArrayError::UnsupportedSegmentFormat { .. }
143 | ArrayError::InvalidReplicaId { .. }
144 | ArrayError::InvalidHlc { .. }
145 | ArrayError::HlcLockPoisoned
146 | ArrayError::InvalidOp { .. }
147 | ArrayError::LoroError { .. }
148 | ArrayError::LoroSnapshotVersionMismatch { .. }
149 | ArrayError::LoroSnapshotTooLarge { .. }
150 | ArrayError::LoroSnapshotMalformed { .. }
151 | ArrayError::LoroSnapshotOperationLimitExceeded { .. }
152 | ArrayError::MissingKek
153 | ArrayError::KekRequired
154 | ArrayError::EncryptionFailed { .. }
155 | ArrayError::DecryptionFailed { .. } => "",
156 }
157 }
158}
159
160impl From<ArrayError> for NodeDbError {
161 fn from(e: ArrayError) -> Self {
162 let array = e.array_name().to_string();
163 NodeDbError::array(array, e)
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170
171 #[test]
172 fn array_error_carries_name() {
173 let e = ArrayError::CoordArityMismatch {
174 array: "genome".into(),
175 expected: 4,
176 got: 3,
177 };
178 assert_eq!(e.array_name(), "genome");
179 }
180
181 #[test]
182 fn array_error_converts_to_nodedb_error() {
183 let e = ArrayError::InvalidSchema {
184 array: "vcf".into(),
185 detail: "no dimensions".into(),
186 };
187 let n: NodeDbError = e.into();
188 assert!(n.to_string().contains("NDB-1300"));
189 assert!(n.to_string().contains("vcf"));
190 }
191
192 #[test]
193 fn array_error_round_trips_through_details() {
194 let e = ArrayError::InvalidDim {
195 array: "raster".into(),
196 dim: "lat".into(),
197 detail: "lo > hi".into(),
198 };
199 let n: NodeDbError = e.into();
200 let json = serde_json::to_value(&n).unwrap();
201 assert_eq!(json["details"]["kind"], "array");
202 assert_eq!(json["details"]["array"], "raster");
203 }
204}