1use super::cid::Cid;
4use serde::{Deserialize, Serialize};
5
6#[derive(Clone, Debug, PartialEq)]
12pub enum Mutation {
13 Upsert { key: Vec<u8>, val: Vec<u8> },
15 Delete { key: Vec<u8> },
17}
18
19impl Mutation {
20 pub fn key(&self) -> &[u8] {
23 match self {
24 Mutation::Upsert { key, .. } => key,
25 Mutation::Delete { key } => key,
26 }
27 }
28
29 pub fn is_delete(&self) -> bool {
31 matches!(self, Mutation::Delete { .. })
32 }
33}
34
35#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
40pub enum Diff {
41 Added { key: Vec<u8>, val: Vec<u8> },
43 Removed { key: Vec<u8>, val: Vec<u8> },
45 Changed {
47 key: Vec<u8>,
48 old: Vec<u8>,
49 new: Vec<u8>,
50 },
51}
52
53impl Diff {
54 pub fn key(&self) -> &[u8] {
56 match self {
57 Diff::Added { key, .. } | Diff::Removed { key, .. } | Diff::Changed { key, .. } => key,
58 }
59 }
60}
61
62#[derive(Clone, Debug)]
67pub struct Conflict {
68 pub key: Vec<u8>,
70 pub base: Option<Vec<u8>>,
72 pub left: Option<Vec<u8>>,
74 pub right: Option<Vec<u8>>,
76}
77
78#[derive(Clone, Debug, PartialEq, Eq)]
83pub enum Resolution {
84 Value(Vec<u8>),
86 Delete,
88 Unresolved,
90}
91
92impl Resolution {
93 pub fn value(value: impl Into<Vec<u8>>) -> Self {
95 Self::Value(value.into())
96 }
97
98 pub fn delete() -> Self {
100 Self::Delete
101 }
102
103 pub fn unresolved() -> Self {
105 Self::Unresolved
106 }
107}
108
109pub type Resolver = Box<dyn Fn(&Conflict) -> Resolution + Send + Sync>;
149
150pub mod resolver {
152 use super::{Conflict, Resolution};
153
154 pub fn prefer_left(conflict: &Conflict) -> Resolution {
156 match &conflict.left {
157 Some(value) => Resolution::value(value.clone()),
158 None => Resolution::delete(),
159 }
160 }
161
162 pub fn prefer_right(conflict: &Conflict) -> Resolution {
164 match &conflict.right {
165 Some(value) => Resolution::value(value.clone()),
166 None => Resolution::delete(),
167 }
168 }
169
170 pub fn delete_wins(conflict: &Conflict) -> Resolution {
172 if conflict.left.is_none() || conflict.right.is_none() {
173 Resolution::delete()
174 } else {
175 Resolution::unresolved()
176 }
177 }
178
179 pub fn update_wins(conflict: &Conflict) -> Resolution {
181 match (&conflict.left, &conflict.right) {
182 (Some(value), None) | (None, Some(value)) => Resolution::value(value.clone()),
183 _ => Resolution::unresolved(),
184 }
185 }
186}
187
188use super::secondary_index::IndexProjection;
189use super::transaction::TransactionConflict;
190use super::versioned_map::MapVersionId;
191
192#[derive(Clone, Copy, Debug, PartialEq, Eq)]
193pub enum IndexErrorCode {
194 FormatUnsupported,
195 StoreProfileUnsupported,
196 Conflict,
197 Corruption,
198 DefinitionInvalid,
199 RuntimeDefinitionMissing,
200 ManagedWriteRequired,
201 OperationUnsupported,
202 ExtractionFailed,
203 ProjectionInvalid,
204 ResourceLimit,
205 CursorInvalid,
206 BundleInvalid,
207 SnapshotUnavailable,
208}
209
210#[derive(Clone, Copy, Debug, PartialEq, Eq)]
211pub enum RetryAdvice {
212 Never,
213 RetryFreshState,
214 RetryAfter,
215}
216
217impl IndexErrorCode {
218 pub const fn as_str(self) -> &'static str {
219 match self {
220 Self::FormatUnsupported => "format_unsupported",
221 Self::StoreProfileUnsupported => "store_profile_unsupported",
222 Self::Conflict => "conflict",
223 Self::Corruption => "corruption",
224 Self::DefinitionInvalid => "definition_invalid",
225 Self::RuntimeDefinitionMissing => "runtime_definition_missing",
226 Self::ManagedWriteRequired => "managed_write_required",
227 Self::OperationUnsupported => "operation_unsupported",
228 Self::ExtractionFailed => "extraction_failed",
229 Self::ProjectionInvalid => "projection_invalid",
230 Self::ResourceLimit => "resource_limit",
231 Self::CursorInvalid => "cursor_invalid",
232 Self::BundleInvalid => "bundle_invalid",
233 Self::SnapshotUnavailable => "snapshot_unavailable",
234 }
235 }
236}
237
238impl RetryAdvice {
239 pub const fn as_str(self) -> &'static str {
240 match self {
241 Self::Never => "never",
242 Self::RetryFreshState => "retry_fresh_state",
243 Self::RetryAfter => "retry_after",
244 }
245 }
246}
247
248pub enum Error {
250 NotFound(Cid),
252 InvalidNode,
254 InvalidFormat(String),
256 InvalidExecutionConfig { field: &'static str, value: usize },
258 FormatMismatch { expected: Cid, actual: Cid },
260 EntryTooLarge { encoded_bytes: u64, limit: u64 },
262 Deserialize(String),
264 Serialize(String),
266 Store(Box<dyn std::error::Error + Send + Sync>),
268 CidMismatch { expected: Cid, actual: Cid },
270 Conflict(Conflict),
274 BufferFull,
276 InvalidSavepoint,
278 PatchBaseMismatch,
280 InvalidStructuralPatch(String),
282 UnsortedInput { previous: Vec<u8>, next: Vec<u8> },
284 DuplicateMutation { key: Vec<u8> },
286 SpliceConfigMismatch,
288 MissingNamedRoots { names: Vec<Vec<u8>> },
290 InvalidSnapshotBundle(String),
292 UnsupportedTransactions { store: &'static str },
294 UnsupportedIndexedStoreProfile {
296 store: &'static str,
297 required: &'static str,
298 actual: &'static str,
299 },
300 IndexFormatUnsupported,
302 TransactionConflict(Box<TransactionConflict>),
304 InvalidVersionedMap(String),
306 InvalidIndexDefinition { reason: String },
308 IndexRuntimeDefinitionMissing { name: Vec<u8>, generation: u64 },
310 IndexDefinitionMismatch {
312 name: Vec<u8>,
313 persisted: Cid,
314 runtime: Cid,
315 },
316 IndexesRequireIndexedMap {
318 map_id: Vec<u8>,
319 active_indexes: Vec<Vec<u8>>,
320 },
321 IndexOperationUnsupported { operation: &'static str },
323 IndexExtractionFailed {
325 name: Vec<u8>,
326 primary_key: Vec<u8>,
327 reason: String,
328 },
329 IndexProjectionMismatch {
331 name: Vec<u8>,
332 mode: IndexProjection,
333 primary_key: Vec<u8>,
334 },
335 ConflictingIndexProjection {
337 name: Vec<u8>,
338 primary_key: Vec<u8>,
339 term: Vec<u8>,
340 },
341 IndexBuildConflictLimitExceeded { name: Vec<u8>, attempts: usize },
343 IndexUnavailableAtVersion {
345 name: Vec<u8>,
346 source_version: MapVersionId,
347 },
348 IndexSnapshotMismatch {
350 name: Vec<u8>,
351 source_version: MapVersionId,
352 reason: String,
353 },
354 IndexCursorVersionMismatch { expected: String, actual: String },
356 IndexGcUnsafe,
358 IndexResourceLimitExceeded {
360 resource: &'static str,
361 limit: usize,
362 actual: usize,
363 },
364 InvalidIndexedSnapshotBundle { reason: String },
366 InvalidProximityConfig { reason: String },
368 UnsupportedProximityVersion { found: u8, required: u8 },
370 InvalidProximityVector { reason: String },
372 ZeroCosineVector,
374 DuplicateProximityKey { key: Vec<u8> },
376 InvalidProximitySearch { reason: String },
378 ProximityResourceLimitExceeded {
380 resource: &'static str,
381 limit: usize,
382 actual: usize,
383 },
384 InvalidProximityObject { kind: &'static str, reason: String },
386 ProximityNodeTooLarge {
388 level: u8,
389 entries: usize,
390 encoded_bytes: usize,
391 limit: usize,
392 },
393 ContentGraphResourceLimitExceeded {
395 resource: &'static str,
396 limit: usize,
397 actual: usize,
398 },
399}
400
401impl std::fmt::Debug for Error {
402 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
403 std::fmt::Display::fmt(self, f)
409 }
410}
411
412impl Error {
413 pub(crate) fn transaction_conflict(conflict: TransactionConflict) -> Self {
414 Self::TransactionConflict(Box::new(conflict))
415 }
416
417 pub fn index_code(&self) -> Option<IndexErrorCode> {
418 use IndexErrorCode as Code;
419 Some(match self {
420 Self::IndexFormatUnsupported => Code::FormatUnsupported,
421 Self::UnsupportedIndexedStoreProfile { .. } => Code::StoreProfileUnsupported,
422 Self::TransactionConflict(_) | Self::IndexBuildConflictLimitExceeded { .. } => {
423 Code::Conflict
424 }
425 Self::IndexSnapshotMismatch { .. } => Code::Corruption,
426 Self::InvalidIndexDefinition { .. } | Self::IndexDefinitionMismatch { .. } => {
427 Code::DefinitionInvalid
428 }
429 Self::IndexRuntimeDefinitionMissing { .. } => Code::RuntimeDefinitionMissing,
430 Self::IndexesRequireIndexedMap { .. } => Code::ManagedWriteRequired,
431 Self::IndexOperationUnsupported { .. } => Code::OperationUnsupported,
432 Self::IndexExtractionFailed { .. } => Code::ExtractionFailed,
433 Self::IndexProjectionMismatch { .. } | Self::ConflictingIndexProjection { .. } => {
434 Code::ProjectionInvalid
435 }
436 Self::IndexResourceLimitExceeded { .. } => Code::ResourceLimit,
437 Self::IndexCursorVersionMismatch { .. } => Code::CursorInvalid,
438 Self::IndexGcUnsafe => Code::OperationUnsupported,
439 Self::InvalidIndexedSnapshotBundle { .. } => Code::BundleInvalid,
440 Self::IndexUnavailableAtVersion { .. } => Code::SnapshotUnavailable,
441 _ => return None,
442 })
443 }
444
445 pub fn retry_advice(&self) -> RetryAdvice {
446 match self {
447 Self::TransactionConflict(_) | Self::IndexBuildConflictLimitExceeded { .. } => {
448 RetryAdvice::RetryFreshState
449 }
450 Self::Store(_) => RetryAdvice::RetryAfter,
451 _ => RetryAdvice::Never,
452 }
453 }
454}
455
456impl std::fmt::Display for Error {
457 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
458 match self {
459 Error::NotFound(cid) => write!(f, "node not found: {:?}", cid),
460 Error::InvalidNode => write!(f, "invalid node structure"),
461 Error::InvalidFormat(message) => write!(f, "invalid tree format: {message}"),
462 Error::InvalidExecutionConfig { field, value } => {
463 write!(f, "invalid execution config: {field} must be nonzero, got {value}")
464 }
465 Error::FormatMismatch { expected, actual } => write!(
466 f,
467 "tree format mismatch: expected {:?}, got {:?}",
468 expected, actual
469 ),
470 Error::EntryTooLarge {
471 encoded_bytes,
472 limit,
473 } => write!(
474 f,
475 "entry encodes to {encoded_bytes} bytes, exceeding node limit {limit}"
476 ),
477 Error::Deserialize(e) => write!(f, "deserialize error: {}", e),
478 Error::Serialize(e) => write!(f, "serialize error: {}", e),
479 Error::Store(e) => write!(f, "storage error: {}", e),
480 Error::CidMismatch { expected, actual } => {
481 write!(
482 f,
483 "content CID mismatch: expected {:?}, got {:?}",
484 expected, actual
485 )
486 }
487 Error::Conflict(c) => write!(f, "merge conflict at key: {:?}", c.key),
488 Error::BufferFull => write!(f, "mutation buffer is full"),
489 Error::InvalidSavepoint => write!(f, "invalid or stale write-session savepoint"),
490 Error::PatchBaseMismatch => write!(f, "patch base root or values do not match"),
491 Error::InvalidStructuralPatch(reason) => {
492 write!(f, "invalid structural patch: {reason}")
493 }
494 Error::UnsortedInput { previous, next } => write!(
495 f,
496 "sorted input keys are out of order: previous={:?} next={:?}",
497 previous, next
498 ),
499 Error::DuplicateMutation { key } => {
500 write!(f, "duplicate splice mutation: {key:?}")
501 }
502 Error::SpliceConfigMismatch => {
503 write!(f, "splice manager/tree configuration mismatch")
504 }
505 Error::MissingNamedRoots { names } => {
506 write!(f, "missing named roots for retention policy: {:?}", names)
507 }
508 Error::InvalidSnapshotBundle(message) => {
509 write!(f, "invalid snapshot bundle: {message}")
510 }
511 Error::UnsupportedTransactions { store } => {
512 write!(f, "store does not support strict transactions: {store}")
513 }
514 Error::UnsupportedIndexedStoreProfile {
515 store,
516 required,
517 actual,
518 } => write!(
519 f,
520 "store {store} does not satisfy indexed-store requirement {required}: {actual}"
521 ),
522 Error::TransactionConflict(conflict) => {
523 write!(
524 f,
525 "transaction conflict for named root: {:?}",
526 conflict.name
527 )
528 }
529 Error::InvalidVersionedMap(message) => {
530 write!(f, "invalid versioned map: {message}")
531 }
532 Error::IndexFormatUnsupported => {
533 write!(f, "secondary index format is unsupported; hard cutover required")
534 }
535 Error::InvalidIndexDefinition { .. } => {
536 write!(f, "invalid secondary index definition")
537 }
538 Error::IndexRuntimeDefinitionMissing { generation, .. } => write!(
539 f,
540 "runtime secondary index definition missing: generation={generation}"
541 ),
542 Error::IndexDefinitionMismatch { .. } => {
543 write!(f, "secondary index definition mismatch")
544 }
545 Error::IndexesRequireIndexedMap { .. } => {
546 write!(f, "managed map requires IndexedMap coordinator")
547 }
548 Error::IndexOperationUnsupported { operation } => {
549 write!(f, "indexed map operation is unsupported: {operation}")
550 }
551 Error::IndexExtractionFailed { .. } => {
552 write!(f, "secondary index extraction failed")
553 }
554 Error::IndexProjectionMismatch { mode, .. } => {
555 write!(f, "secondary index projection mismatch: mode={mode:?}")
556 }
557 Error::ConflictingIndexProjection { .. } => {
558 write!(f, "conflicting secondary index projection")
559 }
560 Error::IndexBuildConflictLimitExceeded { attempts, .. } => write!(
561 f,
562 "secondary index conflict limit exceeded: attempts={attempts}"
563 ),
564 Error::IndexUnavailableAtVersion { .. } => {
565 write!(f, "secondary index unavailable at selected snapshot")
566 }
567 Error::IndexSnapshotMismatch { .. } => {
568 write!(f, "secondary index snapshot closure is inconsistent")
569 }
570 Error::IndexCursorVersionMismatch { .. } => {
571 write!(f, "secondary index cursor does not match the query snapshot")
572 }
573 Error::IndexGcUnsafe => {
574 write!(f, "secondary index garbage collection lacks a safety proof")
575 }
576 Error::IndexResourceLimitExceeded {
577 resource,
578 limit,
579 actual,
580 } => write!(
581 f,
582 "secondary index resource limit exceeded: resource={resource} limit={limit} actual={actual}"
583 ),
584 Error::InvalidIndexedSnapshotBundle { .. } => {
585 write!(f, "invalid indexed snapshot bundle")
586 }
587 Error::InvalidProximityConfig { reason } => {
588 write!(f, "invalid proximity configuration: {reason}")
589 }
590 Error::UnsupportedProximityVersion { found, required } => write!(
591 f,
592 "unsupported proximity format version: found={found} required={required}"
593 ),
594 Error::InvalidProximityVector { reason } => {
595 write!(f, "invalid proximity vector: {reason}")
596 }
597 Error::ZeroCosineVector => write!(f, "cosine proximity vector has zero norm"),
598 Error::DuplicateProximityKey { key } => {
599 write!(f, "duplicate proximity key: {key:?}")
600 }
601 Error::InvalidProximitySearch { reason } => {
602 write!(f, "invalid proximity search options: {reason}")
603 }
604 Error::ProximityResourceLimitExceeded {
605 resource,
606 limit,
607 actual,
608 } => write!(
609 f,
610 "proximity accelerator resource limit exceeded: resource={resource} limit={limit} actual={actual}"
611 ),
612 Error::InvalidProximityObject { kind, reason } => {
613 write!(f, "invalid proximity {kind}: {reason}")
614 }
615 Error::ProximityNodeTooLarge {
616 level,
617 entries,
618 encoded_bytes,
619 limit,
620 } => write!(
621 f,
622 "proximity node exceeds byte limit: level={level} entries={entries} bytes={encoded_bytes} limit={limit}"
623 ),
624 Error::ContentGraphResourceLimitExceeded {
625 resource,
626 limit,
627 actual,
628 } => write!(
629 f,
630 "content graph resource limit exceeded: resource={resource} limit={limit} actual={actual}"
631 ),
632 }
633 }
634}
635
636impl std::error::Error for Error {}