use litl::ValRef;
use thiserror::Error;
use ti64::MsSinceEpoch;
use crate::ops::{ObjID, Op, OpID, OpKind, OpWithTarget};
#[derive(Default)]
pub struct OpStreamState {
last_op_id: Option<OpID>,
last_time: Option<MsSinceEpoch>,
last_target_obj_id: Option<ObjID>,
last_kind: Option<OpKind>,
}
impl OpStreamState {
pub fn set_last(&mut self, op: &Op, target_obj_id: &ObjID) {
self.last_op_id = Some(op.id);
self.last_time = Some(op.time);
self.last_target_obj_id = Some(*target_obj_id);
self.last_kind = Some(op.kind);
}
}
#[derive(Default)]
pub struct OpSerializer {
state: OpStreamState,
}
impl OpSerializer {
pub fn new() -> Self {
Default::default()
}
pub fn serialize(&mut self, op_with_target: &OpWithTarget) -> litl::Val {
let OpWithTarget { op, target_obj_id } = op_with_target;
let prev_is_predecessor = Some(op.prev) == self.state.last_op_id;
let same_target_obj_id = Some(*target_obj_id) == self.state.last_target_obj_id;
let same_time = Some(op.time) == self.state.last_time;
let same_kind = Some(op.kind) == self.state.last_kind;
let serialized = match op.val.as_ref().map(|v| v.direct_ref()) {
Some(litl::ValRef::String(short_string))
if op.kind == OpKind::ListInsertAfter
&& prev_is_predecessor
&& same_target_obj_id
&& same_time =>
{
litl::Val::str(short_string)
}
_ => litl::Val::object(
IntoIterator::into_iter([
if prev_is_predecessor {
None
} else {
Some(("id".to_string(), litl::to_val(op.id).unwrap()))
},
if prev_is_predecessor {
None
} else {
Some(("prev".to_string(), litl::to_val(op.prev).unwrap()))
},
if same_time {
None
} else {
Some(("time".to_string(), litl::to_val(op.time).unwrap()))
},
if same_target_obj_id {
None
} else {
Some(("trgt".to_string(), litl::to_val(target_obj_id).unwrap()))
},
if same_kind {
None
} else {
Some(("kind".to_string(), litl::to_val(op.kind).unwrap()))
},
op.val.clone().map(|val| ("val".to_string(), val)),
])
.flatten(),
),
};
self.state.set_last(op, target_obj_id);
serialized
}
}
#[derive(Default)]
pub struct OpDeserializer {
state: OpStreamState,
}
impl OpDeserializer {
pub fn new() -> Self {
Default::default()
}
pub fn deserialize(
&mut self,
partial_op: litl::Val,
) -> Result<OpWithTarget, StreamFormatReadError> {
let partial_op_ref: ValRef = partial_op.direct_ref();
if let litl::ValRef::String(_) = partial_op_ref {
let last_op_id = self
.state
.last_op_id
.expect("Should have last op id if no id given");
let id = OpID(last_op_id.0, last_op_id.1 + 1);
let op_with_target = OpWithTarget {
op: Op {
id,
prev: last_op_id,
time: self
.state
.last_time
.ok_or(StreamFormatReadError::ShouldHaveLastTimeIfNoTimeGiven)?,
kind: OpKind::ListInsertAfter,
val: Some(partial_op),
},
target_obj_id: self
.state
.last_target_obj_id
.ok_or(StreamFormatReadError::ShouldHaveLastTargetObjIDIfNoTargetObjIDGiven)?,
};
self.state
.set_last(&op_with_target.op, &op_with_target.target_obj_id);
return Ok(op_with_target);
}
let mut map = match partial_op.into() {
litl::ValE::Object(map) => map.into_entries(),
_ => return Err(StreamFormatReadError::ExpectedPartialOpToBeObject),
};
let id = match map.take("id").map(litl::from_val) {
Some(Ok(id)) => id,
Some(Err(err)) => return Err(StreamFormatReadError::MalformedOpID(err)),
None => {
let last_op_id = self
.state
.last_op_id
.ok_or(StreamFormatReadError::ShouldHaveLastOpIDIfNoOpIDGiven)?;
OpID(last_op_id.0, last_op_id.1 + 1)
}
};
let prev = match map.take("prev").map(litl::from_val) {
Some(Ok(prev)) => prev,
Some(Err(err)) => return Err(StreamFormatReadError::MalformedPrev(err)),
None => self
.state
.last_op_id
.ok_or(StreamFormatReadError::ShouldHaveLastOpIDIfNoPrevGiven)?,
};
let time = match map.take("time").map(litl::from_val) {
Some(Ok(time)) => time,
Some(Err(err)) => return Err(StreamFormatReadError::MalformedTime(err)),
None => self
.state
.last_time
.ok_or(StreamFormatReadError::ShouldHaveLastTimeIfNoTimeGiven)?,
};
let target_obj_id = match map.take("trgt").map(litl::from_val) {
Some(Ok(target_obj_id)) => target_obj_id,
Some(Err(err)) => return Err(StreamFormatReadError::MalformedTargetObjID(err)),
None => self
.state
.last_target_obj_id
.ok_or(StreamFormatReadError::ShouldHaveLastTargetObjIDIfNoTargetObjIDGiven)?,
};
let kind = match map.take("kind").map(litl::from_val) {
Some(Ok(kind)) => kind,
Some(Err(err)) => return Err(StreamFormatReadError::MalformedKind(err)),
None => self
.state
.last_kind
.ok_or(StreamFormatReadError::ShouldHaveLastKindIfNoKindGiven)?,
};
let val = map.take("val");
let op_with_target = OpWithTarget {
op: Op {
id,
prev,
time,
kind,
val,
},
target_obj_id,
};
self.state.set_last(&op_with_target.op, &target_obj_id);
Ok(op_with_target)
}
}
#[derive(Error, Debug)]
pub enum StreamFormatReadError {
#[error("Expected partial op to be object")]
ExpectedPartialOpToBeObject,
#[error("Malformed op id")]
MalformedOpID(litl::ValDeserializerError),
#[error("Malformed prev")]
MalformedPrev(litl::ValDeserializerError),
#[error("Malformed prev op id")]
MalformedPrevOpID(litl::ValDeserializerError),
#[error("Malformed time")]
MalformedTime(litl::ValDeserializerError),
#[error("Malformed target obj id")]
MalformedTargetObjID(litl::ValDeserializerError),
#[error("Malformed kind")]
MalformedKind(litl::ValDeserializerError),
#[error("Should have last time if no time given")]
ShouldHaveLastTimeIfNoTimeGiven,
#[error("Should have last target obj id if no target obj id given")]
ShouldHaveLastTargetObjIDIfNoTargetObjIDGiven,
#[error("Should have last op id if no op id given")]
ShouldHaveLastOpIDIfNoOpIDGiven,
#[error("Should have last op id if no prev id given")]
ShouldHaveLastOpIDIfNoPrevGiven,
#[error("Should have last kind if no kind given")]
ShouldHaveLastKindIfNoKindGiven,
}