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}
104
105impl ShardErrorKind {
106 const fn project(error: &ShardError) -> Self {
107 match error {
108 ShardError::QueueFull { .. } => Self::QueueFull,
109 ShardError::RequestSizeOverflow { .. } => Self::RequestSizeOverflow,
110 ShardError::ActorUnavailable { .. } => Self::ActorUnavailable,
111 ShardError::ReplyDisconnected { .. } => Self::ReplyDisconnected,
112 ShardError::OutcomeUnknown { .. } => Self::OutcomeUnknown,
113 ShardError::Wal(_) => Self::Wal,
114 ShardError::Tree(_) => Self::Tree,
115 ShardError::Store(_) => Self::Store,
116 ShardError::Spawn(_) => Self::Spawn,
117 ShardError::ReclaimClose { .. } => Self::ReclaimClose,
118 ShardError::ShutdownStorage { .. } => Self::ShutdownStorage,
119 ShardError::SequenceConflict { .. } => Self::SequenceConflict,
120 ShardError::ReservedSequenceKey => Self::ReservedSequenceKey,
121 ShardError::EmptyAppend => Self::EmptyAppend,
122 ShardError::LeafRecordCorrupt { .. } => Self::LeafRecordCorrupt,
123 ShardError::LeafLogCorrupt { .. } => Self::LeafLogCorrupt,
124 ShardError::LeafLogNotAdmitted => Self::LeafLogNotAdmitted,
125 ShardError::LeafLogTruncate => Self::LeafLogTruncate,
126 ShardError::LeafLogStreamDelete => Self::LeafLogStreamDelete,
127 ShardError::LeafLogTtlWrite => Self::LeafLogTtlWrite,
128 ShardError::LeafLogMergeAdopt => Self::LeafLogMergeAdopt,
129 ShardError::PinWouldGoBack { .. } => Self::PinWouldGoBack,
130 ShardError::PinConflict { .. } => Self::PinConflict,
131 ShardError::PinBeyondExtent { .. } => Self::PinBeyondExtent,
132 ShardError::CasMismatch { .. } => Self::CasMismatch,
133 ShardError::CasHashMismatch { .. } => Self::CasHashMismatch,
134 ShardError::Fenced { .. } => Self::Fenced,
135 ShardError::WriteDuringRootAdvanceEmission => Self::WriteDuringRootAdvanceEmission,
136 }
137 }
138}
139
140#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
141#[serde(deny_unknown_fields)]
142pub struct WireCallback {
143 pub job_index: u64,
144 pub shard: u64,
145 pub pid: u64,
146 pub command: String,
147 pub subscriber_index: u64,
148 pub event: WireAdvance,
149}
150
151#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
152#[serde(deny_unknown_fields)]
153pub struct WireAdvance {
154 pub shard_id: u64,
155 pub prior_root: [u8; 32],
156 pub new_root: [u8; 32],
157 pub advance_gen: u64,
158}
159
160#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
161pub enum DatabaseErrorKind {
162 BatchFailed,
163 BatchShardFailed,
164 RootCallbackPanicked,
165 BatchJobUnknown,
166 TailRepairRefused,
167 DirectoryCreate,
168 ConfigWrite,
169 ConfigRead,
170 ConfigParse,
171 InvalidShardCount,
172 InvalidLongWaitInterval,
173 InvalidQueueByteLimit,
174 ShardQueueFull,
175 ShardRequestSizeOverflow,
176 ShardSpawn,
177 SweepSpawn,
178 ShardError,
179 OutcomeUnknown,
180 SweepError,
181 SyncSchedulerSpawn,
182 SyncSchedulerError,
183 IoError,
184 InvalidSweepInterval,
185 MissingSyncTopology,
186 InvalidSyncInterval,
187 FormatVersionTooNew,
188 ReservedSequenceKey,
189 SequenceConflict,
190 CasMismatch,
191 CasHashMismatch,
192 ConsistencyError,
193 Distribution,
194 LocalCommitFailed,
195 CommittedButOwnerBehind,
196 ElectionLost,
197 ElectionTimeout,
198 Fenced,
199 CasConflict,
200 ClusterMembers,
201 DataDirLocked,
202 LockFileIo,
203 DataDirAlreadyInitialised,
204 LeafLogStore,
205 EphemeralStore,
206 PathWriterRequired,
207 WriteDuringRootAdvanceEmission,
208 DuplicateShardId,
209 ExecutorThreadsInvalid,
210 MissingNodeCacheBudget,
211 InvalidNodeCacheBudget,
212 RetentionRefused,
213 StreamDeleteRefused,
214 RootNotRetained,
215 ReclaimFailed,
216 ExecutorShutdown,
217 MigrationInProgress,
218 InvalidChunkingStamp,
219 MigrationFailed,
220 MigrationRefusedFormat,
221 MigrationRefusedWalTail,
222 MigrationRefusedUncommittedMutations,
223}
224
225impl DatabaseErrorKind {
226 const fn project(error: &DatabaseError) -> Self {
227 match error {
228 DatabaseError::BatchFailed(_) => Self::BatchFailed,
229 DatabaseError::BatchShardFailed { .. } => Self::BatchShardFailed,
230 DatabaseError::RootCallbackPanicked { .. } => Self::RootCallbackPanicked,
231 DatabaseError::BatchJobUnknown { .. } => Self::BatchJobUnknown,
232 DatabaseError::TailRepairRefused { .. } => Self::TailRepairRefused,
233 DatabaseError::DirectoryCreate(_) => Self::DirectoryCreate,
234 DatabaseError::ConfigWrite(_) => Self::ConfigWrite,
235 DatabaseError::ConfigRead(_) => Self::ConfigRead,
236 DatabaseError::ConfigParse(_) => Self::ConfigParse,
237 DatabaseError::InvalidShardCount => Self::InvalidShardCount,
238 DatabaseError::InvalidLongWaitInterval { .. } => Self::InvalidLongWaitInterval,
239 DatabaseError::InvalidQueueByteLimit => Self::InvalidQueueByteLimit,
240 DatabaseError::ShardQueueFull { .. } => Self::ShardQueueFull,
241 DatabaseError::ShardRequestSizeOverflow { .. } => Self::ShardRequestSizeOverflow,
242 DatabaseError::ShardSpawn(_) => Self::ShardSpawn,
243 DatabaseError::SweepSpawn(_) => Self::SweepSpawn,
244 DatabaseError::ShardError(_) => Self::ShardError,
245 DatabaseError::OutcomeUnknown { .. } => Self::OutcomeUnknown,
246 DatabaseError::SweepError(_) => Self::SweepError,
247 DatabaseError::SyncSchedulerSpawn(_) => Self::SyncSchedulerSpawn,
248 DatabaseError::SyncSchedulerError(_) => Self::SyncSchedulerError,
249 DatabaseError::IoError(_) => Self::IoError,
250 DatabaseError::InvalidSweepInterval => Self::InvalidSweepInterval,
251 DatabaseError::MissingSyncTopology => Self::MissingSyncTopology,
252 DatabaseError::InvalidSyncInterval => Self::InvalidSyncInterval,
253 DatabaseError::FormatVersionTooNew { .. } => Self::FormatVersionTooNew,
254 DatabaseError::ReservedSequenceKey => Self::ReservedSequenceKey,
255 DatabaseError::SequenceConflict { .. } => Self::SequenceConflict,
256 DatabaseError::CasMismatch { .. } => Self::CasMismatch,
257 DatabaseError::CasHashMismatch { .. } => Self::CasHashMismatch,
258 DatabaseError::ConsistencyError(_) => Self::ConsistencyError,
259 DatabaseError::Distribution(_) => Self::Distribution,
260 DatabaseError::LocalCommitFailed(_) => Self::LocalCommitFailed,
261 DatabaseError::CommittedButOwnerBehind { .. } => Self::CommittedButOwnerBehind,
262 DatabaseError::ElectionLost { .. } => Self::ElectionLost,
263 DatabaseError::ElectionTimeout { .. } => Self::ElectionTimeout,
264 DatabaseError::Fenced { .. } => Self::Fenced,
265 DatabaseError::CasConflict { .. } => Self::CasConflict,
266 DatabaseError::ClusterMembers(_) => Self::ClusterMembers,
267 DatabaseError::DataDirLocked { .. } => Self::DataDirLocked,
268 DatabaseError::LockFileIo { .. } => Self::LockFileIo,
269 DatabaseError::DataDirAlreadyInitialised { .. } => Self::DataDirAlreadyInitialised,
270 DatabaseError::LeafLogStore { .. } => Self::LeafLogStore,
271 DatabaseError::EphemeralStore { .. } => Self::EphemeralStore,
272 DatabaseError::PathWriterRequired { .. } => Self::PathWriterRequired,
273 DatabaseError::WriteDuringRootAdvanceEmission => Self::WriteDuringRootAdvanceEmission,
274 DatabaseError::DuplicateShardId { .. } => Self::DuplicateShardId,
275 DatabaseError::ExecutorThreadsInvalid(_) => Self::ExecutorThreadsInvalid,
276 DatabaseError::MissingNodeCacheBudget => Self::MissingNodeCacheBudget,
277 DatabaseError::InvalidNodeCacheBudget(_) => Self::InvalidNodeCacheBudget,
278 DatabaseError::RetentionRefused(_) => Self::RetentionRefused,
279 DatabaseError::StreamDeleteRefused(_) => Self::StreamDeleteRefused,
280 DatabaseError::RootNotRetained { .. } => Self::RootNotRetained,
281 DatabaseError::ReclaimFailed { .. } => Self::ReclaimFailed,
282 DatabaseError::ExecutorShutdown => Self::ExecutorShutdown,
283 DatabaseError::MigrationInProgress { .. } => Self::MigrationInProgress,
284 DatabaseError::InvalidChunkingStamp(_) => Self::InvalidChunkingStamp,
285 DatabaseError::MigrationFailed(_) => Self::MigrationFailed,
286 DatabaseError::MigrationRefusedFormat { .. } => Self::MigrationRefusedFormat,
287 DatabaseError::MigrationRefusedWalTail { .. } => Self::MigrationRefusedWalTail,
288 DatabaseError::MigrationRefusedUncommittedMutations { .. } => {
289 Self::MigrationRefusedUncommittedMutations
290 }
291 }
292 }
293}
294
295fn index(value: usize) -> Result<u64, ReceiptCodecError> {
296 u64::try_from(value).map_err(|_| ReceiptCodecError::IndexOverflow)
297}
298
299fn sequences(values: &[(Vec<u8>, u64)]) -> Result<Vec<WireSequence>, ReceiptCodecError> {
300 let mut result = Vec::new();
301 result
302 .try_reserve_exact(values.len())
303 .map_err(ReceiptCodecError::allocation)?;
304 for (key, sequence) in values {
305 result.push(WireSequence {
306 key_hex: encode_hex(key)?,
307 sequence: *sequence,
308 });
309 }
310 Ok(result)
311}
312
313impl WireReceipt {
314 pub fn project(receipt: &BatchReceipt) -> Result<Self, ReceiptCodecError> {
320 let mut outcomes = Vec::new();
321 outcomes
322 .try_reserve_exact(receipt.outcomes().len())
323 .map_err(ReceiptCodecError::allocation)?;
324 for job in receipt.outcomes() {
325 let outcome = match &job.outcome {
326 BatchOutcome::Committed {
327 root,
328 callback_failure,
329 } => WireOutcome::Committed {
330 root: *root.as_bytes(),
331 callback_failure: callback_failure
332 .as_deref()
333 .map(WireCause::project)
334 .transpose()?,
335 },
336 BatchOutcome::SequenceRead { sequences: values } => WireOutcome::SequenceRead {
337 sequences: sequences(values)?,
338 },
339 BatchOutcome::RangeRead {
340 sequences: values,
341 hit_limit,
342 invalid_outside_range,
343 } => WireOutcome::RangeRead {
344 sequences: sequences(values)?,
345 hit_limit: *hit_limit,
346 invalid_outside_range: index(*invalid_outside_range)?,
347 },
348 BatchOutcome::Failed { cause } => WireOutcome::Failed {
349 cause: WireCause::project(cause)?,
350 },
351 BatchOutcome::Unknown { cause } => WireOutcome::Unknown {
352 cause: WireCause::project(cause)?,
353 },
354 };
355 outcomes.push(WireJobOutcome {
356 job_index: index(job.job_index)?,
357 shard: index(job.shard)?,
358 outcome,
359 });
360 }
361 Ok(Self {
362 outcomes,
363 submit_error: receipt
364 .submit_error()
365 .map(|cause| WireCause::project(cause))
366 .transpose()?,
367 first_error: receipt.first_error().map(WireCause::project).transpose()?,
368 })
369 }
370}
371
372impl WireCause {
373 pub fn project(error: &DatabaseError) -> Result<Self, ReceiptCodecError> {
379 let kind = DatabaseErrorKind::project(error);
380 let shard = if let DatabaseError::BatchShardFailed { cause } = error {
381 Some(WireShardCause {
382 kind: ShardErrorKind::project(cause),
383 message: cause.to_string(),
384 })
385 } else {
386 None
387 };
388 let callback = if let DatabaseError::RootCallbackPanicked { cause } = error {
389 let identity = cause.identity();
390 let event = cause.event();
391 Some(WireCallback {
392 job_index: index(identity.job_index)?,
393 shard: index(identity.shard)?,
394 pid: identity.pid,
395 command: identity.command.to_owned(),
396 subscriber_index: index(cause.subscriber_index())?,
397 event: WireAdvance {
398 shard_id: index(event.shard_id)?,
399 prior_root: *event.prior_root.as_bytes(),
400 new_root: *event.new_root.as_bytes(),
401 advance_gen: event.advance_gen,
402 },
403 })
404 } else {
405 None
406 };
407 let (batch_receipt, first_error) = if let DatabaseError::BatchFailed(failure) = error {
408 (
409 Some(Box::new(WireReceipt::project(failure.receipt())?)),
410 Some(Box::new(Self::project(failure.first_error())?)),
411 )
412 } else {
413 (None, None)
414 };
415 Ok(Self {
416 kind,
417 message: error.to_string(),
418 shard,
419 callback,
420 batch_receipt,
421 first_error,
422 })
423 }
424}
425
426fn read_key_hex<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<String, D::Error> {
427 let key = String::deserialize(deserializer)?;
428 if !key.len().is_multiple_of(2) || !key.bytes().all(|byte| byte.is_ascii_hexdigit()) {
429 return Err(serde::de::Error::custom("receipt_read_key_hex_invalid"));
430 }
431 Ok(key)
432}