use futures::StreamExt;
use litl::{impl_debug_as_litl, impl_serde_enum_of_distinct_tagged_values};
use serde_derive::{Deserialize, Serialize};
use thiserror::Error;
use crate::{
blob::{BlobDiff, BlobError, BlobID, BlobState, BlobStateInfo},
log::{LogDiff, LogError, LogID, LogState, LogStateInfo, LogWriteAccess},
set::{SetDiff, SetError, SetID, SetState, SetStateInfo, SetWriteAccess},
telepathic::{ApplyDiffErrorFor, ApplyDiffSuccess, Telepathic, TelepathicDiff},
};
#[derive(Debug)]
pub enum ObjectState {
Log(Box<LogState>),
Set(Box<SetState>),
Blob(Box<BlobState>),
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub enum ObjectID {
Log(LogID),
Set(SetID),
Blob(BlobID),
}
impl_serde_enum_of_distinct_tagged_values!(ObjectID,
"log" => (Log, LogID),
"set" => (Set, SetID),
"blob" => (Blob, BlobID)
);
impl ObjectID {
#[track_caller]
pub fn expect_log(&self) -> LogID {
match self {
ObjectID::Log(id) => *id,
_ => panic!("Expected LogID, got {:?}", self),
}
}
#[track_caller]
pub fn expect_set(&self) -> SetID {
match self {
ObjectID::Set(id) => *id,
_ => panic!("Expected SetID, got {:?}", self),
}
}
#[track_caller]
pub fn expect_blob(&self) -> BlobID {
match self {
ObjectID::Blob(id) => *id,
_ => panic!("Expected BlobID, got {:?}", self),
}
}
pub fn test_random() -> Self {
ObjectID::Blob(BlobID::test_random())
}
}
impl_debug_as_litl!(ObjectID);
pub enum WriteAccess {
Log(LogWriteAccess),
Set(SetWriteAccess),
}
impl_serde_enum_of_distinct_tagged_values!(WriteAccess,
"log" => (Log, LogWriteAccess),
"set" => (Set, SetWriteAccess)
);
impl_debug_as_litl!(WriteAccess);
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
pub enum StateInfo {
Log(LogStateInfo),
Set(SetStateInfo),
Blob(BlobStateInfo),
}
#[derive(Clone, Serialize, Deserialize)]
pub enum Diff {
Log(LogDiff),
Set(SetDiff),
Blob(BlobDiff),
}
impl Diff {
#[track_caller]
pub fn expect_log(&self) -> &LogDiff {
match self {
Diff::Log(diff) => diff,
_ => panic!("Expected LogDiff, got {:?}", self),
}
}
#[track_caller]
pub fn expect_set(&self) -> &SetDiff {
match self {
Diff::Set(diff) => diff,
_ => panic!("Expected SetDiff, got {:?}", self),
}
}
#[track_caller]
pub fn expect_blob(&self) -> &BlobDiff {
match self {
Diff::Blob(diff) => diff,
_ => panic!("Expected BlobDiff, got {:?}", self),
}
}
}
impl_debug_as_litl!(Diff);
#[derive(Debug, Error)]
pub enum ObjectError {
#[error(transparent)]
Log(LogError),
#[error(transparent)]
Set(SetError),
#[error(transparent)]
Blob(BlobError),
}
impl Telepathic for ObjectState {
type ID = ObjectID;
type WriteAccess = WriteAccess;
type StateInfo = StateInfo;
type Diff = Diff;
type Error = ObjectError;
fn id(&self) -> Self::ID {
match self {
ObjectState::Log(log) => ObjectID::Log(log.id()),
ObjectState::Set(set) => ObjectID::Set(set.id()),
ObjectState::Blob(blob) => ObjectID::Blob(blob.id()),
}
}
fn try_apply_diff(
&mut self,
diff: Self::Diff,
) -> Result<
ApplyDiffSuccess<Self::StateInfo, Self::ID, Self::Diff>,
ApplyDiffErrorFor<Self::Error>,
> {
match (self, diff) {
(ObjectState::Log(log), Diff::Log(delta)) => log
.try_apply_diff(delta)
.map(
|ApplyDiffSuccess {
new_state_info: new_state,
effective_diff,
}| ApplyDiffSuccess {
new_state_info: StateInfo::Log(new_state),
effective_diff: Diff::Log(effective_diff),
},
)
.map_err(|e| match e {
ApplyDiffErrorFor::InvalidKnownStateAssumption => {
ApplyDiffErrorFor::InvalidKnownStateAssumption
}
ApplyDiffErrorFor::Other(e) => ApplyDiffErrorFor::Other(ObjectError::Log(e)),
}),
(ObjectState::Set(set), Diff::Set(delta)) => set
.try_apply_diff(delta)
.map(
|ApplyDiffSuccess {
new_state_info: new_state,
effective_diff,
}| ApplyDiffSuccess {
new_state_info: StateInfo::Set(new_state),
effective_diff: Diff::Set(effective_diff),
},
)
.map_err(|e| match e {
ApplyDiffErrorFor::InvalidKnownStateAssumption => {
ApplyDiffErrorFor::InvalidKnownStateAssumption
}
ApplyDiffErrorFor::Other(e) => ApplyDiffErrorFor::Other(ObjectError::Set(e)),
}),
(ObjectState::Blob(blob), Diff::Blob(delta)) => blob
.try_apply_diff(delta)
.map(
|ApplyDiffSuccess {
new_state_info: new_state,
effective_diff,
}| ApplyDiffSuccess {
new_state_info: StateInfo::Blob(new_state),
effective_diff: Diff::Blob(effective_diff),
},
)
.map_err(|e| match e {
ApplyDiffErrorFor::InvalidKnownStateAssumption => {
ApplyDiffErrorFor::InvalidKnownStateAssumption
}
ApplyDiffErrorFor::Other(e) => ApplyDiffErrorFor::Other(ObjectError::Blob(e)),
}),
(s, diff) => unreachable!("Invalid object state/diff combination {s:?} {diff:?}"),
}
}
fn state_info(&self) -> Option<Self::StateInfo> {
match self {
ObjectState::Log(log) => log.state_info().map(StateInfo::Log),
ObjectState::Set(set) => set.state_info().map(StateInfo::Set),
ObjectState::Blob(blob) => blob.state_info().map(StateInfo::Blob),
}
}
fn diff_since(&self, state_info: Option<&Self::StateInfo>) -> Option<Self::Diff> {
match (self, state_info) {
(ObjectState::Log(log), Some(StateInfo::Log(state_info))) => {
log.diff_since(Some(state_info)).map(Diff::Log)
}
(ObjectState::Log(log), None) => log.diff_since(None).map(Diff::Log),
(ObjectState::Set(set), Some(StateInfo::Set(state_info))) => {
set.diff_since(Some(state_info)).map(Diff::Set)
}
(ObjectState::Set(set), None) => set.diff_since(None).map(Diff::Set),
(ObjectState::Blob(blob), Some(StateInfo::Blob(state_info))) => {
blob.diff_since(Some(state_info)).map(Diff::Blob)
}
(ObjectState::Blob(blob), None) => blob.diff_since(None).map(Diff::Blob),
_ => {
unreachable!("Invalid object state/state_info combination {self:?} {state_info:?}")
}
}
}
fn load(
id: ObjectID,
storage: Box<dyn crate::StorageBackend>,
) -> std::pin::Pin<Box<dyn futures::Stream<Item = Self::Diff>>> {
match id {
ObjectID::Log(id) => LogState::load(id, storage).map(Diff::Log).boxed_local(),
ObjectID::Set(id) => SetState::load(id, storage).map(Diff::Set).boxed_local(),
ObjectID::Blob(id) => BlobState::load(id, storage).map(Diff::Blob).boxed_local(),
}
}
fn store(
effective_diff: Diff,
storage: Box<dyn crate::StorageBackend>,
) -> std::pin::Pin<Box<dyn futures::Future<Output = ()>>> {
match effective_diff {
Diff::Log(diff) => LogState::store(diff, storage),
Diff::Set(diff) => SetState::store(diff, storage),
Diff::Blob(diff) => BlobState::store(diff, storage),
}
}
}
impl TelepathicDiff for Diff {
type ID = ObjectID;
fn id(&self) -> Self::ID {
match self {
Diff::Log(log) => ObjectID::Log(log.id),
Diff::Set(set) => ObjectID::Set(set.id),
Diff::Blob(blob) => ObjectID::Blob(blob.id),
}
}
}
#[cfg(test)]
mod test {
use crate::{log::LogState, set::SetState, BlobState, ObjectID};
#[test]
fn object_id_serialization_roundtrips() {
let (log, _) = LogState::new::<()>(None);
let id = ObjectID::Log(log.id);
let serialized = litl::to_vec(&id).unwrap();
let deserialized: ObjectID = litl::from_slice(&serialized).unwrap();
println!("{}", String::from_utf8(serialized).unwrap());
assert_eq!(id, deserialized);
let (set, _) = SetState::new::<()>(None);
let id = ObjectID::Set(set.id);
let serialized = litl::to_vec(&id).unwrap();
let deserialized: ObjectID = litl::from_slice(&serialized).unwrap();
println!("{}", String::from_utf8(serialized).unwrap());
assert_eq!(id, deserialized);
let blob = BlobState::new::<()>(());
let id = ObjectID::Blob(blob.id);
let serialized = litl::to_vec(&id).unwrap();
let deserialized: ObjectID = litl::from_slice(&serialized).unwrap();
println!("{}", String::from_utf8(serialized).unwrap());
assert_eq!(id, deserialized);
}
}