Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
node: Move doc stuff to its own module
Alexis Sellier committed 3 years ago
commit 73d62de0cc7af246093147bb00e4f4548b0c1808
parent 3e9a4eedaed767999de09fd51754a132dc7c2e61
6 files changed +232 -212
modified node/src/identity.rs
@@ -1,22 +1,19 @@
-
use std::marker::PhantomData;
+
pub mod doc;
+

use std::ops::Deref;
-
use std::path::{Path, PathBuf};
-
use std::{ffi::OsString, fmt, io, str::FromStr};
+
use std::path::PathBuf;
+
use std::{ffi::OsString, fmt, str::FromStr};

-
use nonempty::NonEmpty;
-
use once_cell::sync::Lazy;
-
use radicle_git_ext::Oid;
use serde::{Deserialize, Serialize};
use thiserror::Error;

-
use crate::crypto::{self, Unverified, Verified};
+
use crate::crypto::{self, Verified};
use crate::hash;
use crate::serde_ext;
-
use crate::storage::{BranchName, Remotes};
+
use crate::storage::Remotes;

pub use crypto::PublicKey;
-

-
pub static IDENTITY_PATH: Lazy<&Path> = Lazy::new(|| Path::new("radicle.json"));
+
pub use doc::{Delegate, Doc};

#[derive(Error, Debug)]
pub enum IdError {
@@ -170,193 +167,6 @@ pub struct Project {
    pub path: PathBuf,
}

-
#[derive(Error, Debug)]
-
pub enum DocError {
-
    #[error("json: {0}")]
-
    Json(#[from] serde_json::Error),
-
    #[error("i/o: {0}")]
-
    Io(#[from] io::Error),
-
}
-

-
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
-
pub struct Delegate {
-
    pub name: String,
-
    pub id: Did,
-
}
-

-
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
-
#[serde(rename_all = "kebab-case")]
-
pub struct Doc<V> {
-
    pub name: String,
-
    pub description: String,
-
    pub default_branch: String,
-
    pub version: u32,
-
    pub parent: Option<Oid>,
-
    pub delegates: NonEmpty<Delegate>,
-
    pub threshold: usize,
-

-
    verified: PhantomData<V>,
-
}
-

-
impl Doc<Verified> {
-
    pub fn write<W: io::Write>(&self, mut writer: W) -> Result<Id, DocError> {
-
        let mut buf = Vec::new();
-
        let mut ser =
-
            serde_json::Serializer::with_formatter(&mut buf, olpc_cjson::CanonicalFormatter::new());
-

-
        self.serialize(&mut ser)?;
-

-
        let digest = hash::Digest::new(&buf);
-
        let id = Id::from(digest);
-

-
        // TODO: Currently, we serialize the document in canonical JSON. This isn't strictly
-
        // necessary, as we could use CJSON just to get the hash, but then write a prettified
-
        // version to disk to make it easier for users to edit.
-
        writer.write_all(&buf)?;
-

-
        Ok(id)
-
    }
-
}
-

-
pub const MAX_STRING_LENGTH: usize = 255;
-
pub const MAX_DELEGATES: usize = 255;
-

-
#[derive(Error, Debug)]
-
pub enum DocVerificationError {
-
    #[error("invalid name: {0}")]
-
    Name(&'static str),
-
    #[error("invalid description: {0}")]
-
    Description(&'static str),
-
    #[error("invalid default branch: {0}")]
-
    DefaultBranch(&'static str),
-
    #[error("invalid delegates: {0}")]
-
    Delegates(&'static str),
-
    #[error("invalid version `{0}`")]
-
    Version(u32),
-
    #[error("invalid parent: {0}")]
-
    Parent(&'static str),
-
    #[error("invalid threshold `{0}`: {1}")]
-
    Threshold(usize, &'static str),
-
}
-

-
impl Doc<Unverified> {
-
    pub fn initial(
-
        name: String,
-
        description: String,
-
        default_branch: BranchName,
-
        delegate: Delegate,
-
    ) -> Self {
-
        Self {
-
            name,
-
            description,
-
            default_branch,
-
            version: 1,
-
            parent: None,
-
            delegates: NonEmpty::new(delegate),
-
            threshold: 1,
-
            verified: PhantomData,
-
        }
-
    }
-

-
    pub fn new(
-
        name: String,
-
        description: String,
-
        default_branch: BranchName,
-
        parent: Option<Oid>,
-
        delegates: NonEmpty<Delegate>,
-
        threshold: usize,
-
    ) -> Self {
-
        Self {
-
            name,
-
            description,
-
            default_branch,
-
            version: 1,
-
            parent,
-
            delegates,
-
            threshold,
-
            verified: PhantomData,
-
        }
-
    }
-

-
    pub fn from_json(bytes: &[u8]) -> Result<Self, serde_json::Error> {
-
        serde_json::from_slice(bytes)
-
    }
-

-
    pub fn verified(self) -> Result<Doc<Verified>, DocVerificationError> {
-
        if self.name.is_empty() {
-
            return Err(DocVerificationError::Name("name cannot be empty"));
-
        }
-
        if self.name.len() > MAX_STRING_LENGTH {
-
            return Err(DocVerificationError::Name("name cannot exceed 255 bytes"));
-
        }
-
        if self.description.len() > MAX_STRING_LENGTH {
-
            return Err(DocVerificationError::Description(
-
                "description cannot exceed 255 bytes",
-
            ));
-
        }
-
        if self.delegates.len() > MAX_DELEGATES {
-
            return Err(DocVerificationError::Delegates(
-
                "number of delegates cannot exceed 255",
-
            ));
-
        }
-
        if self
-
            .delegates
-
            .iter()
-
            .any(|d| d.name.is_empty() || d.name.len() > MAX_STRING_LENGTH)
-
        {
-
            return Err(DocVerificationError::Delegates(
-
                "delegate name must not be empty and must not exceed 255 bytes",
-
            ));
-
        }
-
        if self.delegates.is_empty() {
-
            return Err(DocVerificationError::Delegates(
-
                "delegate list cannot be empty",
-
            ));
-
        }
-
        if self.default_branch.is_empty() {
-
            return Err(DocVerificationError::DefaultBranch(
-
                "default branch cannot be empty",
-
            ));
-
        }
-
        if self.default_branch.len() > MAX_STRING_LENGTH {
-
            return Err(DocVerificationError::DefaultBranch(
-
                "default branch cannot exceed 255 bytes",
-
            ));
-
        }
-
        if let Some(parent) = self.parent {
-
            if parent.is_zero() {
-
                return Err(DocVerificationError::Parent("parent cannot be zero"));
-
            }
-
        }
-
        if self.version != 1 {
-
            return Err(DocVerificationError::Version(self.version));
-
        }
-
        if self.threshold > self.delegates.len() {
-
            return Err(DocVerificationError::Threshold(
-
                self.threshold,
-
                "threshold cannot exceed number of delegates",
-
            ));
-
        }
-
        if self.threshold == 0 {
-
            return Err(DocVerificationError::Threshold(
-
                self.threshold,
-
                "threshold cannot be zero",
-
            ));
-
        }
-

-
        Ok(Doc {
-
            name: self.name,
-
            description: self.description,
-
            delegates: self.delegates,
-
            default_branch: self.default_branch,
-
            parent: self.parent,
-
            version: self.version,
-
            threshold: self.threshold,
-
            verified: PhantomData,
-
        })
-
    }
-
}
-

#[cfg(test)]
mod test {
    use super::*;
@@ -365,14 +175,6 @@ mod test {
    use std::collections::HashSet;

    #[quickcheck]
-
    fn prop_encode_decode(doc: Doc<Verified>) {
-
        let mut bytes = Vec::new();
-

-
        doc.write(&mut bytes).unwrap();
-
        assert_eq!(Doc::from_json(&bytes).unwrap().verified().unwrap(), doc);
-
    }
-

-
    #[quickcheck]
    fn prop_key_equality(a: PublicKey, b: PublicKey) {
        assert_ne!(a, b);

added node/src/identity/doc.rs
@@ -0,0 +1,218 @@
+
use std::io;
+
use std::marker::PhantomData;
+
use std::path::Path;
+

+
use nonempty::NonEmpty;
+
use once_cell::sync::Lazy;
+
use radicle_git_ext::Oid;
+
use serde::{Deserialize, Serialize};
+
use thiserror::Error;
+

+
use crate::crypto::{self, Unverified, Verified};
+
use crate::hash;
+
use crate::identity::{Did, Id};
+
use crate::storage::BranchName;
+

+
pub use crypto::PublicKey;
+

+
pub static PATH: Lazy<&Path> = Lazy::new(|| Path::new("radicle.json"));
+
pub const MAX_STRING_LENGTH: usize = 255;
+
pub const MAX_DELEGATES: usize = 255;
+

+
#[derive(Error, Debug)]
+
pub enum Error {
+
    #[error("json: {0}")]
+
    Json(#[from] serde_json::Error),
+
    #[error("i/o: {0}")]
+
    Io(#[from] io::Error),
+
}
+

+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
+
pub struct Delegate {
+
    pub name: String,
+
    pub id: Did,
+
}
+

+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+
#[serde(rename_all = "kebab-case")]
+
pub struct Doc<V> {
+
    pub name: String,
+
    pub description: String,
+
    pub default_branch: String,
+
    pub version: u32,
+
    pub parent: Option<Oid>,
+
    pub delegates: NonEmpty<Delegate>,
+
    pub threshold: usize,
+

+
    verified: PhantomData<V>,
+
}
+

+
impl Doc<Verified> {
+
    pub fn write<W: io::Write>(&self, mut writer: W) -> Result<Id, Error> {
+
        let mut buf = Vec::new();
+
        let mut ser =
+
            serde_json::Serializer::with_formatter(&mut buf, olpc_cjson::CanonicalFormatter::new());
+

+
        self.serialize(&mut ser)?;
+

+
        let digest = hash::Digest::new(&buf);
+
        let id = Id::from(digest);
+

+
        // TODO: Currently, we serialize the document in canonical JSON. This isn't strictly
+
        // necessary, as we could use CJSON just to get the hash, but then write a prettified
+
        // version to disk to make it easier for users to edit.
+
        writer.write_all(&buf)?;
+

+
        Ok(id)
+
    }
+
}
+

+
#[derive(Error, Debug)]
+
pub enum VerificationError {
+
    #[error("invalid name: {0}")]
+
    Name(&'static str),
+
    #[error("invalid description: {0}")]
+
    Description(&'static str),
+
    #[error("invalid default branch: {0}")]
+
    DefaultBranch(&'static str),
+
    #[error("invalid delegates: {0}")]
+
    Delegates(&'static str),
+
    #[error("invalid version `{0}`")]
+
    Version(u32),
+
    #[error("invalid parent: {0}")]
+
    Parent(&'static str),
+
    #[error("invalid threshold `{0}`: {1}")]
+
    Threshold(usize, &'static str),
+
}
+

+
impl Doc<Unverified> {
+
    pub fn initial(
+
        name: String,
+
        description: String,
+
        default_branch: BranchName,
+
        delegate: Delegate,
+
    ) -> Self {
+
        Self {
+
            name,
+
            description,
+
            default_branch,
+
            version: 1,
+
            parent: None,
+
            delegates: NonEmpty::new(delegate),
+
            threshold: 1,
+
            verified: PhantomData,
+
        }
+
    }
+

+
    pub fn new(
+
        name: String,
+
        description: String,
+
        default_branch: BranchName,
+
        parent: Option<Oid>,
+
        delegates: NonEmpty<Delegate>,
+
        threshold: usize,
+
    ) -> Self {
+
        Self {
+
            name,
+
            description,
+
            default_branch,
+
            version: 1,
+
            parent,
+
            delegates,
+
            threshold,
+
            verified: PhantomData,
+
        }
+
    }
+

+
    pub fn from_json(bytes: &[u8]) -> Result<Self, serde_json::Error> {
+
        serde_json::from_slice(bytes)
+
    }
+

+
    pub fn verified(self) -> Result<Doc<Verified>, VerificationError> {
+
        if self.name.is_empty() {
+
            return Err(VerificationError::Name("name cannot be empty"));
+
        }
+
        if self.name.len() > MAX_STRING_LENGTH {
+
            return Err(VerificationError::Name("name cannot exceed 255 bytes"));
+
        }
+
        if self.description.len() > MAX_STRING_LENGTH {
+
            return Err(VerificationError::Description(
+
                "description cannot exceed 255 bytes",
+
            ));
+
        }
+
        if self.delegates.len() > MAX_DELEGATES {
+
            return Err(VerificationError::Delegates(
+
                "number of delegates cannot exceed 255",
+
            ));
+
        }
+
        if self
+
            .delegates
+
            .iter()
+
            .any(|d| d.name.is_empty() || d.name.len() > MAX_STRING_LENGTH)
+
        {
+
            return Err(VerificationError::Delegates(
+
                "delegate name must not be empty and must not exceed 255 bytes",
+
            ));
+
        }
+
        if self.delegates.is_empty() {
+
            return Err(VerificationError::Delegates(
+
                "delegate list cannot be empty",
+
            ));
+
        }
+
        if self.default_branch.is_empty() {
+
            return Err(VerificationError::DefaultBranch(
+
                "default branch cannot be empty",
+
            ));
+
        }
+
        if self.default_branch.len() > MAX_STRING_LENGTH {
+
            return Err(VerificationError::DefaultBranch(
+
                "default branch cannot exceed 255 bytes",
+
            ));
+
        }
+
        if let Some(parent) = self.parent {
+
            if parent.is_zero() {
+
                return Err(VerificationError::Parent("parent cannot be zero"));
+
            }
+
        }
+
        if self.version != 1 {
+
            return Err(VerificationError::Version(self.version));
+
        }
+
        if self.threshold > self.delegates.len() {
+
            return Err(VerificationError::Threshold(
+
                self.threshold,
+
                "threshold cannot exceed number of delegates",
+
            ));
+
        }
+
        if self.threshold == 0 {
+
            return Err(VerificationError::Threshold(
+
                self.threshold,
+
                "threshold cannot be zero",
+
            ));
+
        }
+

+
        Ok(Doc {
+
            name: self.name,
+
            description: self.description,
+
            delegates: self.delegates,
+
            default_branch: self.default_branch,
+
            parent: self.parent,
+
            version: self.version,
+
            threshold: self.threshold,
+
            verified: PhantomData,
+
        })
+
    }
+
}
+

+
#[cfg(test)]
+
mod test {
+
    use super::*;
+
    use quickcheck_macros::quickcheck;
+

+
    #[quickcheck]
+
    fn prop_encode_decode(doc: Doc<Verified>) {
+
        let mut bytes = Vec::new();
+

+
        doc.write(&mut bytes).unwrap();
+
        assert_eq!(Doc::from_json(&bytes).unwrap().verified().unwrap(), doc);
+
    }
+
}
modified node/src/rad.rs
@@ -16,9 +16,9 @@ pub const REMOTE_NAME: &str = "rad";
#[derive(Error, Debug)]
pub enum InitError {
    #[error("doc: {0}")]
-
    Doc(#[from] identity::DocError),
+
    Doc(#[from] identity::doc::Error),
    #[error("doc: {0}")]
-
    DocVerification(#[from] identity::DocVerificationError),
+
    DocVerification(#[from] identity::doc::VerificationError),
    #[error("git: {0}")]
    Git(#[from] git2::Error),
    #[error("i/o: {0}")]
@@ -56,7 +56,7 @@ pub fn init<'r, G: Signer, S: storage::WriteStorage<'r>>(
    )
    .verified()?;

-
    let filename = *identity::IDENTITY_PATH;
+
    let filename = *identity::doc::PATH;
    let mut doc_bytes = Vec::new();
    let id = doc.write(&mut doc_bytes)?;
    let project = storage.repository(&id)?;
modified node/src/storage.rs
@@ -42,7 +42,7 @@ pub enum Error {
    #[error("i/o: {0}")]
    Io(#[from] io::Error),
    #[error("doc: {0}")]
-
    Doc(#[from] identity::DocError),
+
    Doc(#[from] identity::doc::Error),
    #[error("invalid repository head")]
    InvalidHead,
}
modified node/src/storage/git.rs
@@ -9,7 +9,7 @@ pub use radicle_git_ext::Oid;

use crate::crypto::{Signer, Verified};
use crate::git;
-
use crate::identity::{self, IDENTITY_PATH};
+
use crate::identity;
use crate::identity::{Id, Project};
use crate::storage::refs;
use crate::storage::refs::{Refs, SignedRefs};
@@ -263,7 +263,7 @@ impl Repository {
            return Ok(None);
        };

-
        let doc = match self.blob_at(oid, Path::new(&*IDENTITY_PATH)) {
+
        let doc = match self.blob_at(oid, Path::new(&*identity::doc::PATH)) {
            Err(git::ext::Error::NotFound(_)) => return Ok(None),
            Err(e) => return Err(e.into()),
            Ok(doc) => doc,
modified node/src/test/arbitrary.rs
@@ -14,7 +14,7 @@ use crate::crypto;
use crate::crypto::{PublicKey, SecretKey, Signer, Unverified, Verified};
use crate::git;
use crate::hash;
-
use crate::identity::{Delegate, Did, Doc, Id, Project};
+
use crate::identity::{doc::Delegate, doc::Doc, Did, Id, Project};
use crate::service::filter::{Filter, FILTER_SIZE};
use crate::service::message::{
    Address, Envelope, InventoryAnnouncement, Message, NodeAnnouncement, RefsAnnouncement,