Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
cob: Move clock out of `Issue`
Alexis Sellier committed 3 years ago
commit 09a8b46f343f1666933ed0bd086a5f8eb2a7815c
parent c5225c64cdcf5fe48830d069f2bf5c04a1c27c7c
5 files changed +66 -51
modified radicle-cli/src/commands/issue.rs
@@ -234,7 +234,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
        }
        Operation::List => {
            for result in issues.all()? {
-
                let (id, issue) = result?;
+
                let (id, issue, _) = result?;
                println!("{} {}", id, issue.title());
            }
        }
modified radicle-httpd/src/api/v1/projects.rs
@@ -328,7 +328,7 @@ async fn issues_handler(
        .all()?
        .into_iter()
        .filter_map(|r| r.ok())
-
        .map(|(id, issue)| {
+
        .map(|(id, issue, _)| {
            json!({
                "id": id,
                "author": issue.author(),
modified radicle/src/cob/issue.rs
@@ -77,7 +77,6 @@ pub struct Issue {
    title: LWWReg<Max<String>, clock::Lamport>,
    status: LWWReg<Max<Status>, clock::Lamport>,
    thread: Thread,
-
    clock: clock::Lamport,
}

impl Semilattice for Issue {
@@ -94,7 +93,6 @@ impl Default for Issue {
            title: Max::from(String::default()).into(),
            status: Max::from(Status::default()).into(),
            thread: Thread::default(),
-
            clock: clock::Lamport::default(),
        }
    }
}
@@ -104,10 +102,13 @@ impl store::FromHistory for Issue {
        &*TYPENAME
    }

-
    fn from_history(history: &radicle_cob::History) -> Result<Self, store::Error> {
-
        Ok(history.traverse(Self::default(), |mut acc, entry| {
+
    fn from_history(
+
        history: &radicle_cob::History,
+
    ) -> Result<(Self, clock::Lamport), store::Error> {
+
        let mut clock = clock::Lamport::default();
+
        let obj = history.traverse(Self::default(), |mut acc, entry| {
            if let Ok(change) = Change::decode(entry.contents()) {
-
                if let Err(err) = acc.apply(change) {
+
                if let Err(err) = acc.apply(change, &mut clock) {
                    log::warn!("Error applying change to issue state: {err}");
                    return ControlFlow::Break(acc);
                }
@@ -115,7 +116,9 @@ impl store::FromHistory for Issue {
                return ControlFlow::Break(acc);
            }
            ControlFlow::Continue(acc)
-
        }))
+
        });
+

+
        Ok((obj, clock))
    }
}

@@ -147,32 +150,31 @@ impl Issue {
        self.thread.comments().map(|(id, comment)| (id, comment))
    }

-
    pub fn clock(&self) -> &clock::Lamport {
-
        &self.clock
-
    }
-

    pub fn timeline(&self) -> Vec<Action> {
        todo!();
    }

-
    pub fn apply(&mut self, change: Change) -> Result<(), Error> {
+
    pub fn apply(&mut self, change: Change, clock: &mut clock::Lamport) -> Result<(), Error> {
        match change.action {
            Action::Title { title } => {
                self.title.set(title, change.clock);
+
                clock.merge(change.clock);
            }
            Action::Lifecycle { status } => {
                self.status.set(status, change.clock);
+
                clock.merge(change.clock);
            }
            Action::Thread { action } => {
-
                self.thread.apply([crdt::Change {
-
                    action,
-
                    author: change.author,
-
                    clock: change.clock,
-
                }]);
+
                self.thread.apply(
+
                    [crdt::Change {
+
                        action,
+
                        author: change.author,
+
                        clock: change.clock,
+
                    }],
+
                    clock,
+
                );
            }
        }
-
        self.clock.merge(change.clock);
-

        Ok(())
    }
}
@@ -300,7 +302,7 @@ impl<'a, 'g> IssueMut<'a, 'g> {
    ) -> Result<ChangeId, Error> {
        let id = change.id();

-
        self.issue.apply(change.clone())?;
+
        self.issue.apply(change.clone(), &mut self.clock)?;
        self.store
            .update(self.id, msg, change, signer)
            .map_err(|e| Error::Store(store::Error::from(e)))?;
@@ -342,19 +344,19 @@ impl<'a> Issues<'a> {

    /// Get an issue.
    pub fn get(&self, id: &ObjectId) -> Result<Option<Issue>, store::Error> {
-
        self.raw.get(id)
+
        self.raw.get(id).map(|r| r.map(|(i, _clock)| i))
    }

    /// Get an issue mutably.
    pub fn get_mut<'g>(&'g mut self, id: &ObjectId) -> Result<IssueMut<'a, 'g>, store::Error> {
-
        let issue = self
+
        let (issue, clock) = self
            .raw
            .get(id)?
            .ok_or_else(move || store::Error::NotFound(TYPENAME.clone(), *id))?;

        Ok(IssueMut {
            id: *id,
-
            clock: issue.clock,
+
            clock,
            issue,
            store: self,
        })
@@ -375,10 +377,10 @@ impl<'a> Issues<'a> {
            action: Action::Title { title },
            clock: clock::Lamport::default(),
        };
-
        let (id, issue): (_, Issue) = self.raw.create("Create issue", change, signer)?;
+
        let (id, issue, clock) = self.raw.create("Create issue", change, signer)?;
        let mut issue = IssueMut {
            id,
-
            clock: issue.clock,
+
            clock,
            issue,
            store: self,
        };
@@ -600,13 +602,14 @@ mod test {
        let issues = issues
            .all()
            .unwrap()
+
            .map(|r| r.map(|(_, i, _)| i))
            .collect::<Result<Vec<_>, _>>()
            .unwrap();

        assert_eq!(issues.len(), 3);

-
        issues.iter().find(|(_, i)| i.title() == "First").unwrap();
-
        issues.iter().find(|(_, i)| i.title() == "Second").unwrap();
-
        issues.iter().find(|(_, i)| i.title() == "Third").unwrap();
+
        issues.iter().find(|i| i.title() == "First").unwrap();
+
        issues.iter().find(|i| i.title() == "Second").unwrap();
+
        issues.iter().find(|i| i.title() == "Third").unwrap();
    }
}
modified radicle/src/cob/store.rs
@@ -2,7 +2,7 @@
#![allow(clippy::large_enum_variant)]
use std::marker::PhantomData;

-
use radicle_crdt::Change;
+
use radicle_crdt::{Change, Lamport};
use serde::Serialize;

use crate::cob;
@@ -21,7 +21,7 @@ pub trait FromHistory: Sized {
    /// The object type name.
    fn type_name() -> &'static TypeName;
    /// Create an object from a history.
-
    fn from_history(history: &History) -> Result<Self, Error>;
+
    fn from_history(history: &History) -> Result<(Self, Lamport), Error>;
}

/// Store error.
@@ -107,7 +107,7 @@ impl<'a, T: FromHistory> Store<'a, T> {
        message: &'static str,
        change: Change<A>,
        signer: &G,
-
    ) -> Result<(ObjectId, T), Error> {
+
    ) -> Result<(ObjectId, T, Lamport), Error> {
        let cob = cob::create(
            self.raw,
            signer,
@@ -120,30 +120,32 @@ impl<'a, T: FromHistory> Store<'a, T> {
                contents: change.encode(),
            },
        )?;
-
        let object = T::from_history(cob.history())?;
+
        let (object, clock) = T::from_history(cob.history())?;

-
        Ok((*cob.id(), object))
+
        Ok((*cob.id(), object, clock))
    }

    /// Get an object.
-
    pub fn get(&self, id: &ObjectId) -> Result<Option<T>, Error> {
+
    pub fn get(&self, id: &ObjectId) -> Result<Option<(T, Lamport)>, Error> {
        let cob = cob::get(self.raw, T::type_name(), id)?;

        if let Some(cob) = cob {
-
            let obj = T::from_history(cob.history())?;
-
            Ok(Some(obj))
+
            let (obj, clock) = T::from_history(cob.history())?;
+
            Ok(Some((obj, clock)))
        } else {
            Ok(None)
        }
    }

    /// Return all objects.
-
    pub fn all(&self) -> Result<impl Iterator<Item = Result<(ObjectId, T), Error>>, Error> {
+
    pub fn all(
+
        &self,
+
    ) -> Result<impl Iterator<Item = Result<(ObjectId, T, Lamport), Error>>, Error> {
        let raw = cob::list(self.raw, T::type_name())?;

        Ok(raw.into_iter().map(|o| {
-
            let obj = T::from_history(o.history())?;
-
            Ok((*o.id(), obj))
+
            let (obj, clock) = T::from_history(o.history())?;
+
            Ok((*o.id(), obj, clock))
        }))
    }

modified radicle/src/cob/thread.rs
@@ -87,15 +87,18 @@ impl store::FromHistory for Thread {
        &*TYPENAME
    }

-
    fn from_history(history: &History) -> Result<Self, store::Error> {
-
        Ok(history.traverse(Thread::default(), |mut acc, entry| {
+
    fn from_history(history: &History) -> Result<(Self, Lamport), store::Error> {
+
        let mut clock = Lamport::default();
+
        let obj = history.traverse(Thread::default(), |mut acc, entry| {
            if let Ok(change) = Change::decode(entry.contents()) {
-
                acc.apply([change]);
+
                acc.apply([change], &mut clock);
                ControlFlow::Continue(acc)
            } else {
                ControlFlow::Break(acc)
            }
-
        }))
+
        });
+

+
        Ok((obj, clock))
    }
}

@@ -153,7 +156,11 @@ impl Thread {
            .map(|(a, r)| (a, r))
    }

-
    pub fn apply(&mut self, changes: impl IntoIterator<Item = Change<Action>>) {
+
    pub fn apply(
+
        &mut self,
+
        changes: impl IntoIterator<Item = Change<Action>>,
+
        clock: &mut Lamport,
+
    ) {
        for change in changes.into_iter() {
            let id = change.id();

@@ -217,6 +224,7 @@ impl Thread {
                        });
                }
            }
+
            clock.merge(change.clock);
        }
    }

@@ -413,6 +421,7 @@ mod tests {
        let (_, signer, repository) = radicle::test::setup::context(&tmp);
        let store =
            radicle::cob::store::Store::<Thread>::open(*signer.public_key(), &repository).unwrap();
+
        let mut clock = Lamport::default();

        let mut alice = Actor::new(signer);

@@ -420,14 +429,14 @@ mod tests {
        let a2 = alice.comment("Second comment", None);

        let mut expected = Thread::default();
-
        expected.apply([a1.clone(), a2.clone()]);
+
        expected.apply([a1.clone(), a2.clone()], &mut clock);

-
        let (id, _) = store.create("Thread created", a1, &alice.signer).unwrap();
+
        let (id, _, _) = store.create("Thread created", a1, &alice.signer).unwrap();
        store
            .update(id, "Thread updated", a2, &alice.signer)
            .unwrap();

-
        let actual = store.get(&id).unwrap().unwrap();
+
        let (actual, _) = store.get(&id).unwrap().unwrap();

        assert_eq!(actual, expected);
    }
@@ -506,15 +515,16 @@ mod tests {
    fn prop_invariants(log: Changes<3>) {
        let t = Thread::default();
        let [p1, p2, p3] = log.permutations;
+
        let mut clock = Lamport::default();

        let mut t1 = t.clone();
-
        t1.apply(p1);
+
        t1.apply(p1, &mut clock);

        let mut t2 = t.clone();
-
        t2.apply(p2);
+
        t2.apply(p2, &mut clock);

        let mut t3 = t;
-
        t3.apply(p3);
+
        t3.apply(p3, &mut clock);

        assert_eq!(t1, t2);
        assert_eq!(t2, t3);