Handle errors correctly in storage
Signed-off-by: Alexis Sellier <alexis@radicle.xyz>
This commit is contained in:
parent
d652df7c30
commit
fde0af09f7
|
|
@ -851,7 +851,7 @@ pub trait ServiceState {
|
||||||
/// Get the current inventory.
|
/// Get the current inventory.
|
||||||
fn inventory(&self) -> Result<Inventory, storage::Error>;
|
fn inventory(&self) -> Result<Inventory, storage::Error>;
|
||||||
/// Get a project from storage, using the local node's key.
|
/// Get a project from storage, using the local node's key.
|
||||||
fn get(&self, proj: Id) -> Result<Option<Doc<Verified>>, storage::Error>;
|
fn get(&self, proj: Id) -> Result<Option<Doc<Verified>>, storage::ProjectError>;
|
||||||
/// Get the clock.
|
/// Get the clock.
|
||||||
fn clock(&self) -> &RefClock;
|
fn clock(&self) -> &RefClock;
|
||||||
/// Get service configuration.
|
/// Get service configuration.
|
||||||
|
|
@ -874,7 +874,7 @@ where
|
||||||
self.storage.inventory()
|
self.storage.inventory()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get(&self, proj: Id) -> Result<Option<Doc<Verified>>, storage::Error> {
|
fn get(&self, proj: Id) -> Result<Option<Doc<Verified>>, storage::ProjectError> {
|
||||||
self.storage.get(&self.node_id(), proj)
|
self.storage.get(&self.node_id(), proj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -944,6 +944,8 @@ pub enum LookupError {
|
||||||
Storage(#[from] storage::Error),
|
Storage(#[from] storage::Error),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Routing(#[from] routing::Error),
|
Routing(#[from] routing::Error),
|
||||||
|
#[error(transparent)]
|
||||||
|
Project(#[from] storage::ProjectError),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Information on a peer, that we may or may not be connected to.
|
/// Information on a peer, that we may or may not be connected to.
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ use crate::crypto;
|
||||||
use crate::crypto::{Signature, Unverified, Verified};
|
use crate::crypto::{Signature, Unverified, Verified};
|
||||||
use crate::git;
|
use crate::git;
|
||||||
use crate::identity::Did;
|
use crate::identity::Did;
|
||||||
|
use crate::storage;
|
||||||
use crate::storage::git::trailers;
|
use crate::storage::git::trailers;
|
||||||
use crate::storage::{BranchName, ReadRepository, RemoteId, WriteRepository, WriteStorage};
|
use crate::storage::{BranchName, ReadRepository, RemoteId, WriteRepository, WriteStorage};
|
||||||
|
|
||||||
|
|
@ -36,7 +37,7 @@ pub const MAX_STRING_LENGTH: usize = 255;
|
||||||
pub const MAX_DELEGATES: usize = 255;
|
pub const MAX_DELEGATES: usize = 255;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum Error {
|
pub enum DocError {
|
||||||
#[error("json: {0}")]
|
#[error("json: {0}")]
|
||||||
Json(#[from] serde_json::Error),
|
Json(#[from] serde_json::Error),
|
||||||
#[error("i/o: {0}")]
|
#[error("i/o: {0}")]
|
||||||
|
|
@ -47,6 +48,21 @@ pub enum Error {
|
||||||
Git(#[from] git::Error),
|
Git(#[from] git::Error),
|
||||||
#[error("git: {0}")]
|
#[error("git: {0}")]
|
||||||
RawGit(#[from] git2::Error),
|
RawGit(#[from] git2::Error),
|
||||||
|
#[error("storage: {0}")]
|
||||||
|
Storage(#[from] storage::Error),
|
||||||
|
#[error("git: reference `{0}` was not found")]
|
||||||
|
NotFound(git::RefString),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DocError {
|
||||||
|
/// Whether this error is caused by the document not being found.
|
||||||
|
pub fn is_not_found(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::NotFound(_) => true,
|
||||||
|
Self::Git(git::Error::NotFound(_)) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
|
@ -93,7 +109,7 @@ pub struct Doc<V> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Doc<Verified> {
|
impl Doc<Verified> {
|
||||||
pub fn encode(&self) -> Result<(git::Oid, Vec<u8>), Error> {
|
pub fn encode(&self) -> Result<(git::Oid, Vec<u8>), DocError> {
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
let mut serializer =
|
let mut serializer =
|
||||||
serde_json::Serializer::with_formatter(&mut buf, olpc_cjson::CanonicalFormatter::new());
|
serde_json::Serializer::with_formatter(&mut buf, olpc_cjson::CanonicalFormatter::new());
|
||||||
|
|
@ -118,7 +134,7 @@ impl Doc<Verified> {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sign<G: crypto::Signer>(&self, signer: G) -> Result<(git::Oid, Signature), Error> {
|
pub fn sign<G: crypto::Signer>(&self, signer: G) -> Result<(git::Oid, Signature), DocError> {
|
||||||
let (oid, bytes) = self.encode()?;
|
let (oid, bytes) = self.encode()?;
|
||||||
let sig = signer.sign(&bytes);
|
let sig = signer.sign(&bytes);
|
||||||
|
|
||||||
|
|
@ -130,7 +146,7 @@ impl Doc<Verified> {
|
||||||
remote: &RemoteId,
|
remote: &RemoteId,
|
||||||
msg: &str,
|
msg: &str,
|
||||||
storage: &S,
|
storage: &S,
|
||||||
) -> Result<(Id, git::Oid, S::Repository), Error> {
|
) -> Result<(Id, git::Oid, S::Repository), DocError> {
|
||||||
// You can checkout this branch in your working copy with:
|
// You can checkout this branch in your working copy with:
|
||||||
//
|
//
|
||||||
// git fetch rad
|
// git fetch rad
|
||||||
|
|
@ -138,7 +154,7 @@ impl Doc<Verified> {
|
||||||
//
|
//
|
||||||
let (doc_oid, doc) = self.encode()?;
|
let (doc_oid, doc) = self.encode()?;
|
||||||
let id = Id::from(doc_oid);
|
let id = Id::from(doc_oid);
|
||||||
let repo = storage.repository(id).unwrap();
|
let repo = storage.repository(id)?;
|
||||||
let tree = git::write_tree(*PATH, doc.as_slice(), repo.raw())?;
|
let tree = git::write_tree(*PATH, doc.as_slice(), repo.raw())?;
|
||||||
let oid = Doc::commit(remote, &tree, msg, &[], repo.raw())?;
|
let oid = Doc::commit(remote, &tree, msg, &[], repo.raw())?;
|
||||||
|
|
||||||
|
|
@ -153,7 +169,7 @@ impl Doc<Verified> {
|
||||||
msg: &str,
|
msg: &str,
|
||||||
signatures: &[(&PublicKey, Signature)],
|
signatures: &[(&PublicKey, Signature)],
|
||||||
repo: &R,
|
repo: &R,
|
||||||
) -> Result<git::Oid, Error> {
|
) -> Result<git::Oid, DocError> {
|
||||||
let mut msg = format!("{msg}\n\n");
|
let mut msg = format!("{msg}\n\n");
|
||||||
for (key, sig) in signatures {
|
for (key, sig) in signatures {
|
||||||
writeln!(&mut msg, "{}: {key} {sig}", trailers::SIGNATURE_TRAILER)
|
writeln!(&mut msg, "{}: {key} {sig}", trailers::SIGNATURE_TRAILER)
|
||||||
|
|
@ -175,7 +191,7 @@ impl Doc<Verified> {
|
||||||
msg: &str,
|
msg: &str,
|
||||||
parents: &[&git2::Commit],
|
parents: &[&git2::Commit],
|
||||||
repo: &git2::Repository,
|
repo: &git2::Repository,
|
||||||
) -> Result<git::Oid, Error> {
|
) -> Result<git::Oid, DocError> {
|
||||||
let sig = repo
|
let sig = repo
|
||||||
.signature()
|
.signature()
|
||||||
.or_else(|_| git2::Signature::now("radicle", remote.to_string().as_str()))?;
|
.or_else(|_| git2::Signature::now("radicle", remote.to_string().as_str()))?;
|
||||||
|
|
@ -320,48 +336,30 @@ impl Doc<Unverified> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn blob_at<R: ReadRepository>(
|
pub fn blob_at<R: ReadRepository>(commit: Oid, repo: &R) -> Result<git2::Blob, DocError> {
|
||||||
commit: Oid,
|
repo.blob_at(commit, Path::new(&*PATH))
|
||||||
repo: &R,
|
.map_err(DocError::from)
|
||||||
) -> Result<Option<git2::Blob>, git::Error> {
|
|
||||||
match repo.blob_at(commit, Path::new(&*PATH)) {
|
|
||||||
Err(git::ext::Error::NotFound(_)) => Ok(None),
|
|
||||||
Err(e) => Err(e),
|
|
||||||
Ok(blob) => Ok(Some(blob)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_at<R: ReadRepository>(
|
pub fn load_at<R: ReadRepository>(commit: Oid, repo: &R) -> Result<(Self, Oid), DocError> {
|
||||||
commit: Oid,
|
let blob = Self::blob_at(commit, repo)?;
|
||||||
repo: &R,
|
let doc = Doc::from_json(blob.content())?;
|
||||||
) -> Result<Option<(Self, Oid)>, git::Error> {
|
|
||||||
if let Some(blob) = Self::blob_at(commit, repo)? {
|
Ok((doc, blob.id().into()))
|
||||||
let doc = Doc::from_json(blob.content()).unwrap();
|
|
||||||
return Ok(Some((doc, blob.id().into())));
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load<R: ReadRepository>(
|
pub fn load<R: ReadRepository>(remote: &RemoteId, repo: &R) -> Result<(Self, Oid), DocError> {
|
||||||
remote: &RemoteId,
|
let oid = Self::head(remote, repo)?;
|
||||||
repo: &R,
|
|
||||||
) -> Result<Option<(Self, Oid)>, git::Error> {
|
Self::load_at(oid, repo)
|
||||||
if let Some(oid) = Self::head(remote, repo)? {
|
|
||||||
Self::load_at(oid, repo)
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V> Doc<V> {
|
impl<V> Doc<V> {
|
||||||
pub fn head<R: ReadRepository>(remote: &RemoteId, repo: &R) -> Result<Option<Oid>, git::Error> {
|
pub fn head<R: ReadRepository>(remote: &RemoteId, repo: &R) -> Result<Oid, DocError> {
|
||||||
let head = &git::refname!("heads").join(&*git::refs::IDENTITY_BRANCH);
|
let head = &git::refname!("heads").join(&*git::refs::IDENTITY_BRANCH);
|
||||||
if let Some(oid) = repo.reference_oid(remote, head)? {
|
repo.reference_oid(remote, head)?
|
||||||
Ok(Some(oid))
|
.ok_or_else(|| DocError::NotFound(head.to_owned()))
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -379,8 +377,8 @@ pub enum IdentityError {
|
||||||
InvalidSignature(PublicKey, crypto::Error),
|
InvalidSignature(PublicKey, crypto::Error),
|
||||||
#[error("quorum not reached: {0} signatures for a threshold of {1}")]
|
#[error("quorum not reached: {0} signatures for a threshold of {1}")]
|
||||||
QuorumNotReached(usize, usize),
|
QuorumNotReached(usize, usize),
|
||||||
#[error("the identity branch was not found")]
|
#[error("identity document error: {0}")]
|
||||||
NotFound,
|
Doc(#[from] DocError),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
|
@ -424,60 +422,58 @@ impl Identity<Untrusted> {
|
||||||
remote: &RemoteId,
|
remote: &RemoteId,
|
||||||
repo: &R,
|
repo: &R,
|
||||||
) -> Result<Identity<Oid>, IdentityError> {
|
) -> Result<Identity<Oid>, IdentityError> {
|
||||||
if let Some(head) = Doc::<Untrusted>::head(remote, repo)? {
|
let head = Doc::<Untrusted>::head(remote, repo)?;
|
||||||
let mut history = repo.revwalk(head)?.collect::<Vec<_>>();
|
let mut history = repo.revwalk(head)?.collect::<Vec<_>>();
|
||||||
|
|
||||||
// Retrieve root document.
|
// Retrieve root document.
|
||||||
let root_oid = history.pop().unwrap()?.into();
|
let root_oid = history.pop().unwrap()?.into();
|
||||||
let root_blob = Doc::blob_at(root_oid, repo)?.unwrap();
|
let root_blob = Doc::blob_at(root_oid, repo)?;
|
||||||
let root: git::Oid = root_blob.id().into();
|
let root: git::Oid = root_blob.id().into();
|
||||||
let trusted = Doc::from_json(root_blob.content()).unwrap();
|
let trusted = Doc::from_json(root_blob.content()).unwrap();
|
||||||
let revision = history.len() as u32;
|
let revision = history.len() as u32;
|
||||||
|
|
||||||
let mut trusted = trusted.verified()?;
|
let mut trusted = trusted.verified()?;
|
||||||
let mut current = root;
|
let mut current = root;
|
||||||
let mut signatures = Vec::new();
|
let mut signatures = Vec::new();
|
||||||
|
|
||||||
// Traverse the history chronologically.
|
// Traverse the history chronologically.
|
||||||
for oid in history.into_iter().rev() {
|
for oid in history.into_iter().rev() {
|
||||||
let oid = oid?;
|
let oid = oid?;
|
||||||
let blob = Doc::blob_at(oid.into(), repo)?.unwrap();
|
let blob = Doc::blob_at(oid.into(), repo)?;
|
||||||
let untrusted = Doc::from_json(blob.content()).unwrap();
|
let untrusted = Doc::from_json(blob.content()).map_err(DocError::from)?;
|
||||||
let untrusted = untrusted.verified()?;
|
let untrusted = untrusted.verified()?;
|
||||||
let commit = repo.commit(oid.into())?.unwrap();
|
let commit = repo.commit(oid.into())?.unwrap();
|
||||||
let msg = commit.message_raw().unwrap();
|
let msg = commit.message_raw().unwrap();
|
||||||
|
|
||||||
// Keys that signed the *current* document version.
|
// Keys that signed the *current* document version.
|
||||||
signatures = trailers::parse_signatures(msg).unwrap();
|
signatures = trailers::parse_signatures(msg).unwrap();
|
||||||
for (pk, sig) in &signatures {
|
for (pk, sig) in &signatures {
|
||||||
if let Err(err) = pk.verify(blob.content(), sig) {
|
if let Err(err) = pk.verify(blob.content(), sig) {
|
||||||
return Err(IdentityError::InvalidSignature(*pk, err));
|
return Err(IdentityError::InvalidSignature(*pk, err));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that enough delegates signed this next version.
|
|
||||||
let quorum = signatures
|
|
||||||
.iter()
|
|
||||||
.filter(|(key, _)| trusted.delegates.iter().any(|d| d.matches(key)))
|
|
||||||
.count();
|
|
||||||
if quorum < trusted.threshold {
|
|
||||||
return Err(IdentityError::QuorumNotReached(quorum, trusted.threshold));
|
|
||||||
}
|
|
||||||
|
|
||||||
trusted = untrusted;
|
|
||||||
current = blob.id().into();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(Identity {
|
// Check that enough delegates signed this next version.
|
||||||
root,
|
let quorum = signatures
|
||||||
head,
|
.iter()
|
||||||
current,
|
.filter(|(key, _)| trusted.delegates.iter().any(|d| d.matches(key)))
|
||||||
revision,
|
.count();
|
||||||
doc: trusted,
|
if quorum < trusted.threshold {
|
||||||
signatures: signatures.into_iter().collect(),
|
return Err(IdentityError::QuorumNotReached(quorum, trusted.threshold));
|
||||||
});
|
}
|
||||||
|
|
||||||
|
trusted = untrusted;
|
||||||
|
current = blob.id().into();
|
||||||
}
|
}
|
||||||
Err(IdentityError::NotFound)
|
|
||||||
|
Ok(Identity {
|
||||||
|
root,
|
||||||
|
head,
|
||||||
|
current,
|
||||||
|
revision,
|
||||||
|
doc: trusted,
|
||||||
|
signatures: signatures.into_iter().collect(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
#![allow(clippy::match_like_matches_macro)]
|
||||||
pub mod collections;
|
pub mod collections;
|
||||||
pub mod crypto;
|
pub mod crypto;
|
||||||
pub mod git;
|
pub mod git;
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,10 @@ use thiserror::Error;
|
||||||
|
|
||||||
use crate::crypto::{Signer, Verified};
|
use crate::crypto::{Signer, Verified};
|
||||||
use crate::git;
|
use crate::git;
|
||||||
|
use crate::identity::project::DocError;
|
||||||
use crate::identity::Id;
|
use crate::identity::Id;
|
||||||
use crate::node;
|
use crate::node;
|
||||||
|
use crate::storage::git::ProjectError;
|
||||||
use crate::storage::refs::SignedRefs;
|
use crate::storage::refs::SignedRefs;
|
||||||
use crate::storage::{BranchName, ReadRepository as _, RemoteId, WriteRepository as _};
|
use crate::storage::{BranchName, ReadRepository as _, RemoteId, WriteRepository as _};
|
||||||
use crate::{identity, storage};
|
use crate::{identity, storage};
|
||||||
|
|
@ -19,7 +21,7 @@ pub static REMOTE_NAME: Lazy<git::RefString> = Lazy::new(|| git::refname!("rad")
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum InitError {
|
pub enum InitError {
|
||||||
#[error("doc: {0}")]
|
#[error("doc: {0}")]
|
||||||
Doc(#[from] identity::project::Error),
|
Doc(#[from] identity::project::DocError),
|
||||||
#[error("doc: {0}")]
|
#[error("doc: {0}")]
|
||||||
DocVerification(#[from] identity::project::VerificationError),
|
DocVerification(#[from] identity::project::VerificationError),
|
||||||
#[error("git: {0}")]
|
#[error("git: {0}")]
|
||||||
|
|
@ -95,6 +97,8 @@ pub enum ForkError {
|
||||||
NotFound(Id),
|
NotFound(Id),
|
||||||
#[error("project identity error: {0}")]
|
#[error("project identity error: {0}")]
|
||||||
InvalidIdentity(#[from] storage::git::ProjectError),
|
InvalidIdentity(#[from] storage::git::ProjectError),
|
||||||
|
#[error("project identity document error: {0}")]
|
||||||
|
Doc(#[from] DocError),
|
||||||
#[error("git: invalid reference")]
|
#[error("git: invalid reference")]
|
||||||
InvalidReference,
|
InvalidReference,
|
||||||
}
|
}
|
||||||
|
|
@ -206,6 +210,8 @@ pub enum CloneError {
|
||||||
Fork(#[from] ForkError),
|
Fork(#[from] ForkError),
|
||||||
#[error("checkout: {0}")]
|
#[error("checkout: {0}")]
|
||||||
Checkout(#[from] CheckoutError),
|
Checkout(#[from] CheckoutError),
|
||||||
|
#[error("identity document error: {0}")]
|
||||||
|
Doc(#[from] DocError),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clone<P: AsRef<Path>, G: Signer, S: storage::WriteStorage, H: node::Handle>(
|
pub fn clone<P: AsRef<Path>, G: Signer, S: storage::WriteStorage, H: node::Handle>(
|
||||||
|
|
@ -258,6 +264,8 @@ pub enum CheckoutError {
|
||||||
Storage(#[from] storage::Error),
|
Storage(#[from] storage::Error),
|
||||||
#[error("project `{0}` was not found in storage")]
|
#[error("project `{0}` was not found in storage")]
|
||||||
NotFound(Id),
|
NotFound(Id),
|
||||||
|
#[error("project error: {0}")]
|
||||||
|
Project(#[from] ProjectError),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checkout a project from storage as a working copy.
|
/// Checkout a project from storage as a working copy.
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use std::{fmt, io};
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
pub use git::VerifyError;
|
pub use git::{ProjectError, VerifyError};
|
||||||
pub use radicle_git_ext::Oid;
|
pub use radicle_git_ext::Oid;
|
||||||
|
|
||||||
use crate::collections::HashMap;
|
use crate::collections::HashMap;
|
||||||
|
|
@ -20,7 +20,6 @@ use crate::git::Url;
|
||||||
use crate::git::{RefError, RefStr, RefString};
|
use crate::git::{RefError, RefStr, RefString};
|
||||||
use crate::identity;
|
use crate::identity;
|
||||||
use crate::identity::{Id, IdError};
|
use crate::identity::{Id, IdError};
|
||||||
use crate::storage::git::ProjectError;
|
|
||||||
use crate::storage::refs::Refs;
|
use crate::storage::refs::Refs;
|
||||||
|
|
||||||
use self::refs::SignedRefs;
|
use self::refs::SignedRefs;
|
||||||
|
|
@ -43,8 +42,6 @@ pub enum Error {
|
||||||
Id(#[from] IdError),
|
Id(#[from] IdError),
|
||||||
#[error("i/o: {0}")]
|
#[error("i/o: {0}")]
|
||||||
Io(#[from] io::Error),
|
Io(#[from] io::Error),
|
||||||
#[error("doc: {0}")]
|
|
||||||
Doc(#[from] identity::project::Error),
|
|
||||||
#[error("invalid repository head")]
|
#[error("invalid repository head")]
|
||||||
InvalidHead,
|
InvalidHead,
|
||||||
}
|
}
|
||||||
|
|
@ -59,6 +56,8 @@ pub enum FetchError {
|
||||||
Io(#[from] io::Error),
|
Io(#[from] io::Error),
|
||||||
#[error("verify: {0}")]
|
#[error("verify: {0}")]
|
||||||
Verify(#[from] git::VerifyError),
|
Verify(#[from] git::VerifyError),
|
||||||
|
#[error(transparent)]
|
||||||
|
Storage(#[from] Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type RemoteId = PublicKey;
|
pub type RemoteId = PublicKey;
|
||||||
|
|
@ -219,7 +218,11 @@ impl Remote<Verified> {
|
||||||
pub trait ReadStorage {
|
pub trait ReadStorage {
|
||||||
fn path(&self) -> &Path;
|
fn path(&self) -> &Path;
|
||||||
fn url(&self, proj: &Id) -> Url;
|
fn url(&self, proj: &Id) -> Url;
|
||||||
fn get(&self, remote: &RemoteId, proj: Id) -> Result<Option<identity::Doc<Verified>>, Error>;
|
fn get(
|
||||||
|
&self,
|
||||||
|
remote: &RemoteId,
|
||||||
|
proj: Id,
|
||||||
|
) -> Result<Option<identity::Doc<Verified>>, ProjectError>;
|
||||||
fn inventory(&self) -> Result<Inventory, Error>;
|
fn inventory(&self) -> Result<Inventory, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -281,7 +284,11 @@ where
|
||||||
self.deref().inventory()
|
self.deref().inventory()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get(&self, remote: &RemoteId, proj: Id) -> Result<Option<identity::Doc<Verified>>, Error> {
|
fn get(
|
||||||
|
&self,
|
||||||
|
remote: &RemoteId,
|
||||||
|
proj: Id,
|
||||||
|
) -> Result<Option<identity::Doc<Verified>>, ProjectError> {
|
||||||
self.deref().get(remote, proj)
|
self.deref().get(remote, proj)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,12 +28,19 @@ pub static REMOTES_GLOB: Lazy<refspec::PatternString> =
|
||||||
pub static SIGNATURES_GLOB: Lazy<refspec::PatternString> =
|
pub static SIGNATURES_GLOB: Lazy<refspec::PatternString> =
|
||||||
Lazy::new(|| refspec::pattern!("refs/remotes/*/radicle/signature"));
|
Lazy::new(|| refspec::pattern!("refs/remotes/*/radicle/signature"));
|
||||||
|
|
||||||
|
// FIXME: Should this be here?
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum ProjectError {
|
pub enum ProjectError {
|
||||||
#[error("identity branches diverge from each other")]
|
#[error("identity branches diverge from each other")]
|
||||||
BranchesDiverge,
|
BranchesDiverge,
|
||||||
#[error("identity branches are in an invalid state")]
|
#[error("identity branches are in an invalid state")]
|
||||||
InvalidState,
|
InvalidState,
|
||||||
|
#[error("storage error: {0}")]
|
||||||
|
Storage(#[from] Error),
|
||||||
|
#[error("identity document error: {0}")]
|
||||||
|
Doc(#[from] identity::project::DocError),
|
||||||
|
#[error("identity verification error: {0}")]
|
||||||
|
Verify(#[from] identity::project::VerificationError),
|
||||||
#[error("git: {0}")]
|
#[error("git: {0}")]
|
||||||
Git(#[from] git2::Error),
|
Git(#[from] git2::Error),
|
||||||
#[error("git: {0}")]
|
#[error("git: {0}")]
|
||||||
|
|
@ -42,6 +49,16 @@ pub enum ProjectError {
|
||||||
Refs(#[from] refs::Error),
|
Refs(#[from] refs::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ProjectError {
|
||||||
|
/// Whether this error is caused by the project not being found.
|
||||||
|
pub fn is_not_found(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Doc(doc) => doc.is_not_found(),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Storage {
|
pub struct Storage {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
@ -68,12 +85,15 @@ impl ReadStorage for Storage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get(&self, remote: &RemoteId, proj: Id) -> Result<Option<Doc<Verified>>, Error> {
|
fn get(&self, remote: &RemoteId, proj: Id) -> Result<Option<Doc<Verified>>, ProjectError> {
|
||||||
// TODO: Don't create a repo here if it doesn't exist?
|
// TODO: Don't create a repo here if it doesn't exist?
|
||||||
// Perhaps for checking we could have a `contains` method?
|
// Perhaps for checking we could have a `contains` method?
|
||||||
self.repository(proj)?
|
match self.repository(proj)?.project_of(remote) {
|
||||||
.project_of(remote)
|
Ok(doc) => Ok(Some(doc)),
|
||||||
.map_err(Error::from)
|
|
||||||
|
Err(err) if err.is_not_found() => Ok(None),
|
||||||
|
Err(err) => Err(err),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn inventory(&self) -> Result<Inventory, Error> {
|
fn inventory(&self) -> Result<Inventory, Error> {
|
||||||
|
|
@ -97,7 +117,7 @@ impl WriteStorage for Storage {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch(&self, proj_id: Id, remote: &Url) -> Result<Vec<RefUpdate>, FetchError> {
|
fn fetch(&self, proj_id: Id, remote: &Url) -> Result<Vec<RefUpdate>, FetchError> {
|
||||||
let mut repo = self.repository(proj_id).unwrap();
|
let mut repo = self.repository(proj_id)?;
|
||||||
let mut path = remote.path.clone();
|
let mut path = remote.path.clone();
|
||||||
|
|
||||||
path.push(b'/');
|
path.push(b'/');
|
||||||
|
|
@ -272,15 +292,11 @@ impl Repository {
|
||||||
Identity::load(remote, self)
|
Identity::load(remote, self)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn project_of(
|
pub fn project_of(&self, remote: &RemoteId) -> Result<identity::Doc<Verified>, ProjectError> {
|
||||||
&self,
|
let (doc, _) = identity::Doc::load(remote, self)?;
|
||||||
remote: &RemoteId,
|
let verified = doc.verified()?;
|
||||||
) -> Result<Option<identity::Doc<Verified>>, refs::Error> {
|
|
||||||
if let Some((doc, _)) = identity::Doc::load(remote, self)? {
|
Ok(verified)
|
||||||
Ok(Some(doc.verified().unwrap()))
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the canonical identity [`git::Oid`] and document.
|
/// Return the canonical identity [`git::Oid`] and document.
|
||||||
|
|
@ -288,7 +304,7 @@ impl Repository {
|
||||||
let mut heads = Vec::new();
|
let mut heads = Vec::new();
|
||||||
for remote in self.remote_ids()? {
|
for remote in self.remote_ids()? {
|
||||||
let remote = remote?;
|
let remote = remote?;
|
||||||
let oid = Doc::<Unverified>::head(&remote, self)?.unwrap();
|
let oid = Doc::<Unverified>::head(&remote, self)?;
|
||||||
|
|
||||||
heads.push(oid.into());
|
heads.push(oid.into());
|
||||||
}
|
}
|
||||||
|
|
@ -329,8 +345,7 @@ impl Repository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Doc::load_at(longest.into(), self)?
|
Doc::load_at(longest.into(), self)
|
||||||
.ok_or(refs::Error::NotFound)
|
|
||||||
.map(|(doc, _)| (longest.into(), doc))
|
.map(|(doc, _)| (longest.into(), doc))
|
||||||
.map_err(ProjectError::from)
|
.map_err(ProjectError::from)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,11 @@ impl ReadStorage for MockStorage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get(&self, _remote: &RemoteId, proj: Id) -> Result<Option<Doc<Verified>>, Error> {
|
fn get(
|
||||||
|
&self,
|
||||||
|
_remote: &RemoteId,
|
||||||
|
proj: Id,
|
||||||
|
) -> Result<Option<Doc<Verified>>, git::ProjectError> {
|
||||||
Ok(self.inventory.get(&proj).cloned())
|
Ok(self.inventory.get(&proj).cloned())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue