use serde::{Deserialize, Serialize};
use crate::DatabaseId;
use crate::sync::compensation::CompensationHint;
use crate::sync::wire::ack_status::AckStatus;
#[derive(
Debug, Clone, Serialize, Deserialize, zerompk::ToMessagePack, zerompk::FromMessagePack,
)]
pub struct DeltaPushMsg {
pub collection: String,
pub document_id: String,
pub delta: Vec<u8>,
pub peer_id: u64,
pub mutation_id: u64,
#[serde(default)]
pub checksum: u32,
#[serde(default)]
pub device_valid_time_ms: Option<i64>,
#[serde(default)]
pub producer_id: u64,
#[serde(default)]
pub epoch: u64,
#[serde(default)]
pub seq: u64,
#[serde(default)]
pub device_id: u64,
#[serde(default)]
pub delta_signature: [u8; 32],
}
#[derive(
Debug, Clone, Serialize, Deserialize, zerompk::ToMessagePack, zerompk::FromMessagePack,
)]
pub struct DeltaAckMsg {
pub mutation_id: u64,
pub lsn: u64,
#[serde(default)]
pub clock_skew_warning_ms: Option<i64>,
#[serde(default)]
pub applied_seq: u64,
#[serde(default)]
pub status: AckStatus,
}
#[derive(
Debug, Clone, Serialize, Deserialize, zerompk::ToMessagePack, zerompk::FromMessagePack,
)]
pub struct DeltaRejectMsg {
pub mutation_id: u64,
pub reason: String,
pub compensation: Option<CompensationHint>,
}
#[derive(
Debug, Clone, Serialize, Deserialize, zerompk::ToMessagePack, zerompk::FromMessagePack,
)]
pub struct CollectionPurgedMsg {
pub tenant_id: u64,
pub database_id: DatabaseId,
pub name: String,
pub purge_lsn: u64,
}
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
Default,
Serialize,
Deserialize,
zerompk::ToMessagePack,
zerompk::FromMessagePack,
)]
pub enum RowOp {
#[default]
Upsert,
Delete,
}
#[derive(
Debug, Clone, Serialize, Deserialize, zerompk::ToMessagePack, zerompk::FromMessagePack,
)]
pub struct RowPushMsg {
pub collection: String,
pub document_id: String,
pub payload: Vec<u8>,
pub op: RowOp,
#[serde(default)]
pub lsn: u64,
#[serde(default)]
pub peer_id: u64,
#[serde(default)]
pub sequence: u64,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sync::wire::{SyncFrame, SyncMessageType};
#[test]
fn delta_reject_with_compensation() {
let reject = DeltaRejectMsg {
mutation_id: 42,
reason: "unique violation".into(),
compensation: Some(CompensationHint::UniqueViolation {
field: "email".into(),
conflicting_value: "alice@example.com".into(),
}),
};
let frame = SyncFrame::new_msgpack(SyncMessageType::DeltaReject, &reject).unwrap();
let decoded: DeltaRejectMsg = SyncFrame::from_bytes(&frame.to_bytes())
.unwrap()
.decode_body()
.unwrap();
assert_eq!(decoded.mutation_id, 42);
assert!(matches!(
decoded.compensation,
Some(CompensationHint::UniqueViolation { .. })
));
}
#[test]
fn collection_purged_roundtrip() {
let msg = CollectionPurgedMsg {
tenant_id: 7,
database_id: DatabaseId::new(42),
name: "embeddings".into(),
purge_lsn: 987_654_321,
};
let frame = SyncFrame::new_msgpack(SyncMessageType::CollectionPurged, &msg).unwrap();
let bytes = frame.to_bytes();
assert_eq!(bytes[0], SyncFrame::FORMAT_VERSION);
assert_eq!(bytes[1], 0x14);
let decoded: CollectionPurgedMsg = SyncFrame::from_bytes(&bytes)
.unwrap()
.decode_body()
.unwrap();
assert_eq!(decoded.tenant_id, 7);
assert_eq!(decoded.database_id, DatabaseId::new(42));
assert_eq!(decoded.name, "embeddings");
assert_eq!(decoded.purge_lsn, 987_654_321);
}
}