From c9162121b078f036405e9c4fd9358789e20259a2 Mon Sep 17 00:00:00 2001 From: Alexis Sellier Date: Fri, 23 Jun 2023 12:49:57 +0200 Subject: [PATCH] cob: Parametrize `cob::Store` with repository type The additional flexibility in choosing a backend for the COB store will allow things like "draft" COBs that are stored in a different location, without changing much of the logic. --- radicle-cli/src/commands/issue.rs | 14 +++---- radicle-cob/src/lib.rs | 4 +- .../src/object/collaboration/create.rs | 4 +- radicle-cob/src/object/collaboration/get.rs | 4 +- radicle-cob/src/object/collaboration/list.rs | 4 +- .../src/object/collaboration/remove.rs | 4 +- .../src/object/collaboration/update.rs | 4 +- radicle-cob/src/object/storage.rs | 2 +- radicle-cob/src/test/storage.rs | 2 +- radicle/src/cob.rs | 6 +-- radicle/src/cob/identity.rs | 37 ++++++++++++------- radicle/src/cob/issue.rs | 32 +++++++++------- radicle/src/cob/patch.rs | 37 +++++++++++-------- radicle/src/cob/store.rs | 26 +++++++------ radicle/src/storage.rs | 11 +++++- radicle/src/storage/git.rs | 6 --- 16 files changed, 111 insertions(+), 86 deletions(-) diff --git a/radicle-cli/src/commands/issue.rs b/radicle-cli/src/commands/issue.rs index e5b35178..b1de43d5 100644 --- a/radicle-cli/src/commands/issue.rs +++ b/radicle-cli/src/commands/issue.rs @@ -14,7 +14,7 @@ use radicle::node::{AliasStore, Handle}; use radicle::prelude::Did; use radicle::profile; use radicle::storage; -use radicle::storage::WriteStorage; +use radicle::storage::{WriteRepository, WriteStorage}; use radicle::{cob, Node}; use radicle_term::table::TableOptions; use radicle_term::{Paint, Table, VStack}; @@ -359,8 +359,8 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { Ok(()) } -fn list( - issues: &Issues, +fn list( + issues: &Issues, assigned: &Option, state: &Option, profile: &profile::Profile, @@ -544,12 +544,12 @@ fn prompt_issue( Ok(Some((meta, description))) } -fn open( +fn open( title: Option, description: Option, tags: Vec, options: &Options, - issues: &mut Issues, + issues: &mut Issues, signer: &G, ) -> anyhow::Result<()> { let Some((meta, description)) = prompt_issue( @@ -579,8 +579,8 @@ fn open( Ok(()) } -fn edit( - issues: &mut issue::Issues, +fn edit( + issues: &mut issue::Issues, signer: &G, repo: &storage::git::Repository, id: Rev, diff --git a/radicle-cob/src/lib.rs b/radicle-cob/src/lib.rs index 63a5211b..a9720c51 100644 --- a/radicle-cob/src/lib.rs +++ b/radicle-cob/src/lib.rs @@ -116,9 +116,9 @@ mod tests; /// [`git2::Repository`]. It is expected that the underlying storage /// for `object::Storage` will also be `git2::Repository`, but if not /// please open an issue to change the definition of `Store` :) -pub trait Store +pub trait Store where - Self: object::Storage + Self: object::Storage + change::Storage< StoreError = git::change::error::Create, LoadError = git::change::error::Load, diff --git a/radicle-cob/src/object/collaboration/create.rs b/radicle-cob/src/object/collaboration/create.rs index 8e73fb00..841ea39d 100644 --- a/radicle-cob/src/object/collaboration/create.rs +++ b/radicle-cob/src/object/collaboration/create.rs @@ -48,7 +48,7 @@ impl Create { /// /// The `args` are the metadata for this [`CollaborativeObject`]. See /// [`Create`] for further information. -pub fn create( +pub fn create( storage: &S, signer: &G, resource: Oid, @@ -57,7 +57,7 @@ pub fn create( args: Create, ) -> Result where - S: Store, + S: Store, G: crypto::Signer, { let Create { ref typename, .. } = &args; diff --git a/radicle-cob/src/object/collaboration/get.rs b/radicle-cob/src/object/collaboration/get.rs index 28cb6712..32750bb8 100644 --- a/radicle-cob/src/object/collaboration/get.rs +++ b/radicle-cob/src/object/collaboration/get.rs @@ -13,13 +13,13 @@ use super::error; /// The `typename` is the type of object to be found, while the /// `object_id` is the identifier for the particular object under that /// type. -pub fn get( +pub fn get( storage: &S, typename: &TypeName, oid: &ObjectId, ) -> Result, error::Retrieve> where - S: Store, + S: Store, { let tip_refs = storage .objects(typename, oid) diff --git a/radicle-cob/src/object/collaboration/list.rs b/radicle-cob/src/object/collaboration/list.rs index 1780b6d1..0acecea3 100644 --- a/radicle-cob/src/object/collaboration/list.rs +++ b/radicle-cob/src/object/collaboration/list.rs @@ -11,12 +11,12 @@ use super::error; /// [`Store`] for further information. /// /// The `typename` is the type of objects to be listed. -pub fn list( +pub fn list( storage: &S, typename: &TypeName, ) -> Result, error::Retrieve> where - S: Store, + S: Store, { let references = storage .types(typename) diff --git a/radicle-cob/src/object/collaboration/remove.rs b/radicle-cob/src/object/collaboration/remove.rs index 4969ccc5..334148a0 100644 --- a/radicle-cob/src/object/collaboration/remove.rs +++ b/radicle-cob/src/object/collaboration/remove.rs @@ -13,14 +13,14 @@ use super::error; /// The `typename` is the type of object to be found, while the /// `object_id` is the identifier for the particular object under that /// type. -pub fn remove( +pub fn remove( storage: &S, identifier: &S::Identifier, typename: &TypeName, oid: &ObjectId, ) -> Result<(), error::Remove> where - S: Store, + S: Store, { storage .remove(identifier, typename, oid) diff --git a/radicle-cob/src/object/collaboration/update.rs b/radicle-cob/src/object/collaboration/update.rs index d13e5cde..113f0558 100644 --- a/radicle-cob/src/object/collaboration/update.rs +++ b/radicle-cob/src/object/collaboration/update.rs @@ -49,7 +49,7 @@ pub struct Update { /// /// The `args` are the metadata for this [`CollaborativeObject`] /// udpate. See [`Update`] for further information. -pub fn update( +pub fn update( storage: &S, signer: &G, resource: Oid, @@ -58,7 +58,7 @@ pub fn update( args: Update, ) -> Result where - S: Store, + S: Store, G: crypto::Signer, { let Update { diff --git a/radicle-cob/src/object/storage.rs b/radicle-cob/src/object/storage.rs index 40b74861..aeddbce4 100644 --- a/radicle-cob/src/object/storage.rs +++ b/radicle-cob/src/object/storage.rs @@ -10,7 +10,7 @@ use crate::{ObjectId, TypeName}; /// The [`Reference`]s that refer to the commits that make up a /// [`crate::CollaborativeObject`]. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct Objects(Vec); impl Objects { diff --git a/radicle-cob/src/test/storage.rs b/radicle-cob/src/test/storage.rs index 85f7d510..7c83a2ad 100644 --- a/radicle-cob/src/test/storage.rs +++ b/radicle-cob/src/test/storage.rs @@ -56,7 +56,7 @@ impl Storage { } } -impl Store for Storage {} +impl Store for Storage {} impl change::Storage for Storage { type StoreError = ::StoreError; diff --git a/radicle/src/cob.rs b/radicle/src/cob.rs index 05d29c7c..79cd7859 100644 --- a/radicle/src/cob.rs +++ b/radicle/src/cob.rs @@ -9,11 +9,11 @@ pub mod thread; #[cfg(test)] pub mod test; -pub use cob::{create, get, list, remove, update}; pub use cob::{ - history::EntryId, object::collaboration::error, CollaborativeObject, Contents, Create, Entry, - History, ObjectId, TypeName, Update, Updated, + change, history::EntryId, object, object::collaboration::error, CollaborativeObject, Contents, + Create, Entry, History, ObjectId, Store, TypeName, Update, Updated, }; +pub use cob::{create, get, list, remove, update}; pub use common::*; pub use op::{ActorId, Op}; diff --git a/radicle/src/cob/identity.rs b/radicle/src/cob/identity.rs index 6b791d02..e4ab03fa 100644 --- a/radicle/src/cob/identity.rs +++ b/radicle/src/cob/identity.rs @@ -17,7 +17,7 @@ use crate::{ }, identity::{doc::DocError, Did, Identity, IdentityError}, prelude::{Doc, ReadRepository}, - storage::{git as storage, RemoteId, WriteRepository}, + storage::{RemoteId, WriteRepository}, }; use super::{ @@ -561,20 +561,23 @@ impl store::Transaction { } } -pub struct ProposalMut<'a, 'g> { +pub struct ProposalMut<'a, 'g, R> { pub id: ObjectId, proposal: Proposal, clock: clock::Lamport, - store: &'g mut Proposals<'a>, + store: &'g mut Proposals<'a, R>, } -impl<'a, 'g> ProposalMut<'a, 'g> { +impl<'a, 'g, R> ProposalMut<'a, 'g, R> +where + R: WriteRepository + cob::Store, +{ pub fn new( id: ObjectId, proposal: Proposal, clock: clock::Lamport, - store: &'g mut Proposals<'a>, + store: &'g mut Proposals<'a, R>, ) -> Self { Self { id, @@ -672,7 +675,7 @@ impl<'a, 'g> ProposalMut<'a, 'g> { } } -impl<'a, 'g> Deref for ProposalMut<'a, 'g> { +impl<'a, 'g, R> Deref for ProposalMut<'a, 'g, R> { type Target = Proposal; fn deref(&self) -> &Self::Target { @@ -680,21 +683,24 @@ impl<'a, 'g> Deref for ProposalMut<'a, 'g> { } } -pub struct Proposals<'a> { - raw: store::Store<'a, Proposal>, +pub struct Proposals<'a, R> { + raw: store::Store<'a, Proposal, R>, } -impl<'a> Deref for Proposals<'a> { - type Target = store::Store<'a, Proposal>; +impl<'a, R> Deref for Proposals<'a, R> { + type Target = store::Store<'a, Proposal, R>; fn deref(&self) -> &Self::Target { &self.raw } } -impl<'a> Proposals<'a> { +impl<'a, R: WriteRepository> Proposals<'a, R> +where + R: WriteRepository + cob::Store, +{ /// Open a proposals store. - pub fn open(repository: &'a storage::Repository) -> Result { + pub fn open(repository: &'a R) -> Result { let raw = store::Store::open(repository)?; Ok(Self { raw }) @@ -708,7 +714,7 @@ impl<'a> Proposals<'a> { current: impl Into, proposed: Doc, signer: &G, - ) -> Result, Error> { + ) -> Result, Error> { let (id, proposal, clock) = Transaction::initial("Create proposal", &mut self.raw, signer, |tx| { tx.revision(current.into(), proposed)?; @@ -728,7 +734,10 @@ impl<'a> Proposals<'a> { } /// Get a proposal mutably. - pub fn get_mut<'g>(&'g mut self, id: &ObjectId) -> Result, store::Error> { + pub fn get_mut<'g>( + &'g mut self, + id: &ObjectId, + ) -> Result, store::Error> { let (proposal, clock) = self .raw .get(id)? diff --git a/radicle/src/cob/issue.rs b/radicle/src/cob/issue.rs index 8c53bbba..0254cc6c 100644 --- a/radicle/src/cob/issue.rs +++ b/radicle/src/cob/issue.rs @@ -17,7 +17,7 @@ use crate::cob::thread::{CommentId, Thread}; use crate::cob::{store, ActorId, EntryId, ObjectId, TypeName}; use crate::crypto::Signer; use crate::prelude::{Did, ReadRepository}; -use crate::storage::git as storage; +use crate::storage::WriteRepository; /// Issue operation. pub type Op = cob::Op; @@ -331,14 +331,17 @@ impl store::Transaction { } } -pub struct IssueMut<'a, 'g> { +pub struct IssueMut<'a, 'g, R> { id: ObjectId, clock: clock::Lamport, issue: Issue, - store: &'g mut Issues<'a>, + store: &'g mut Issues<'a, R>, } -impl<'a, 'g> IssueMut<'a, 'g> { +impl<'a, 'g, R> IssueMut<'a, 'g, R> +where + R: WriteRepository + cob::Store, +{ /// Get the issue id. pub fn id(&self) -> &ObjectId { &self.id @@ -457,7 +460,7 @@ impl<'a, 'g> IssueMut<'a, 'g> { } } -impl<'a, 'g> Deref for IssueMut<'a, 'g> { +impl<'a, 'g, R> Deref for IssueMut<'a, 'g, R> { type Target = Issue; fn deref(&self) -> &Self::Target { @@ -465,12 +468,12 @@ impl<'a, 'g> Deref for IssueMut<'a, 'g> { } } -pub struct Issues<'a> { - raw: store::Store<'a, Issue>, +pub struct Issues<'a, R> { + raw: store::Store<'a, Issue, R>, } -impl<'a> Deref for Issues<'a> { - type Target = store::Store<'a, Issue>; +impl<'a, R> Deref for Issues<'a, R> { + type Target = store::Store<'a, Issue, R>; fn deref(&self) -> &Self::Target { &self.raw @@ -485,9 +488,12 @@ pub struct IssueCounts { pub closed: usize, } -impl<'a> Issues<'a> { +impl<'a, R: WriteRepository> Issues<'a, R> +where + R: ReadRepository + cob::Store, +{ /// Open an issues store. - pub fn open(repository: &'a storage::Repository) -> Result { + pub fn open(repository: &'a R) -> Result { let raw = store::Store::open(repository)?; Ok(Self { raw }) @@ -499,7 +505,7 @@ impl<'a> Issues<'a> { } /// Get an issue mutably. - pub fn get_mut<'g>(&'g mut self, id: &ObjectId) -> Result, store::Error> { + pub fn get_mut<'g>(&'g mut self, id: &ObjectId) -> Result, store::Error> { let (issue, clock) = self .raw .get(id)? @@ -521,7 +527,7 @@ impl<'a> Issues<'a> { tags: &[Tag], assignees: &[ActorId], signer: &G, - ) -> Result, Error> { + ) -> Result, Error> { let (id, issue, clock) = Transaction::initial("Create issue", &mut self.raw, signer, |tx| { tx.thread(description)?; diff --git a/radicle/src/cob/patch.rs b/radicle/src/cob/patch.rs index 1a658ac9..e6205bb3 100644 --- a/radicle/src/cob/patch.rs +++ b/radicle/src/cob/patch.rs @@ -30,7 +30,6 @@ use crate::identity; use crate::identity::doc::DocError; use crate::identity::PayloadError; use crate::prelude::*; -use crate::storage::git as storage; /// The logical clock we use to order operations to patches. pub use clock::Lamport as Clock; @@ -1143,20 +1142,23 @@ impl store::Transaction { } } -pub struct PatchMut<'a, 'g> { +pub struct PatchMut<'a, 'g, R> { pub id: ObjectId, patch: Patch, clock: clock::Lamport, - store: &'g mut Patches<'a>, + store: &'g mut Patches<'a, R>, } -impl<'a, 'g> PatchMut<'a, 'g> { +impl<'a, 'g, R> PatchMut<'a, 'g, R> +where + R: WriteRepository + cob::Store, +{ pub fn new( id: ObjectId, patch: Patch, clock: clock::Lamport, - store: &'g mut Patches<'a>, + store: &'g mut Patches<'a, R>, ) -> Self { Self { id, @@ -1358,7 +1360,7 @@ impl<'a, 'g> PatchMut<'a, 'g> { } } -impl<'a, 'g> Deref for PatchMut<'a, 'g> { +impl<'a, 'g, R> Deref for PatchMut<'a, 'g, R> { type Target = Patch; fn deref(&self) -> &Self::Target { @@ -1376,21 +1378,24 @@ pub struct PatchCounts { pub merged: usize, } -pub struct Patches<'a> { - raw: store::Store<'a, Patch>, +pub struct Patches<'a, R> { + raw: store::Store<'a, Patch, R>, } -impl<'a> Deref for Patches<'a> { - type Target = store::Store<'a, Patch>; +impl<'a, R> Deref for Patches<'a, R> { + type Target = store::Store<'a, Patch, R>; fn deref(&self) -> &Self::Target { &self.raw } } -impl<'a> Patches<'a> { +impl<'a, R> Patches<'a, R> +where + R: WriteRepository + cob::Store, +{ /// Open an patches store. - pub fn open(repository: &'a storage::Repository) -> Result { + pub fn open(repository: &'a R) -> Result { let raw = store::Store::open(repository)?; Ok(Self { raw }) @@ -1406,7 +1411,7 @@ impl<'a> Patches<'a> { oid: impl Into, tags: &[Tag], signer: &G, - ) -> Result, Error> { + ) -> Result, Error> { self._create( title, description, @@ -1429,7 +1434,7 @@ impl<'a> Patches<'a> { oid: impl Into, tags: &[Tag], signer: &G, - ) -> Result, Error> { + ) -> Result, Error> { self._create( title, description, @@ -1484,7 +1489,7 @@ impl<'a> Patches<'a> { } /// Get a patch mutably. - pub fn get_mut<'g>(&'g mut self, id: &ObjectId) -> Result, store::Error> { + pub fn get_mut<'g>(&'g mut self, id: &ObjectId) -> Result, store::Error> { let (patch, clock) = self .raw .get(id)? @@ -1531,7 +1536,7 @@ impl<'a> Patches<'a> { tags: &[Tag], state: State, signer: &G, - ) -> Result, Error> { + ) -> Result, Error> { let (id, patch, clock) = Transaction::initial("Create patch", &mut self.raw, signer, |tx| { tx.revision(description, base, oid)?; diff --git a/radicle/src/cob/store.rs b/radicle/src/cob/store.rs index 30ca1d16..54227d1a 100644 --- a/radicle/src/cob/store.rs +++ b/radicle/src/cob/store.rs @@ -128,21 +128,21 @@ impl Error { } /// Storage for collaborative objects of a specific type `T` in a single repository. -pub struct Store<'a, T> { +pub struct Store<'a, T, R> { identity: git::Oid, - repo: &'a storage::Repository, + repo: &'a R, witness: PhantomData, } -impl<'a, T> AsRef for Store<'a, T> { - fn as_ref(&self) -> &storage::Repository { +impl<'a, T, R> AsRef for Store<'a, T, R> { + fn as_ref(&self) -> &R { self.repo } } -impl<'a, T> Store<'a, T> { +impl<'a, T, R: ReadRepository> Store<'a, T, R> { /// Open a new generic store. - pub fn open(repo: &'a storage::Repository) -> Result { + pub fn open(repo: &'a R) -> Result { let identity = repo.identity()?; Ok(Self { @@ -153,8 +153,10 @@ impl<'a, T> Store<'a, T> { } } -impl<'a, T: FromHistory> Store<'a, T> +impl<'a, T, R> Store<'a, T, R> where + R: WriteRepository + cob::Store, + T: FromHistory, T::Action: Serialize, { /// Update an object. @@ -299,15 +301,16 @@ impl Transaction { } /// Create a new transaction to be used as the initial set of operations for a COB. - pub fn initial( + pub fn initial( message: &str, - store: &mut Store, + store: &mut Store, signer: &G, operations: F, ) -> Result<(ObjectId, T, Lamport), Error> where G: Signer, F: FnOnce(&mut Self) -> Result<(), Error>, + R: WriteRepository + cob::Store, T::Action: Serialize + Clone, { let actor = *signer.public_key(); @@ -339,14 +342,15 @@ impl Transaction { /// Commit transaction. /// /// Returns a list of operations that can be applied onto an in-memory CRDT. - pub fn commit( + pub fn commit( mut self, msg: &str, id: ObjectId, - store: &mut Store, + store: &mut Store, signer: &G, ) -> Result<(Vec>, Lamport, EntryId), Error> where + R: WriteRepository + cob::Store, T::Action: Serialize + Clone, { let actions = NonEmpty::from_vec(self.actions) diff --git a/radicle/src/storage.rs b/radicle/src/storage.rs index 36f3b2b9..aedf66ba 100644 --- a/radicle/src/storage.rs +++ b/radicle/src/storage.rs @@ -20,7 +20,7 @@ use crate::git::{refspec::Refspec, PatternString, Qualified, RefError, RefString use crate::identity; use crate::identity::doc::DocError; use crate::identity::Did; -use crate::identity::{Id, IdentityError}; +use crate::identity::{Id, Identity, IdentityError}; use crate::storage::git::NAMESPACES_GLOB; use crate::storage::refs::Refs; @@ -319,7 +319,7 @@ pub trait WriteStorage: ReadStorage { } /// Allows read-only access to a repository. -pub trait ReadRepository { +pub trait ReadRepository: Sized { /// Return the repository id. fn id(&self) -> Id; @@ -368,6 +368,13 @@ pub trait ReadRepository { /// `rad/id` using [`ReadRepository::canonical_identity_head`]. fn identity_head(&self) -> Result; + /// Load the canonical identity. + fn identity(&self) -> Result, IdentityError> { + let head = self.identity_head()?; + + Identity::load_at(head, self) + } + /// Compute the canonical `rad/id` of this repository. /// /// Ignores any existing `rad/id` reference. diff --git a/radicle/src/storage/git.rs b/radicle/src/storage/git.rs index 6a688bd8..2807c12b 100644 --- a/radicle/src/storage/git.rs +++ b/radicle/src/storage/git.rs @@ -291,12 +291,6 @@ impl Repository { Identity::load(remote, self) } - pub fn identity(&self) -> Result, IdentityError> { - let head = self.identity_head()?; - - Identity::load_at(head, self) - } - /// Get the canonical project information. pub fn project(&self) -> Result { let head = self.identity_head()?;