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 Conflict,
196 Corruption,
197 DefinitionInvalid,
198 RuntimeDefinitionMissing,
199 ManagedWriteRequired,
200 OperationUnsupported,
201 ExtractionFailed,
202 ProjectionInvalid,
203 ResourceLimit,
204 CursorInvalid,
205 BundleInvalid,
206 SnapshotUnavailable,
207}
208
209#[derive(Clone, Copy, Debug, PartialEq, Eq)]
210pub enum RetryAdvice {
211 Never,
212 RetryFreshState,
213 RetryAfter,
214}
215
216impl IndexErrorCode {
217 pub const fn as_str(self) -> &'static str {
218 match self {
219 Self::FormatUnsupported => "format_unsupported",
220 Self::Conflict => "conflict",
221 Self::Corruption => "corruption",
222 Self::DefinitionInvalid => "definition_invalid",
223 Self::RuntimeDefinitionMissing => "runtime_definition_missing",
224 Self::ManagedWriteRequired => "managed_write_required",
225 Self::OperationUnsupported => "operation_unsupported",
226 Self::ExtractionFailed => "extraction_failed",
227 Self::ProjectionInvalid => "projection_invalid",
228 Self::ResourceLimit => "resource_limit",
229 Self::CursorInvalid => "cursor_invalid",
230 Self::BundleInvalid => "bundle_invalid",
231 Self::SnapshotUnavailable => "snapshot_unavailable",
232 }
233 }
234}
235
236impl RetryAdvice {
237 pub const fn as_str(self) -> &'static str {
238 match self {
239 Self::Never => "never",
240 Self::RetryFreshState => "retry_fresh_state",
241 Self::RetryAfter => "retry_after",
242 }
243 }
244}
245
246pub enum Error {
248 NotFound(Cid),
250 InvalidNode,
252 InvalidFormat(String),
254 InvalidExecutionConfig { field: &'static str, value: usize },
256 FormatMismatch { expected: Cid, actual: Cid },
258 EntryTooLarge { encoded_bytes: u64, limit: u64 },
260 Deserialize(String),
262 Serialize(String),
264 Store(Box<dyn std::error::Error + Send + Sync>),
266 CidMismatch { expected: Cid, actual: Cid },
268 Conflict(Conflict),
272 BufferFull,
274 InvalidSavepoint,
276 PatchBaseMismatch,
278 InvalidStructuralPatch(String),
280 UnsortedInput { previous: Vec<u8>, next: Vec<u8> },
282 DuplicateMutation { key: Vec<u8> },
284 SpliceConfigMismatch,
286 MissingNamedRoots { names: Vec<Vec<u8>> },
288 InvalidSnapshotBundle(String),
290 UnsupportedTransactions { store: &'static str },
292 IndexFormatUnsupported,
294 TransactionConflict(Box<TransactionConflict>),
296 InvalidVersionedMap(String),
298 InvalidIndexDefinition { reason: String },
300 IndexRuntimeDefinitionMissing { name: Vec<u8>, generation: u64 },
302 IndexDefinitionMismatch {
304 name: Vec<u8>,
305 persisted: Cid,
306 runtime: Cid,
307 },
308 IndexesRequireIndexedMap {
310 map_id: Vec<u8>,
311 active_indexes: Vec<Vec<u8>>,
312 },
313 IndexOperationUnsupported { operation: &'static str },
315 IndexExtractionFailed {
317 name: Vec<u8>,
318 primary_key: Vec<u8>,
319 reason: String,
320 },
321 IndexProjectionMismatch {
323 name: Vec<u8>,
324 mode: IndexProjection,
325 primary_key: Vec<u8>,
326 },
327 ConflictingIndexProjection {
329 name: Vec<u8>,
330 primary_key: Vec<u8>,
331 term: Vec<u8>,
332 },
333 IndexBuildConflictLimitExceeded { name: Vec<u8>, attempts: usize },
335 IndexUnavailableAtVersion {
337 name: Vec<u8>,
338 source_version: MapVersionId,
339 },
340 IndexSnapshotMismatch {
342 name: Vec<u8>,
343 source_version: MapVersionId,
344 reason: String,
345 },
346 IndexCursorVersionMismatch { expected: String, actual: String },
348 IndexGcUnsafe,
350 IndexResourceLimitExceeded {
352 resource: &'static str,
353 limit: usize,
354 actual: usize,
355 },
356 InvalidIndexedSnapshotBundle { reason: String },
358 InvalidProximityConfig { reason: String },
360 UnsupportedProximityVersion { found: u8, required: u8 },
362 InvalidProximityVector { reason: String },
364 ZeroCosineVector,
366 DuplicateProximityKey { key: Vec<u8> },
368 InvalidProximitySearch { reason: String },
370 ProximityResourceLimitExceeded {
372 resource: &'static str,
373 limit: usize,
374 actual: usize,
375 },
376 InvalidProximityObject { kind: &'static str, reason: String },
378 ProximityNodeTooLarge {
380 level: u8,
381 entries: usize,
382 encoded_bytes: usize,
383 limit: usize,
384 },
385 ContentGraphResourceLimitExceeded {
387 resource: &'static str,
388 limit: usize,
389 actual: usize,
390 },
391}
392
393impl std::fmt::Debug for Error {
394 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
395 std::fmt::Display::fmt(self, f)
401 }
402}
403
404impl Error {
405 pub(crate) fn transaction_conflict(conflict: TransactionConflict) -> Self {
406 Self::TransactionConflict(Box::new(conflict))
407 }
408
409 pub fn index_code(&self) -> Option<IndexErrorCode> {
410 use IndexErrorCode as Code;
411 Some(match self {
412 Self::IndexFormatUnsupported => Code::FormatUnsupported,
413 Self::TransactionConflict(_) | Self::IndexBuildConflictLimitExceeded { .. } => {
414 Code::Conflict
415 }
416 Self::IndexSnapshotMismatch { .. } => Code::Corruption,
417 Self::InvalidIndexDefinition { .. } | Self::IndexDefinitionMismatch { .. } => {
418 Code::DefinitionInvalid
419 }
420 Self::IndexRuntimeDefinitionMissing { .. } => Code::RuntimeDefinitionMissing,
421 Self::IndexesRequireIndexedMap { .. } => Code::ManagedWriteRequired,
422 Self::IndexOperationUnsupported { .. } => Code::OperationUnsupported,
423 Self::IndexExtractionFailed { .. } => Code::ExtractionFailed,
424 Self::IndexProjectionMismatch { .. } | Self::ConflictingIndexProjection { .. } => {
425 Code::ProjectionInvalid
426 }
427 Self::IndexResourceLimitExceeded { .. } => Code::ResourceLimit,
428 Self::IndexCursorVersionMismatch { .. } => Code::CursorInvalid,
429 Self::IndexGcUnsafe => Code::OperationUnsupported,
430 Self::InvalidIndexedSnapshotBundle { .. } => Code::BundleInvalid,
431 Self::IndexUnavailableAtVersion { .. } => Code::SnapshotUnavailable,
432 _ => return None,
433 })
434 }
435
436 pub fn retry_advice(&self) -> RetryAdvice {
437 match self {
438 Self::TransactionConflict(_) | Self::IndexBuildConflictLimitExceeded { .. } => {
439 RetryAdvice::RetryFreshState
440 }
441 Self::Store(_) => RetryAdvice::RetryAfter,
442 _ => RetryAdvice::Never,
443 }
444 }
445}
446
447impl std::fmt::Display for Error {
448 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
449 match self {
450 Error::NotFound(cid) => write!(f, "node not found: {:?}", cid),
451 Error::InvalidNode => write!(f, "invalid node structure"),
452 Error::InvalidFormat(message) => write!(f, "invalid tree format: {message}"),
453 Error::InvalidExecutionConfig { field, value } => {
454 write!(f, "invalid execution config: {field} must be nonzero, got {value}")
455 }
456 Error::FormatMismatch { expected, actual } => write!(
457 f,
458 "tree format mismatch: expected {:?}, got {:?}",
459 expected, actual
460 ),
461 Error::EntryTooLarge {
462 encoded_bytes,
463 limit,
464 } => write!(
465 f,
466 "entry encodes to {encoded_bytes} bytes, exceeding node limit {limit}"
467 ),
468 Error::Deserialize(e) => write!(f, "deserialize error: {}", e),
469 Error::Serialize(e) => write!(f, "serialize error: {}", e),
470 Error::Store(e) => write!(f, "storage error: {}", e),
471 Error::CidMismatch { expected, actual } => {
472 write!(
473 f,
474 "content CID mismatch: expected {:?}, got {:?}",
475 expected, actual
476 )
477 }
478 Error::Conflict(c) => write!(f, "merge conflict at key: {:?}", c.key),
479 Error::BufferFull => write!(f, "mutation buffer is full"),
480 Error::InvalidSavepoint => write!(f, "invalid or stale write-session savepoint"),
481 Error::PatchBaseMismatch => write!(f, "patch base root or values do not match"),
482 Error::InvalidStructuralPatch(reason) => {
483 write!(f, "invalid structural patch: {reason}")
484 }
485 Error::UnsortedInput { previous, next } => write!(
486 f,
487 "sorted input keys are out of order: previous={:?} next={:?}",
488 previous, next
489 ),
490 Error::DuplicateMutation { key } => {
491 write!(f, "duplicate splice mutation: {key:?}")
492 }
493 Error::SpliceConfigMismatch => {
494 write!(f, "splice manager/tree configuration mismatch")
495 }
496 Error::MissingNamedRoots { names } => {
497 write!(f, "missing named roots for retention policy: {:?}", names)
498 }
499 Error::InvalidSnapshotBundle(message) => {
500 write!(f, "invalid snapshot bundle: {message}")
501 }
502 Error::UnsupportedTransactions { store } => {
503 write!(f, "store does not support strict transactions: {store}")
504 }
505 Error::TransactionConflict(conflict) => {
506 write!(
507 f,
508 "transaction conflict for named root: {:?}",
509 conflict.name
510 )
511 }
512 Error::InvalidVersionedMap(message) => {
513 write!(f, "invalid versioned map: {message}")
514 }
515 Error::IndexFormatUnsupported => {
516 write!(f, "secondary index format is unsupported; hard cutover required")
517 }
518 Error::InvalidIndexDefinition { .. } => {
519 write!(f, "invalid secondary index definition")
520 }
521 Error::IndexRuntimeDefinitionMissing { generation, .. } => write!(
522 f,
523 "runtime secondary index definition missing: generation={generation}"
524 ),
525 Error::IndexDefinitionMismatch { .. } => {
526 write!(f, "secondary index definition mismatch")
527 }
528 Error::IndexesRequireIndexedMap { .. } => {
529 write!(f, "managed map requires IndexedMap coordinator")
530 }
531 Error::IndexOperationUnsupported { operation } => {
532 write!(f, "indexed map operation is unsupported: {operation}")
533 }
534 Error::IndexExtractionFailed { .. } => {
535 write!(f, "secondary index extraction failed")
536 }
537 Error::IndexProjectionMismatch { mode, .. } => {
538 write!(f, "secondary index projection mismatch: mode={mode:?}")
539 }
540 Error::ConflictingIndexProjection { .. } => {
541 write!(f, "conflicting secondary index projection")
542 }
543 Error::IndexBuildConflictLimitExceeded { attempts, .. } => write!(
544 f,
545 "secondary index conflict limit exceeded: attempts={attempts}"
546 ),
547 Error::IndexUnavailableAtVersion { .. } => {
548 write!(f, "secondary index unavailable at selected snapshot")
549 }
550 Error::IndexSnapshotMismatch { .. } => {
551 write!(f, "secondary index snapshot closure is inconsistent")
552 }
553 Error::IndexCursorVersionMismatch { .. } => {
554 write!(f, "secondary index cursor does not match the query snapshot")
555 }
556 Error::IndexGcUnsafe => {
557 write!(f, "secondary index garbage collection lacks a safety proof")
558 }
559 Error::IndexResourceLimitExceeded {
560 resource,
561 limit,
562 actual,
563 } => write!(
564 f,
565 "secondary index resource limit exceeded: resource={resource} limit={limit} actual={actual}"
566 ),
567 Error::InvalidIndexedSnapshotBundle { .. } => {
568 write!(f, "invalid indexed snapshot bundle")
569 }
570 Error::InvalidProximityConfig { reason } => {
571 write!(f, "invalid proximity configuration: {reason}")
572 }
573 Error::UnsupportedProximityVersion { found, required } => write!(
574 f,
575 "unsupported proximity format version: found={found} required={required}"
576 ),
577 Error::InvalidProximityVector { reason } => {
578 write!(f, "invalid proximity vector: {reason}")
579 }
580 Error::ZeroCosineVector => write!(f, "cosine proximity vector has zero norm"),
581 Error::DuplicateProximityKey { key } => {
582 write!(f, "duplicate proximity key: {key:?}")
583 }
584 Error::InvalidProximitySearch { reason } => {
585 write!(f, "invalid proximity search options: {reason}")
586 }
587 Error::ProximityResourceLimitExceeded {
588 resource,
589 limit,
590 actual,
591 } => write!(
592 f,
593 "proximity accelerator resource limit exceeded: resource={resource} limit={limit} actual={actual}"
594 ),
595 Error::InvalidProximityObject { kind, reason } => {
596 write!(f, "invalid proximity {kind}: {reason}")
597 }
598 Error::ProximityNodeTooLarge {
599 level,
600 entries,
601 encoded_bytes,
602 limit,
603 } => write!(
604 f,
605 "proximity node exceeds byte limit: level={level} entries={entries} bytes={encoded_bytes} limit={limit}"
606 ),
607 Error::ContentGraphResourceLimitExceeded {
608 resource,
609 limit,
610 actual,
611 } => write!(
612 f,
613 "content graph resource limit exceeded: resource={resource} limit={limit} actual={actual}"
614 ),
615 }
616 }
617}
618
619impl std::error::Error for Error {}