radicle-cob: remove Resource on Store

The `Store` trait had a paramter for a `Resource`. This is too
restrictive for implementations, particularly `heartwood`.

Instead, where a unique identifier is needed for the
`object::Storage`, i.e. in `create` and `update`, the identifier
becomes a parameter.

This allows the removal of passing the resource in `list`, `get` and
`changegraph`.

Signed-off-by: Fintan Halpenny <fintan.halpenny@gmail.com>
X-Clacks-Overhead: GNU Terry Pratchett
This commit is contained in:
Fintan Halpenny 2022-11-07 16:28:02 +00:00
parent 4112b0fdc5
commit d6416c9be1
No known key found for this signature in database
GPG Key ID: 2552FB6F64066CB7
11 changed files with 74 additions and 87 deletions

View File

@ -23,7 +23,4 @@ pub trait Identity {
/// expected to be the latest address for the identity at the time
/// of use.
fn content_id(&self) -> Oid;
/// The unique, stable identifier for the identity.
fn identifier(&self) -> Self::Identifier;
}

View File

@ -40,12 +40,15 @@
//! * [`change::Storage`] -- **Note**: there is already an
//! implementation for this for [`git2::Repository`] for convenience.
//!
//! The `Store` also takes a generic parameter to indicate the type of
//! resource the collaborative object is living inside, for example a
//! software project. This `Resource` must also implement
//! [`identity::Identity`] to allow the internal logic to reference
//! the resource's content-address in `git` as well as the stable
//! identifier used for the resource.
//! ## Resource
//!
//! The [`create`] and [`update`] functions take a `Resource`. It
//! represents the type of resource the collaborative objects are
//! relating to, for example a software project.
//!
//! This `Resource` must implement [`identity::Identity`] to allow the
//! internal logic to reference the resource's content-address in
//! `git` as well as the stable identifier used for the resource.
//!
//! ## History Traversal
//!
@ -111,16 +114,14 @@ mod tests;
/// for the specific `git` storage:
///
/// * [`object::Storage`]
/// * [`identity::Identity`] for `Resource`
///
/// **Note**: [`change::Storage`] is already implemented for
/// [`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<Resource>
pub trait Store
where
Resource: identity::Identity,
Self: object::Storage<Identifier = Resource::Identifier>
Self: object::Storage
+ change::Storage<
CreateError = git::change::error::Create,
LoadError = git::change::error::Load,

View File

@ -40,7 +40,11 @@ impl<Author> Create<Author> {
/// ensures that the objects origin is cryptographically verifiable.
///
/// The `resource` is the parent of this object, for example a
/// software project.
/// software project. Its content-address is stored in the object's
/// history.
///
/// The `identifier` is a unqiue id that is passed through to the
/// [`crate::object::Storage`].
///
/// The `args` are the metadata for this [`CollaborativeObject`]. See
/// [`Create`] for further information.
@ -48,10 +52,11 @@ pub fn create<S, Signer, Author, Resource>(
storage: &S,
signer: Signer,
resource: &Resource,
identifier: &S::Identifier,
args: Create<Author>,
) -> Result<CollaborativeObject, error::Create>
where
S: Store<Resource>,
S: Store,
Author: Identity,
Author::Identifier: Clone + PartialEq,
Resource: Identity,
@ -88,7 +93,7 @@ where
let object_id = init_change.id().into();
storage
.update(&resource.identifier(), typename, &object_id, &init_change)
.update(identifier, typename, &object_id, &init_change)
.map_err(|err| error::Create::Refs { err: Box::new(err) })?;
Ok(CollaborativeObject {

View File

@ -3,7 +3,7 @@
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
// Linking Exception. For full terms see the included LICENSE file.
use crate::{change_graph::ChangeGraph, identity, CollaborativeObject, ObjectId, Store, TypeName};
use crate::{change_graph::ChangeGraph, CollaborativeObject, ObjectId, Store, TypeName};
use super::error;
@ -13,23 +13,23 @@ use super::error;
/// [`crate::Change`]s at content-addressable locations. Please see
/// [`Store`] for further information.
///
/// The `resource` is the parent of this object, for example a
/// software project.
/// The `identifier` is a unqiue id that is passed through to the
/// [`crate::object::Storage`].
///
/// The `typename` is the type of object to be found, while the `oid`
/// is the identifier for the particular object under that type.
pub fn get<S, Resource>(
/// 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>(
storage: &S,
resource: &Resource,
identifier: &S::Identifier,
typename: &TypeName,
oid: &ObjectId,
) -> Result<Option<CollaborativeObject>, error::Retrieve>
where
S: Store<Resource>,
Resource: identity::Identity,
S: Store,
{
let tip_refs = storage
.objects(&resource.identifier(), typename, oid)
.objects(identifier, typename, oid)
.map_err(|err| error::Retrieve::Refs { err: Box::new(err) })?;
Ok(ChangeGraph::load(storage, tip_refs.iter(), typename, oid).map(|graph| graph.evaluate()))
}

View File

@ -14,7 +14,7 @@ use std::collections::BTreeSet;
use git_ext::Oid;
use crate::{change_graph::ChangeGraph, identity::Identity, ObjectId, Store, TypeName};
use crate::{change_graph::ChangeGraph, ObjectId, Store, TypeName};
use super::error;
@ -43,18 +43,17 @@ pub struct ChangeGraphInfo {
///
/// The `typename` is the type of object to be found, while the `oid`
/// is the identifier for the particular object under that type.
pub fn changegraph<S, Resource>(
pub fn changegraph<S>(
storage: &S,
resource: &Resource,
identifier: &S::Identifier,
typename: &TypeName,
oid: &ObjectId,
) -> Result<Option<ChangeGraphInfo>, error::Retrieve>
where
S: Store<Resource>,
Resource: Identity,
S: Store,
{
let tip_refs = storage
.objects(&resource.identifier(), typename, oid)
.objects(identifier, typename, oid)
.map_err(|err| error::Retrieve::Refs { err: Box::new(err) })?;
Ok(
ChangeGraph::load(storage, tip_refs.iter(), typename, oid).map(|graph| ChangeGraphInfo {

View File

@ -3,7 +3,7 @@
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
// Linking Exception. For full terms see the included LICENSE file.
use crate::{change_graph::ChangeGraph, identity::Identity, CollaborativeObject, Store, TypeName};
use crate::{change_graph::ChangeGraph, CollaborativeObject, Store, TypeName};
use super::error;
@ -13,21 +13,20 @@ use super::error;
/// [`crate::Change`]s at content-addressable locations. Please see
/// [`Store`] for further information.
///
/// The `resource` is the parent of this object, for example a
/// software project.
/// The `identifier` is a unqiue id that is passed through to the
/// [`crate::object::Storage`].
///
/// The `typename` is the type of objects to listed.
pub fn list<S, Resource>(
pub fn list<S>(
storage: &S,
resource: &Resource,
identifier: &S::Identifier,
typename: &TypeName,
) -> Result<Vec<CollaborativeObject>, error::Retrieve>
where
S: Store<Resource>,
Resource: Identity,
S: Store,
{
let references = storage
.types(&resource.identifier(), typename)
.types(identifier, typename)
.map_err(|err| error::Retrieve::Refs { err: Box::new(err) })?;
log::trace!("loaded {} references", references.len());
let mut result = Vec::new();

View File

@ -12,7 +12,7 @@ use super::error;
/// The data required to update an object
pub struct Update<Author> {
/// The identity of the author for this object's first change.
/// The identity of the author for the update of this object.
pub author: Option<Author>,
/// The CRDT changes to add to the object.
pub changes: Contents,
@ -34,7 +34,11 @@ pub struct Update<Author> {
/// ensures that the objects origin is cryptographically verifiable.
///
/// The `resource` is the parent of this object, for example a
/// software project.
/// software project. Its content-address is stored in the
/// object's history.
///
/// The `identifier` is a unqiue id that is passed through to the
/// [`crate::object::Storage`].
///
/// The `args` are the metadata for this [`CollaborativeObject`]
/// udpate. See [`Update`] for further information.
@ -42,10 +46,11 @@ pub fn update<S, Signer, Resource, Author>(
storage: &S,
signer: Signer,
resource: &Resource,
identifier: &S::Identifier,
args: Update<Author>,
) -> Result<CollaborativeObject, error::Update>
where
S: Store<Resource>,
S: Store,
Author: Identity,
Author::Identifier: Clone + PartialEq,
Resource: Identity,
@ -71,7 +76,7 @@ where
};
let existing_refs = storage
.objects(&resource.identifier(), typename, &object_id)
.objects(identifier, typename, &object_id)
.map_err(|err| error::Update::Refs { err: Box::new(err) })?;
let mut object = ChangeGraph::load(storage, existing_refs.iter(), typename, &object_id)
@ -94,7 +99,7 @@ where
.history
.extend(change.id, content, change.resource, changes);
storage
.update(&resource.identifier(), typename, &object_id, &change)
.update(identifier, typename, &object_id, &change)
.map_err(|err| error::Update::Refs { err: Box::new(err) })?;
Ok(object)

View File

@ -102,11 +102,4 @@ impl Identity for Person {
fn content_id(&self) -> Oid {
self.content_id
}
fn identifier(&self) -> Self::Identifier {
Urn {
name: self.name().clone(),
remote: None,
}
}
}

View File

@ -14,6 +14,15 @@ pub struct RemoteProject {
pub person: test::Person,
}
impl RemoteProject {
pub fn identifier(&self) -> Urn {
Urn {
name: self.project.name().clone(),
remote: Some(self.person.name().clone()),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Project {
pub payload: Payload,
@ -104,25 +113,6 @@ fn from_commit(
}))
}
impl Identity for Project {
type Identifier = Urn;
fn is_delegate(&self, delegation: &crypto::PublicKey) -> bool {
self.delegates().contains(delegation)
}
fn content_id(&self) -> Oid {
self.content_id
}
fn identifier(&self) -> Self::Identifier {
Urn {
name: self.name().clone(),
remote: None,
}
}
}
impl Identity for RemoteProject {
type Identifier = Urn;
@ -133,11 +123,4 @@ impl Identity for RemoteProject {
fn content_id(&self) -> Oid {
self.project.content_id
}
fn identifier(&self) -> Self::Identifier {
Urn {
name: self.project.name().clone(),
remote: Some(self.person.name().clone()),
}
}
}

View File

@ -8,7 +8,7 @@ use crate::{
ObjectId, Store,
};
use super::identity::{RemoteProject, Urn};
use super::identity::Urn;
pub mod error {
use thiserror::Error;
@ -60,7 +60,7 @@ impl Storage {
}
}
impl Store<RemoteProject> for Storage {}
impl Store for Storage {}
impl change::Storage for Storage {
type CreateError = <git2::Repository as change::Storage>::CreateError;

View File

@ -4,9 +4,7 @@ use crypto::test::signer::MockSigner;
use quickcheck::Arbitrary;
use radicle_crypto::Signer;
use crate::{
create, get, history, identity::Identity, list, update, Create, ObjectId, TypeName, Update,
};
use crate::{create, get, history, list, update, Create, ObjectId, TypeName, Update};
use super::test;
@ -26,6 +24,7 @@ fn roundtrip() {
&storage,
signer,
&proj,
&proj.identifier(),
Create {
author: Some(terry),
contents,
@ -35,7 +34,7 @@ fn roundtrip() {
)
.unwrap();
let expected = get(&storage, &proj, &typename, cob.id())
let expected = get(&storage, &proj.identifier(), &typename, cob.id())
.unwrap()
.expect("BUG: cob was missing");
@ -57,6 +56,7 @@ fn list_cobs() {
&storage,
signer.clone(),
&proj,
&proj.identifier(),
Create {
author: Some(terry.clone()),
contents: history::Contents::Automerge(b"issue 1".to_vec()),
@ -70,6 +70,7 @@ fn list_cobs() {
&storage,
signer,
&proj,
&proj.identifier(),
Create {
author: Some(terry),
contents: history::Contents::Automerge(b"issue 2".to_vec()),
@ -79,7 +80,7 @@ fn list_cobs() {
)
.unwrap();
let mut expected = list(&storage, &proj, &typename).unwrap();
let mut expected = list(&storage, &proj.identifier(), &typename).unwrap();
expected.sort_by(|x, y| x.id().cmp(y.id()));
let mut actual = vec![issue_1, issue_2];
@ -104,6 +105,7 @@ fn update_cob() {
&storage,
signer.clone(),
&proj,
&proj.identifier(),
Create {
author: Some(terry.clone()),
contents,
@ -113,7 +115,7 @@ fn update_cob() {
)
.unwrap();
let not_expected = get(&storage, &proj, &typename, cob.id())
let not_expected = get(&storage, &proj.identifier(), &typename, cob.id())
.unwrap()
.expect("BUG: cob was missing");
@ -121,6 +123,7 @@ fn update_cob() {
&storage,
signer,
&proj,
&proj.identifier(),
Update {
author: Some(terry),
changes: history::Contents::Automerge(b"issue 1".to_vec()),
@ -131,7 +134,7 @@ fn update_cob() {
)
.unwrap();
let expected = get(&storage, &proj, &typename, updated.id())
let expected = get(&storage, &proj.identifier(), &typename, updated.id())
.unwrap()
.expect("BUG: cob was missing");
@ -160,6 +163,7 @@ fn traverse_cobs() {
&storage,
terry_signer,
&terry_proj,
&terry_proj.identifier(),
Create {
author: Some(terry),
contents: history::Contents::Automerge(b"issue 1".to_vec()),
@ -181,6 +185,7 @@ fn traverse_cobs() {
&storage,
neil_signer,
&neil_proj,
&neil_proj.identifier(),
Update {
author: Some(neil),
changes: history::Contents::Automerge(b"issue 2".to_vec()),