use super::cid::Cid;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq)]
pub enum Mutation {
Upsert { key: Vec<u8>, val: Vec<u8> },
Delete { key: Vec<u8> },
}
impl Mutation {
pub fn key(&self) -> &[u8] {
match self {
Mutation::Upsert { key, .. } => key,
Mutation::Delete { key } => key,
}
}
pub fn is_delete(&self) -> bool {
matches!(self, Mutation::Delete { .. })
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Diff {
Added { key: Vec<u8>, val: Vec<u8> },
Removed { key: Vec<u8>, val: Vec<u8> },
Changed {
key: Vec<u8>,
old: Vec<u8>,
new: Vec<u8>,
},
}
impl Diff {
pub fn key(&self) -> &[u8] {
match self {
Diff::Added { key, .. } | Diff::Removed { key, .. } | Diff::Changed { key, .. } => key,
}
}
}
#[derive(Clone, Debug)]
pub struct Conflict {
pub key: Vec<u8>,
pub base: Option<Vec<u8>>,
pub left: Option<Vec<u8>>,
pub right: Option<Vec<u8>>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Resolution {
Value(Vec<u8>),
Delete,
Unresolved,
}
impl Resolution {
pub fn value(value: impl Into<Vec<u8>>) -> Self {
Self::Value(value.into())
}
pub fn delete() -> Self {
Self::Delete
}
pub fn unresolved() -> Self {
Self::Unresolved
}
}
pub type Resolver = Box<dyn Fn(&Conflict) -> Resolution>;
pub mod resolver {
use super::{Conflict, Resolution};
pub fn prefer_left(conflict: &Conflict) -> Resolution {
match &conflict.left {
Some(value) => Resolution::value(value.clone()),
None => Resolution::delete(),
}
}
pub fn prefer_right(conflict: &Conflict) -> Resolution {
match &conflict.right {
Some(value) => Resolution::value(value.clone()),
None => Resolution::delete(),
}
}
pub fn delete_wins(conflict: &Conflict) -> Resolution {
if conflict.left.is_none() || conflict.right.is_none() {
Resolution::delete()
} else {
Resolution::unresolved()
}
}
pub fn update_wins(conflict: &Conflict) -> Resolution {
match (&conflict.left, &conflict.right) {
(Some(value), None) | (None, Some(value)) => Resolution::value(value.clone()),
_ => Resolution::unresolved(),
}
}
}
use super::secondary_index::IndexProjection;
use super::transaction::TransactionConflict;
use super::versioned_map::MapVersionId;
#[derive(Debug)]
pub enum Error {
NotFound(Cid),
InvalidNode,
Deserialize(String),
Serialize(String),
Store(Box<dyn std::error::Error + Send + Sync>),
CidMismatch { expected: Cid, actual: Cid },
Conflict(Conflict),
BufferFull,
UnsortedInput { previous: Vec<u8>, next: Vec<u8> },
DuplicateCanonicalMutation { key: Vec<u8> },
CanonicalSpliceConfigMismatch,
MissingNamedRoots { names: Vec<Vec<u8>> },
InvalidSnapshotBundle(String),
UnsupportedTransactions { store: &'static str },
TransactionConflict(Box<TransactionConflict>),
InvalidVersionedMap(String),
InvalidIndexDefinition { reason: String },
IndexRuntimeDefinitionMissing { name: Vec<u8>, generation: u64 },
IndexDefinitionMismatch {
name: Vec<u8>,
persisted: Cid,
runtime: Cid,
},
IndexesRequireIndexedMap {
map_id: Vec<u8>,
active_indexes: Vec<Vec<u8>>,
},
IndexOperationUnsupported { operation: &'static str },
IndexExtractionFailed {
name: Vec<u8>,
primary_key: Vec<u8>,
reason: String,
},
IndexProjectionMismatch {
name: Vec<u8>,
mode: IndexProjection,
primary_key: Vec<u8>,
},
ConflictingIndexProjection {
name: Vec<u8>,
primary_key: Vec<u8>,
term: Vec<u8>,
},
IndexBuildConflictLimitExceeded { name: Vec<u8>, attempts: usize },
IndexUnavailableAtVersion {
name: Vec<u8>,
source_version: MapVersionId,
},
IndexCheckpointMismatch {
name: Vec<u8>,
source_version: MapVersionId,
reason: String,
},
IndexCursorVersionMismatch { expected: String, actual: String },
IndexResourceLimitExceeded {
resource: &'static str,
limit: usize,
actual: usize,
},
InvalidIndexedSnapshotBundle { reason: String },
InvalidProximityConfig { reason: String },
UnsupportedProximityVersion { found: u8, required: u8 },
InvalidProximityVector { reason: String },
ZeroCosineVector,
DuplicateProximityKey { key: Vec<u8> },
InvalidProximitySearch { reason: String },
InvalidProximityObject { kind: &'static str, reason: String },
ProximityNodeTooLarge {
level: u8,
entries: usize,
encoded_bytes: usize,
limit: usize,
},
ContentGraphResourceLimitExceeded {
resource: &'static str,
limit: usize,
actual: usize,
},
}
impl Error {
pub(crate) fn transaction_conflict(conflict: TransactionConflict) -> Self {
Self::TransactionConflict(Box::new(conflict))
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::NotFound(cid) => write!(f, "node not found: {:?}", cid),
Error::InvalidNode => write!(f, "invalid node structure"),
Error::Deserialize(e) => write!(f, "deserialize error: {}", e),
Error::Serialize(e) => write!(f, "serialize error: {}", e),
Error::Store(e) => write!(f, "storage error: {}", e),
Error::CidMismatch { expected, actual } => {
write!(
f,
"content CID mismatch: expected {:?}, got {:?}",
expected, actual
)
}
Error::Conflict(c) => write!(f, "merge conflict at key: {:?}", c.key),
Error::BufferFull => write!(f, "mutation buffer is full"),
Error::UnsortedInput { previous, next } => write!(
f,
"sorted input keys are out of order: previous={:?} next={:?}",
previous, next
),
Error::DuplicateCanonicalMutation { key } => {
write!(f, "duplicate canonical splice mutation: {key:?}")
}
Error::CanonicalSpliceConfigMismatch => {
write!(f, "canonical splice manager/tree configuration mismatch")
}
Error::MissingNamedRoots { names } => {
write!(f, "missing named roots for retention policy: {:?}", names)
}
Error::InvalidSnapshotBundle(message) => {
write!(f, "invalid snapshot bundle: {message}")
}
Error::UnsupportedTransactions { store } => {
write!(f, "store does not support strict transactions: {store}")
}
Error::TransactionConflict(conflict) => {
write!(
f,
"transaction conflict for named root: {:?}",
conflict.name
)
}
Error::InvalidVersionedMap(message) => {
write!(f, "invalid versioned map: {message}")
}
Error::InvalidIndexDefinition { reason } => {
write!(f, "invalid secondary index definition: {reason}")
}
Error::IndexRuntimeDefinitionMissing { name, generation } => write!(
f,
"runtime secondary index definition missing: name={name:?} generation={generation}"
),
Error::IndexDefinitionMismatch {
name,
persisted,
runtime,
} => write!(
f,
"secondary index definition mismatch: name={name:?} persisted={persisted:?} runtime={runtime:?}"
),
Error::IndexesRequireIndexedMap {
map_id,
active_indexes,
} => write!(
f,
"managed map requires IndexedMap coordinator: map_id={map_id:?} active_indexes={active_indexes:?}"
),
Error::IndexOperationUnsupported { operation } => {
write!(f, "indexed map operation is unsupported in v1: {operation}")
}
Error::IndexExtractionFailed {
name,
primary_key,
reason,
} => write!(
f,
"secondary index extraction failed: name={name:?} primary_key={primary_key:?}: {reason}"
),
Error::IndexProjectionMismatch {
name,
mode,
primary_key,
} => write!(
f,
"secondary index projection mismatch: name={name:?} mode={mode:?} primary_key={primary_key:?}"
),
Error::ConflictingIndexProjection {
name,
primary_key,
term,
} => write!(
f,
"conflicting secondary index projection: name={name:?} primary_key={primary_key:?} term={term:?}"
),
Error::IndexBuildConflictLimitExceeded { name, attempts } => write!(
f,
"secondary index build conflict limit exceeded: name={name:?} attempts={attempts}"
),
Error::IndexUnavailableAtVersion {
name,
source_version,
} => write!(
f,
"secondary index unavailable at source version: name={name:?} source_version={source_version}"
),
Error::IndexCheckpointMismatch {
name,
source_version,
reason,
} => write!(
f,
"secondary index checkpoint mismatch: name={name:?} source_version={source_version}: {reason}"
),
Error::IndexCursorVersionMismatch { expected, actual } => write!(
f,
"secondary index cursor snapshot mismatch: expected={expected} actual={actual}"
),
Error::IndexResourceLimitExceeded {
resource,
limit,
actual,
} => write!(
f,
"secondary index resource limit exceeded: resource={resource} limit={limit} actual={actual}"
),
Error::InvalidIndexedSnapshotBundle { reason } => {
write!(f, "invalid indexed snapshot bundle: {reason}")
}
Error::InvalidProximityConfig { reason } => {
write!(f, "invalid proximity configuration: {reason}")
}
Error::UnsupportedProximityVersion { found, required } => write!(
f,
"unsupported proximity format version: found={found} required={required}"
),
Error::InvalidProximityVector { reason } => {
write!(f, "invalid proximity vector: {reason}")
}
Error::ZeroCosineVector => write!(f, "cosine proximity vector has zero norm"),
Error::DuplicateProximityKey { key } => {
write!(f, "duplicate proximity key: {key:?}")
}
Error::InvalidProximitySearch { reason } => {
write!(f, "invalid proximity search options: {reason}")
}
Error::InvalidProximityObject { kind, reason } => {
write!(f, "invalid proximity {kind}: {reason}")
}
Error::ProximityNodeTooLarge {
level,
entries,
encoded_bytes,
limit,
} => write!(
f,
"proximity node exceeds byte limit: level={level} entries={entries} bytes={encoded_bytes} limit={limit}"
),
Error::ContentGraphResourceLimitExceeded {
resource,
limit,
actual,
} => write!(
f,
"content graph resource limit exceeded: resource={resource} limit={limit} actual={actual}"
),
}
}
}
impl std::error::Error for Error {}