1use haematite::{BatchOutcome, BatchReceipt, DatabaseError, ShardError};
4use serde::{Deserialize, Serialize};
5
6use super::receipt_fragments::{ReceiptCodecError, encode_hex};
7
8#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(deny_unknown_fields)]
10pub struct WireReceipt {
11 pub outcomes: Vec<WireJobOutcome>,
12 pub submit_error: Option<WireCause>,
13 pub first_error: Option<WireCause>,
14}
15
16#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
17#[serde(deny_unknown_fields)]
18pub struct WireJobOutcome {
19 pub job_index: u64,
20 pub shard: u64,
21 pub outcome: WireOutcome,
22}
23
24#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
25#[serde(tag = "kind", deny_unknown_fields)]
26pub enum WireOutcome {
27 Committed {
28 root: [u8; 32],
29 callback_failure: Option<WireCause>,
30 },
31 SequenceRead {
32 sequences: Vec<WireSequence>,
33 },
34 RangeRead {
35 sequences: Vec<WireSequence>,
36 hit_limit: bool,
37 invalid_outside_range: u64,
38 },
39 Failed {
40 cause: WireCause,
41 },
42 Unknown {
43 cause: WireCause,
44 },
45}
46
47#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
48#[serde(deny_unknown_fields)]
49pub struct WireSequence {
50 #[serde(deserialize_with = "read_key_hex")]
51 pub key_hex: String,
52 pub sequence: u64,
53}
54
55#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
56#[serde(deny_unknown_fields)]
57pub struct WireCause {
58 pub kind: DatabaseErrorKind,
59 pub message: String,
60 pub shard: Option<WireShardCause>,
61 pub callback: Option<WireCallback>,
62 pub batch_receipt: Option<Box<WireReceipt>>,
63 pub first_error: Option<Box<Self>>,
64}
65
66#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
67#[serde(deny_unknown_fields)]
68pub struct WireShardCause {
69 pub kind: ShardErrorKind,
70 pub message: String,
71}
72
73#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
74pub enum ShardErrorKind {
75 QueueFull,
76 RequestSizeOverflow,
77 ActorUnavailable,
78 ReplyDisconnected,
79 OutcomeUnknown,
80 Wal,
81 Tree,
82 Store,
83 Spawn,
84 ReclaimClose,
85 ShutdownStorage,
86 SequenceConflict,
87 ReservedSequenceKey,
88 EmptyAppend,
89 LeafRecordCorrupt,
90 LeafLogCorrupt,
91 LeafLogNotAdmitted,
92 LeafLogTruncate,
93 LeafLogStreamDelete,
94 LeafLogTtlWrite,
95 LeafLogMergeAdopt,
96 PinWouldGoBack,
97 PinConflict,
98 PinBeyondExtent,
99 CasMismatch,
100 CasHashMismatch,
101 Fenced,
102 WriteDuringRootAdvanceEmission,
103 DatabaseClosed,
104}
105
106impl ShardErrorKind {
107 const fn project(error: &ShardError) -> Self {
108 match error {
109 ShardError::QueueFull { .. } => Self::QueueFull,
110 ShardError::RequestSizeOverflow { .. } => Self::RequestSizeOverflow,
111 ShardError::ActorUnavailable { .. } => Self::ActorUnavailable,
112 ShardError::ReplyDisconnected { .. } => Self::ReplyDisconnected,
113 ShardError::OutcomeUnknown { .. } => Self::OutcomeUnknown,
114 ShardError::Wal(_) => Self::Wal,
115 ShardError::Tree(_) => Self::Tree,
116 ShardError::Store(_) => Self::Store,
117 ShardError::Spawn(_) => Self::Spawn,
118 ShardError::ReclaimClose { .. } => Self::ReclaimClose,
119 ShardError::ShutdownStorage { .. } => Self::ShutdownStorage,
120 ShardError::SequenceConflict { .. } => Self::SequenceConflict,
121 ShardError::ReservedSequenceKey => Self::ReservedSequenceKey,
122 ShardError::EmptyAppend => Self::EmptyAppend,
123 ShardError::LeafRecordCorrupt { .. } => Self::LeafRecordCorrupt,
124 ShardError::LeafLogCorrupt { .. } => Self::LeafLogCorrupt,
125 ShardError::LeafLogNotAdmitted => Self::LeafLogNotAdmitted,
126 ShardError::LeafLogTruncate => Self::LeafLogTruncate,
127 ShardError::LeafLogStreamDelete => Self::LeafLogStreamDelete,
128 ShardError::LeafLogTtlWrite => Self::LeafLogTtlWrite,
129 ShardError::LeafLogMergeAdopt => Self::LeafLogMergeAdopt,
130 ShardError::PinWouldGoBack { .. } => Self::PinWouldGoBack,
131 ShardError::PinConflict { .. } => Self::PinConflict,
132 ShardError::PinBeyondExtent { .. } => Self::PinBeyondExtent,
133 ShardError::CasMismatch { .. } => Self::CasMismatch,
134 ShardError::CasHashMismatch { .. } => Self::CasHashMismatch,
135 ShardError::Fenced { .. } => Self::Fenced,
136 ShardError::WriteDuringRootAdvanceEmission => Self::WriteDuringRootAdvanceEmission,
137 ShardError::DatabaseClosed => Self::DatabaseClosed,
138 }
139 }
140}
141
142#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
143#[serde(deny_unknown_fields)]
144pub struct WireCallback {
145 pub job_index: u64,
146 pub shard: u64,
147 pub pid: u64,
148 pub command: String,
149 pub subscriber_index: u64,
150 pub event: WireAdvance,
151}
152
153#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
154#[serde(deny_unknown_fields)]
155pub struct WireAdvance {
156 pub shard_id: u64,
157 pub prior_root: [u8; 32],
158 pub new_root: [u8; 32],
159 pub advance_gen: u64,
160}
161
162#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
163pub enum DatabaseErrorKind {
164 BatchFailed,
165 BatchShardFailed,
166 RootCallbackPanicked,
167 BatchJobUnknown,
168 TailRepairRefused,
169 DirectoryCreate,
170 ConfigWrite,
171 ConfigRead,
172 ConfigParse,
173 InvalidShardCount,
174 InvalidLongWaitInterval,
175 InvalidQueueByteLimit,
176 ShardQueueFull,
177 ShardRequestSizeOverflow,
178 ShardSpawn,
179 SweepSpawn,
180 ShardError,
181 OutcomeUnknown,
182 SweepError,
183 SyncSchedulerSpawn,
184 SyncSchedulerError,
185 IoError,
186 InvalidSweepInterval,
187 MissingSyncTopology,
188 InvalidSyncInterval,
189 FormatVersionTooNew,
190 ReservedSequenceKey,
191 ReservedBranchRefKey,
192 SequenceConflict,
193 CasMismatch,
194 CasHashMismatch,
195 ConsistencyError,
196 Distribution,
197 LocalCommitFailed,
198 CommittedButOwnerBehind,
199 ElectionLost,
200 ElectionTimeout,
201 Fenced,
202 CasConflict,
203 ClusterMembers,
204 DataDirLocked,
205 LockFileIo,
206 DataDirAlreadyInitialised,
207 LeafLogStore,
208 EphemeralStore,
209 PathWriterRequired,
210 WriteDuringRootAdvanceEmission,
211 DuplicateShardId,
212 ExecutorThreadsInvalid,
213 MissingNodeCacheBudget,
214 InvalidNodeCacheBudget,
215 RetentionRefused,
216 StreamDeleteRefused,
217 RootNotRetained,
218 ReclaimFailed,
219 ExecutorShutdown,
220 MigrationInProgress,
221 InvalidChunkingStamp,
222 MigrationFailed,
223 MigrationRefusedFormat,
224 MigrationRefusedWalTail,
225 MigrationRefusedUncommittedMutations,
226 DatabaseClosed,
227}
228
229impl DatabaseErrorKind {
230 const fn project(error: &DatabaseError) -> Self {
231 match error {
232 DatabaseError::BatchFailed(_) => Self::BatchFailed,
233 DatabaseError::BatchShardFailed { .. } => Self::BatchShardFailed,
234 DatabaseError::RootCallbackPanicked { .. } => Self::RootCallbackPanicked,
235 DatabaseError::BatchJobUnknown { .. } => Self::BatchJobUnknown,
236 DatabaseError::TailRepairRefused { .. } => Self::TailRepairRefused,
237 DatabaseError::DirectoryCreate(_) => Self::DirectoryCreate,
238 DatabaseError::ConfigWrite(_) => Self::ConfigWrite,
239 DatabaseError::ConfigRead(_) => Self::ConfigRead,
240 DatabaseError::ConfigParse(_) => Self::ConfigParse,
241 DatabaseError::InvalidShardCount => Self::InvalidShardCount,
242 DatabaseError::InvalidLongWaitInterval { .. } => Self::InvalidLongWaitInterval,
243 DatabaseError::InvalidQueueByteLimit => Self::InvalidQueueByteLimit,
244 DatabaseError::ShardQueueFull { .. } => Self::ShardQueueFull,
245 DatabaseError::ShardRequestSizeOverflow { .. } => Self::ShardRequestSizeOverflow,
246 DatabaseError::ShardSpawn(_) => Self::ShardSpawn,
247 DatabaseError::DatabaseClosed => Self::DatabaseClosed,
248 DatabaseError::SweepSpawn(_) => Self::SweepSpawn,
249 DatabaseError::ShardError(_) => Self::ShardError,
250 DatabaseError::OutcomeUnknown { .. } => Self::OutcomeUnknown,
251 DatabaseError::SweepError(_) => Self::SweepError,
252 DatabaseError::SyncSchedulerSpawn(_) => Self::SyncSchedulerSpawn,
253 DatabaseError::SyncSchedulerError(_) => Self::SyncSchedulerError,
254 DatabaseError::IoError(_) => Self::IoError,
255 DatabaseError::InvalidSweepInterval => Self::InvalidSweepInterval,
256 DatabaseError::MissingSyncTopology => Self::MissingSyncTopology,
257 DatabaseError::InvalidSyncInterval => Self::InvalidSyncInterval,
258 DatabaseError::FormatVersionTooNew { .. } => Self::FormatVersionTooNew,
259 DatabaseError::ReservedSequenceKey => Self::ReservedSequenceKey,
260 DatabaseError::ReservedBranchRefKey => Self::ReservedBranchRefKey,
261 DatabaseError::SequenceConflict { .. } => Self::SequenceConflict,
262 DatabaseError::CasMismatch { .. } => Self::CasMismatch,
263 DatabaseError::CasHashMismatch { .. } => Self::CasHashMismatch,
264 DatabaseError::ConsistencyError(_) => Self::ConsistencyError,
265 DatabaseError::Distribution(_) => Self::Distribution,
266 DatabaseError::LocalCommitFailed(_) => Self::LocalCommitFailed,
267 DatabaseError::CommittedButOwnerBehind { .. } => Self::CommittedButOwnerBehind,
268 DatabaseError::ElectionLost { .. } => Self::ElectionLost,
269 DatabaseError::ElectionTimeout { .. } => Self::ElectionTimeout,
270 DatabaseError::Fenced { .. } => Self::Fenced,
271 DatabaseError::CasConflict { .. } => Self::CasConflict,
272 DatabaseError::ClusterMembers(_) => Self::ClusterMembers,
273 DatabaseError::DataDirLocked { .. } => Self::DataDirLocked,
274 DatabaseError::LockFileIo { .. } => Self::LockFileIo,
275 DatabaseError::DataDirAlreadyInitialised { .. } => Self::DataDirAlreadyInitialised,
276 DatabaseError::LeafLogStore { .. } => Self::LeafLogStore,
277 DatabaseError::EphemeralStore { .. } => Self::EphemeralStore,
278 DatabaseError::PathWriterRequired { .. } => Self::PathWriterRequired,
279 DatabaseError::WriteDuringRootAdvanceEmission => Self::WriteDuringRootAdvanceEmission,
280 DatabaseError::DuplicateShardId { .. } => Self::DuplicateShardId,
281 DatabaseError::ExecutorThreadsInvalid(_) => Self::ExecutorThreadsInvalid,
282 DatabaseError::MissingNodeCacheBudget => Self::MissingNodeCacheBudget,
283 DatabaseError::InvalidNodeCacheBudget(_) => Self::InvalidNodeCacheBudget,
284 DatabaseError::RetentionRefused(_) => Self::RetentionRefused,
285 DatabaseError::StreamDeleteRefused(_) => Self::StreamDeleteRefused,
286 DatabaseError::RootNotRetained { .. } => Self::RootNotRetained,
287 DatabaseError::ReclaimFailed { .. } => Self::ReclaimFailed,
288 DatabaseError::ExecutorShutdown => Self::ExecutorShutdown,
289 DatabaseError::MigrationInProgress { .. } => Self::MigrationInProgress,
290 DatabaseError::InvalidChunkingStamp(_) => Self::InvalidChunkingStamp,
291 DatabaseError::MigrationFailed(_) => Self::MigrationFailed,
292 DatabaseError::MigrationRefusedFormat { .. } => Self::MigrationRefusedFormat,
293 DatabaseError::MigrationRefusedWalTail { .. } => Self::MigrationRefusedWalTail,
294 DatabaseError::MigrationRefusedUncommittedMutations { .. } => {
295 Self::MigrationRefusedUncommittedMutations
296 }
297 }
298 }
299}
300
301fn index(value: usize) -> Result<u64, ReceiptCodecError> {
302 u64::try_from(value).map_err(|_| ReceiptCodecError::IndexOverflow)
303}
304
305fn sequences(values: &[(Vec<u8>, u64)]) -> Result<Vec<WireSequence>, ReceiptCodecError> {
306 let mut result = Vec::new();
307 result
308 .try_reserve_exact(values.len())
309 .map_err(ReceiptCodecError::allocation)?;
310 for (key, sequence) in values {
311 result.push(WireSequence {
312 key_hex: encode_hex(key)?,
313 sequence: *sequence,
314 });
315 }
316 Ok(result)
317}
318
319impl WireReceipt {
320 pub fn project(receipt: &BatchReceipt) -> Result<Self, ReceiptCodecError> {
326 let mut outcomes = Vec::new();
327 outcomes
328 .try_reserve_exact(receipt.outcomes().len())
329 .map_err(ReceiptCodecError::allocation)?;
330 for job in receipt.outcomes() {
331 let outcome = match &job.outcome {
332 BatchOutcome::Committed {
333 root,
334 callback_failure,
335 } => WireOutcome::Committed {
336 root: *root.as_bytes(),
337 callback_failure: callback_failure
338 .as_deref()
339 .map(WireCause::project)
340 .transpose()?,
341 },
342 BatchOutcome::SequenceRead { sequences: values } => WireOutcome::SequenceRead {
343 sequences: sequences(values)?,
344 },
345 BatchOutcome::RangeRead {
346 sequences: values,
347 hit_limit,
348 invalid_outside_range,
349 } => WireOutcome::RangeRead {
350 sequences: sequences(values)?,
351 hit_limit: *hit_limit,
352 invalid_outside_range: index(*invalid_outside_range)?,
353 },
354 BatchOutcome::Failed { cause } => WireOutcome::Failed {
355 cause: WireCause::project(cause)?,
356 },
357 BatchOutcome::Unknown { cause } => WireOutcome::Unknown {
358 cause: WireCause::project(cause)?,
359 },
360 };
361 outcomes.push(WireJobOutcome {
362 job_index: index(job.job_index)?,
363 shard: index(job.shard)?,
364 outcome,
365 });
366 }
367 Ok(Self {
368 outcomes,
369 submit_error: receipt
370 .submit_error()
371 .map(|cause| WireCause::project(cause))
372 .transpose()?,
373 first_error: receipt.first_error().map(WireCause::project).transpose()?,
374 })
375 }
376}
377
378impl WireCause {
379 pub fn project(error: &DatabaseError) -> Result<Self, ReceiptCodecError> {
385 let kind = DatabaseErrorKind::project(error);
386 let shard = if let DatabaseError::BatchShardFailed { cause } = error {
387 Some(WireShardCause {
388 kind: ShardErrorKind::project(cause),
389 message: cause.to_string(),
390 })
391 } else {
392 None
393 };
394 let callback = if let DatabaseError::RootCallbackPanicked { cause } = error {
395 let identity = cause.identity();
396 let event = cause.event();
397 Some(WireCallback {
398 job_index: index(identity.job_index)?,
399 shard: index(identity.shard)?,
400 pid: identity.pid,
401 command: identity.command.to_owned(),
402 subscriber_index: index(cause.subscriber_index())?,
403 event: WireAdvance {
404 shard_id: index(event.shard_id)?,
405 prior_root: *event.prior_root.as_bytes(),
406 new_root: *event.new_root.as_bytes(),
407 advance_gen: event.advance_gen,
408 },
409 })
410 } else {
411 None
412 };
413 let (batch_receipt, first_error) = if let DatabaseError::BatchFailed(failure) = error {
414 (
415 Some(Box::new(WireReceipt::project(failure.receipt())?)),
416 Some(Box::new(Self::project(failure.first_error())?)),
417 )
418 } else {
419 (None, None)
420 };
421 Ok(Self {
422 kind,
423 message: error.to_string(),
424 shard,
425 callback,
426 batch_receipt,
427 first_error,
428 })
429 }
430}
431
432fn read_key_hex<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<String, D::Error> {
433 let key = String::deserialize(deserializer)?;
434 if !key.len().is_multiple_of(2) || !key.bytes().all(|byte| byte.is_ascii_hexdigit()) {
435 return Err(serde::de::Error::custom("receipt_read_key_hex_invalid"));
436 }
437 Ok(key)
438}