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.
This commit is contained in:
Alexis Sellier 2023-06-23 12:49:57 +02:00
parent 45ccbde226
commit c9162121b0
No known key found for this signature in database
16 changed files with 111 additions and 86 deletions

View File

@ -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<R: WriteRepository + cob::Store>(
issues: &Issues<R>,
assigned: &Option<Assigned>,
state: &Option<State>,
profile: &profile::Profile,
@ -544,12 +544,12 @@ fn prompt_issue(
Ok(Some((meta, description)))
}
fn open<G: Signer>(
fn open<R: WriteRepository + cob::Store, G: Signer>(
title: Option<String>,
description: Option<String>,
tags: Vec<Tag>,
options: &Options,
issues: &mut Issues,
issues: &mut Issues<R>,
signer: &G,
) -> anyhow::Result<()> {
let Some((meta, description)) = prompt_issue(
@ -579,8 +579,8 @@ fn open<G: Signer>(
Ok(())
}
fn edit<G: radicle::crypto::Signer>(
issues: &mut issue::Issues,
fn edit<R: WriteRepository + cob::Store, G: radicle::crypto::Signer>(
issues: &mut issue::Issues<R>,
signer: &G,
repo: &storage::git::Repository,
id: Rev,

View File

@ -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<I = crypto::PublicKey>
where
Self: object::Storage
Self: object::Storage<Identifier = I>
+ change::Storage<
StoreError = git::change::error::Create,
LoadError = git::change::error::Load,

View File

@ -48,7 +48,7 @@ impl Create {
///
/// The `args` are the metadata for this [`CollaborativeObject`]. See
/// [`Create`] for further information.
pub fn create<S, G>(
pub fn create<S, I, G>(
storage: &S,
signer: &G,
resource: Oid,
@ -57,7 +57,7 @@ pub fn create<S, G>(
args: Create,
) -> Result<CollaborativeObject, error::Create>
where
S: Store,
S: Store<I>,
G: crypto::Signer,
{
let Create { ref typename, .. } = &args;

View File

@ -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<S>(
pub fn get<S, I>(
storage: &S,
typename: &TypeName,
oid: &ObjectId,
) -> Result<Option<CollaborativeObject>, error::Retrieve>
where
S: Store,
S: Store<I>,
{
let tip_refs = storage
.objects(typename, oid)

View File

@ -11,12 +11,12 @@ use super::error;
/// [`Store`] for further information.
///
/// The `typename` is the type of objects to be listed.
pub fn list<S>(
pub fn list<S, I>(
storage: &S,
typename: &TypeName,
) -> Result<Vec<CollaborativeObject>, error::Retrieve>
where
S: Store,
S: Store<I>,
{
let references = storage
.types(typename)

View File

@ -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<S>(
pub fn remove<S, I>(
storage: &S,
identifier: &S::Identifier,
typename: &TypeName,
oid: &ObjectId,
) -> Result<(), error::Remove>
where
S: Store,
S: Store<I>,
{
storage
.remove(identifier, typename, oid)

View File

@ -49,7 +49,7 @@ pub struct Update {
///
/// The `args` are the metadata for this [`CollaborativeObject`]
/// udpate. See [`Update`] for further information.
pub fn update<S, G>(
pub fn update<S, I, G>(
storage: &S,
signer: &G,
resource: Oid,
@ -58,7 +58,7 @@ pub fn update<S, G>(
args: Update,
) -> Result<Updated, error::Update>
where
S: Store,
S: Store<I>,
G: crypto::Signer,
{
let Update {

View File

@ -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<Reference>);
impl Objects {

View File

@ -56,7 +56,7 @@ impl Storage {
}
}
impl Store for Storage {}
impl Store<Urn> for Storage {}
impl change::Storage for Storage {
type StoreError = <git2::Repository as change::Storage>::StoreError;

View File

@ -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};

View File

@ -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<Proposal> {
}
}
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<Self, store::Error> {
pub fn open(repository: &'a R) -> Result<Self, store::Error> {
let raw = store::Store::open(repository)?;
Ok(Self { raw })
@ -708,7 +714,7 @@ impl<'a> Proposals<'a> {
current: impl Into<Oid>,
proposed: Doc<Verified>,
signer: &G,
) -> Result<ProposalMut<'a, 'g>, Error> {
) -> Result<ProposalMut<'a, 'g, R>, 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<ProposalMut<'a, 'g>, store::Error> {
pub fn get_mut<'g>(
&'g mut self,
id: &ObjectId,
) -> Result<ProposalMut<'a, 'g, R>, store::Error> {
let (proposal, clock) = self
.raw
.get(id)?

View File

@ -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<Action>;
@ -331,14 +331,17 @@ impl store::Transaction<Issue> {
}
}
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<Self, store::Error> {
pub fn open(repository: &'a R) -> Result<Self, store::Error> {
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<IssueMut<'a, 'g>, store::Error> {
pub fn get_mut<'g>(&'g mut self, id: &ObjectId) -> Result<IssueMut<'a, 'g, R>, store::Error> {
let (issue, clock) = self
.raw
.get(id)?
@ -521,7 +527,7 @@ impl<'a> Issues<'a> {
tags: &[Tag],
assignees: &[ActorId],
signer: &G,
) -> Result<IssueMut<'a, 'g>, Error> {
) -> Result<IssueMut<'a, 'g, R>, Error> {
let (id, issue, clock) =
Transaction::initial("Create issue", &mut self.raw, signer, |tx| {
tx.thread(description)?;

View File

@ -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<Patch> {
}
}
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<Self, store::Error> {
pub fn open(repository: &'a R) -> Result<Self, store::Error> {
let raw = store::Store::open(repository)?;
Ok(Self { raw })
@ -1406,7 +1411,7 @@ impl<'a> Patches<'a> {
oid: impl Into<git::Oid>,
tags: &[Tag],
signer: &G,
) -> Result<PatchMut<'a, 'g>, Error> {
) -> Result<PatchMut<'a, 'g, R>, Error> {
self._create(
title,
description,
@ -1429,7 +1434,7 @@ impl<'a> Patches<'a> {
oid: impl Into<git::Oid>,
tags: &[Tag],
signer: &G,
) -> Result<PatchMut<'a, 'g>, Error> {
) -> Result<PatchMut<'a, 'g, R>, 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<PatchMut<'a, 'g>, store::Error> {
pub fn get_mut<'g>(&'g mut self, id: &ObjectId) -> Result<PatchMut<'a, 'g, R>, store::Error> {
let (patch, clock) = self
.raw
.get(id)?
@ -1531,7 +1536,7 @@ impl<'a> Patches<'a> {
tags: &[Tag],
state: State,
signer: &G,
) -> Result<PatchMut<'a, 'g>, Error> {
) -> Result<PatchMut<'a, 'g, R>, Error> {
let (id, patch, clock) =
Transaction::initial("Create patch", &mut self.raw, signer, |tx| {
tx.revision(description, base, oid)?;

View File

@ -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<T>,
}
impl<'a, T> AsRef<storage::Repository> for Store<'a, T> {
fn as_ref(&self) -> &storage::Repository {
impl<'a, T, R> AsRef<R> 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<Self, Error> {
pub fn open(repo: &'a R) -> Result<Self, Error> {
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<T: FromHistory> Transaction<T> {
}
/// Create a new transaction to be used as the initial set of operations for a COB.
pub fn initial<G, F>(
pub fn initial<R, G, F>(
message: &str,
store: &mut Store<T>,
store: &mut Store<T, R>,
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<T: FromHistory> Transaction<T> {
/// Commit transaction.
///
/// Returns a list of operations that can be applied onto an in-memory CRDT.
pub fn commit<G: Signer>(
pub fn commit<R, G: Signer>(
mut self,
msg: &str,
id: ObjectId,
store: &mut Store<T>,
store: &mut Store<T, R>,
signer: &G,
) -> Result<(Vec<cob::Op<T::Action>>, Lamport, EntryId), Error>
where
R: WriteRepository + cob::Store,
T::Action: Serialize + Clone,
{
let actions = NonEmpty::from_vec(self.actions)

View File

@ -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<Oid, IdentityError>;
/// Load the canonical identity.
fn identity(&self) -> Result<Identity<Oid>, 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.

View File

@ -291,12 +291,6 @@ impl Repository {
Identity::load(remote, self)
}
pub fn identity(&self) -> Result<Identity<Oid>, IdentityError> {
let head = self.identity_head()?;
Identity::load_at(head, self)
}
/// Get the canonical project information.
pub fn project(&self) -> Result<Project, IdentityError> {
let head = self.identity_head()?;