cojson 0.6.0

Collaborative JSON (A high performance CRDT)
Documentation
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,
}