From 0728ee1f66944c61c1cfcb06f9fce0af2e6d8c11 Mon Sep 17 00:00:00 2001 From: Fintan Halpenny Date: Fri, 4 Nov 2022 09:07:50 +0000 Subject: [PATCH] radicle-git-ext: remove multihash and minicbor Multihash was deemed as not needed for compatibility since it is possible to detect a change from SHA1 to SHA256 based on their encoded lengths. The YAGNI principle is executed and multihash (and minicbor while we're here) are removed. This also had an effect on `Urn` which relied heavily on encoding the value using multihash. The `urn` and `namespace` modules were removed from radicle-git-types, since they were likely not going to be used anyway. Signed-off-by: Fintan Halpenny X-Clacks-Overhead: GNU Terry Pratchett --- radicle-git-ext/Cargo.toml | 15 +- radicle-git-ext/src/oid.rs | 82 ----- radicle-git-ext/src/reference/name.rs | 74 ----- radicle-git-ext/t/Cargo.toml | 2 +- radicle-git-ext/t/src/tests.rs | 27 -- radicle-git-types/Cargo.toml | 1 - radicle-git-types/src/lib.rs | 19 +- radicle-git-types/src/namespace.rs | 105 ------ radicle-git-types/src/urn.rs | 447 -------------------------- 9 files changed, 18 insertions(+), 754 deletions(-) delete mode 100644 radicle-git-types/src/namespace.rs delete mode 100644 radicle-git-types/src/urn.rs diff --git a/radicle-git-ext/Cargo.toml b/radicle-git-ext/Cargo.toml index e5aa616..6a12882 100644 --- a/radicle-git-ext/Cargo.toml +++ b/radicle-git-ext/Cargo.toml @@ -1,8 +1,11 @@ [package] name = "radicle-git-ext" -version = "0.1.0" -authors = ["The Radicle Team "] -edition = "2018" +version = "0.2.0" +authors = [ + "Kim Altintop ", + "Fintan Halpenny Multihash { - self.into() - } -} - #[cfg(feature = "serde")] mod serde_impls { use super::*; @@ -64,35 +55,6 @@ mod serde_impls { } } -#[cfg(feature = "minicbor")] -mod minicbor_impls { - use super::*; - use minicbor::{ - decode, - encode::{self, Write}, - Decode, - Decoder, - Encode, - Encoder, - }; - - impl Encode for Oid { - fn encode(&self, e: &mut Encoder) -> Result<(), encode::Error> { - e.bytes(Multihash::from(self).as_bytes())?; - Ok(()) - } - } - - impl<'b> Decode<'b> for Oid { - fn decode(d: &mut Decoder) -> Result { - let bytes = d.bytes()?; - let mhash = MultihashRef::from_slice(bytes) - .or(Err(decode::Error::Message("not a multihash")))?; - Self::try_from(mhash).or(Err(decode::Error::Message("not a git oid"))) - } - } -} - impl Deref for Oid { type Target = git2::Oid; @@ -147,38 +109,6 @@ impl FromStr for Oid { } } -#[derive(Debug, Error)] -#[non_exhaustive] -pub enum FromMultihashError { - #[error("invalid hash algorithm: expected Sha1, got {actual:?}")] - AlgorithmMismatch { actual: multihash::Code }, - - #[error(transparent)] - Git(#[from] git2::Error), -} - -impl TryFrom for Oid { - type Error = FromMultihashError; - - fn try_from(mhash: Multihash) -> Result { - Self::try_from(mhash.as_ref()) - } -} - -impl TryFrom> for Oid { - type Error = FromMultihashError; - - fn try_from(mhash: MultihashRef) -> Result { - if mhash.algorithm() != multihash::Code::Sha1 { - return Err(Self::Error::AlgorithmMismatch { - actual: mhash.algorithm(), - }); - } - - Self::try_from(mhash.digest()).map_err(Self::Error::from) - } -} - impl TryFrom<&[u8]> for Oid { type Error = git2::Error; @@ -186,15 +116,3 @@ impl TryFrom<&[u8]> for Oid { git2::Oid::from_bytes(bytes).map(Self) } } - -impl From for Multihash { - fn from(oid: Oid) -> Self { - Self::from(&oid) - } -} - -impl From<&Oid> for Multihash { - fn from(oid: &Oid) -> Self { - multihash::wrap(multihash::Code::Sha1, oid.as_ref()) - } -} diff --git a/radicle-git-ext/src/reference/name.rs b/radicle-git-ext/src/reference/name.rs index b4efc8a..1eded4e 100644 --- a/radicle-git-ext/src/reference/name.rs +++ b/radicle-git-ext/src/reference/name.rs @@ -635,77 +635,3 @@ impl From<&git_ref_format::refspec::PatternStr> for RefspecPattern { Self(r.to_owned().into()) } } - -#[cfg(feature = "minicbor")] -mod minicbor_impls { - use super::*; - use minicbor::{ - decode, - encode::{self, Write}, - Decode, - Decoder, - Encode, - Encoder, - }; - - impl Encode for RefLike { - fn encode(&self, e: &mut Encoder) -> Result<(), encode::Error> { - e.str(self.as_str())?; - Ok(()) - } - } - - impl<'b> Decode<'b> for RefLike { - fn decode(d: &mut Decoder) -> Result { - let path = d.str()?; - Self::try_from(path).or(Err(decode::Error::Message("invalid reflike"))) - } - } - - impl minicbor::Encode for OneLevel { - fn encode(&self, e: &mut Encoder) -> Result<(), encode::Error> { - e.str(self.as_str())?; - Ok(()) - } - } - - impl<'b> Decode<'b> for OneLevel { - fn decode(d: &mut Decoder) -> Result { - let refl: RefLike = Decode::decode(d)?; - Ok(Self::from(refl)) - } - } - - impl Encode for Qualified { - fn encode( - &self, - e: &mut Encoder, - ) -> Result<(), encode::Error> { - e.str(self.as_str())?; - Ok(()) - } - } - - impl<'b> Decode<'b> for Qualified { - fn decode(d: &mut Decoder) -> Result { - let refl: RefLike = Decode::decode(d)?; - Ok(Self::from(refl)) - } - } - - impl Encode for RefspecPattern { - fn encode( - &self, - e: &mut Encoder, - ) -> Result<(), encode::Error> { - e.str(self.as_str())?; - Ok(()) - } - } - - impl<'b> Decode<'b> for RefspecPattern { - fn decode(d: &mut Decoder) -> Result { - Self::try_from(d.str()?).or(Err(decode::Error::Message("invalid refspec pattern"))) - } - } -} diff --git a/radicle-git-ext/t/Cargo.toml b/radicle-git-ext/t/Cargo.toml index fd0cba4..a747d21 100644 --- a/radicle-git-ext/t/Cargo.toml +++ b/radicle-git-ext/t/Cargo.toml @@ -30,7 +30,7 @@ features = ["vendored-libgit2"] [dependencies.radicle-git-ext] path = ".." -features = ["minicbor", "serde"] +features = ["serde"] [dev-dependencies.test-helpers] path = "../../test/test-helpers" diff --git a/radicle-git-ext/t/src/tests.rs b/radicle-git-ext/t/src/tests.rs index d879923..99c6f37 100644 --- a/radicle-git-ext/t/src/tests.rs +++ b/radicle-git-ext/t/src/tests.rs @@ -135,22 +135,6 @@ mod reflike { assert!(serde_json::from_str::(&json).is_err()); assert!(serde_json::from_str::(&json).is_err()) } - - #[test] - fn cbor() { - let refl = RefLike::try_from("pu").unwrap(); - roundtrip::cbor(refl.clone()); - roundtrip::cbor(OneLevel::from(refl.clone())); - roundtrip::cbor(Qualified::from(refl)) - } - - #[test] - fn cbor_invalid() { - let cbor = minicbor::to_vec("HEAD^").unwrap(); - assert!(minicbor::decode::(&cbor).is_err()); - assert!(minicbor::decode::(&cbor).is_err()); - assert!(minicbor::decode::(&cbor).is_err()) - } } mod pattern { @@ -233,17 +217,6 @@ mod pattern { assert!(serde_json::from_str::(&json).is_err()) } - #[test] - fn cbor() { - roundtrip::cbor(RefspecPattern::try_from("refs/heads/*").unwrap()) - } - - #[test] - fn cbor_invalid() { - let cbor = minicbor::to_vec("HEAD^").unwrap(); - assert!(minicbor::decode::(&cbor).is_err()) - } - #[test] fn strip_prefix_works_for_different_ends() { let refl = RefLike::try_from("refs/heads/next").unwrap(); diff --git a/radicle-git-types/Cargo.toml b/radicle-git-types/Cargo.toml index 263fb97..1befa1e 100644 --- a/radicle-git-types/Cargo.toml +++ b/radicle-git-types/Cargo.toml @@ -12,7 +12,6 @@ test = false [dependencies] lazy_static = "1.4" multibase = "0.9" -multihash = "0.11" percent-encoding = "2" thiserror = "1.0.30" tracing = "0.1" diff --git a/radicle-git-types/src/lib.rs b/radicle-git-types/src/lib.rs index f8fd255..85cdbb7 100644 --- a/radicle-git-types/src/lib.rs +++ b/radicle-git-types/src/lib.rs @@ -6,21 +6,15 @@ extern crate radicle_git_ext as git_ext; extern crate radicle_std_ext as std_ext; -#[macro_use] -extern crate lazy_static; #[macro_use] extern crate radicle_macros; -pub mod namespace; pub mod reference; pub mod refspec; pub mod remote; -pub mod urn; -pub use urn::Urn; mod sealed; -pub use namespace::{AsNamespace, Namespace}; pub use reference::{ AsRemote, Many, @@ -33,18 +27,27 @@ pub use reference::{ }; pub use refspec::{Fetchspec, Pushspec, Refspec}; +pub trait AsNamespace: Into { + fn into_namespace(self) -> git_ext::RefLike { + self.into() + } +} + /// Helper to aid type inference constructing a [`Reference`] without a /// namespace. pub struct Flat; -impl From for Option> { +impl From for Option +where + N: AsNamespace, +{ fn from(_flat: Flat) -> Self { None } } /// Type specialised reference for the most common use within this crate. -pub type Reference = GenericRef, R, C>; +pub type Reference = GenericRef; /// Whether we should force the overwriting of a reference or not. #[derive(Debug, Clone, Copy)] diff --git a/radicle-git-types/src/namespace.rs b/radicle-git-types/src/namespace.rs deleted file mode 100644 index 068e0e4..0000000 --- a/radicle-git-types/src/namespace.rs +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright © 2019-2020 The Radicle Foundation -// -// This file is part of radicle-link, distributed under the GPLv3 with Radicle -// Linking Exception. For full terms see the included LICENSE file. - -use std::fmt::{self, Display}; - -use git_ext as ext; -use multihash::Multihash; - -use crate::urn::{self, Urn}; - -pub trait AsNamespace: Into { - fn into_namespace(self) -> ext::RefLike { - self.into() - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Namespace(Urn); - -impl AsNamespace for Namespace -where - R: urn::HasProtocol, - for<'a> &'a R: Into, -{ -} - -impl<'a, R> AsNamespace for &'a Namespace -where - R: urn::HasProtocol, - &'a R: Into, -{ -} - -impl From> for Namespace { - fn from(urn: Urn) -> Self { - Self(Urn { path: None, ..urn }) - } -} - -impl From<&Urn> for Namespace { - fn from(urn: &Urn) -> Self { - Self(Urn { - path: None, - id: urn.id.clone(), - }) - } -} - -impl Display for Namespace -where - R: urn::HasProtocol, - for<'a> &'a R: Into, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(ext::RefLike::from(self).as_str()) - } -} - -impl From> for ext::RefLike -where - R: urn::HasProtocol, - for<'a> &'a R: Into, -{ - fn from(ns: Namespace) -> Self { - Self::from(ns.0) - } -} - -impl<'a, R> From<&'a Namespace> for ext::RefLike -where - R: urn::HasProtocol, - &'a R: Into, -{ - fn from(ns: &'a Namespace) -> Self { - Self::from(&ns.0) - } -} - -impl From> for ext::RefspecPattern -where - R: urn::HasProtocol, - for<'a> &'a R: Into, -{ - fn from(ns: Namespace) -> Self { - ext::RefLike::from(ns).into() - } -} - -impl<'a, R> From<&'a Namespace> for ext::RefspecPattern -where - R: urn::HasProtocol, - &'a R: Into, -{ - fn from(ns: &'a Namespace) -> Self { - ext::RefLike::from(ns).into() - } -} - -impl From> for Urn { - fn from(ns: Namespace) -> Self { - ns.0 - } -} diff --git a/radicle-git-types/src/urn.rs b/radicle-git-types/src/urn.rs deleted file mode 100644 index 54fb804..0000000 --- a/radicle-git-types/src/urn.rs +++ /dev/null @@ -1,447 +0,0 @@ -// Copyright © 2019-2020 The Radicle Foundation -// -// This file is part of radicle-link, distributed under the GPLv3 with Radicle -// Linking Exception. For full terms see the included LICENSE file. - -use std::{ - borrow::Cow, - convert::TryFrom, - fmt::{self, Debug, Display}, - str::FromStr, -}; - -use git_ext as ext; -use multihash::{Multihash, MultihashRef}; -use percent_encoding::percent_decode_str; -use thiserror::Error; - -use super::sealed; - -lazy_static! { - pub static ref DEFAULT_PATH: ext::Qualified = ext::Qualified::from(reflike!("refs/rad/id")); -} - -pub mod error { - use super::*; - - #[derive(Debug, Error)] - #[non_exhaustive] - pub enum DecodeId { - #[error("invalid id")] - InvalidId(#[source] E), - - #[error(transparent)] - Encoding(#[from] multibase::Error), - - #[error(transparent)] - Multihash(#[from] multihash::DecodeOwnedError), - } - - #[derive(Debug, Error)] - #[non_exhaustive] - pub enum FromRefLike { - #[error("missing {0}")] - Missing(&'static str), - - #[error("must be a fully-qualified ref, ie. start with `refs/namespaces`")] - Namespaced(#[from] ext::reference::name::StripPrefixError), - - #[error("invalid id")] - InvalidId(#[source] DecodeId), - - #[error(transparent)] - Path(#[from] ext::reference::name::Error), - } - - #[derive(Debug, Error)] - #[non_exhaustive] - pub enum FromStr { - #[error("missing {0}")] - Missing(&'static str), - - #[error("invalid namespace identifier: {0}, expected `rad::`")] - InvalidNID(String), - - #[error("invalid protocol: {0}, expected `rad::`")] - InvalidProto(String), - - #[error("invalid id: {id}, expected `rad::`")] - InvalidId { - id: String, - #[source] - source: DecodeId, - }, - - #[error(transparent)] - Path(#[from] ext::reference::name::Error), - - #[error(transparent)] - Utf8(#[from] std::str::Utf8Error), - } -} - -pub trait HasProtocol: sealed::Sealed { - const PROTOCOL: &'static str; -} - -impl HasProtocol for git_ext::Oid { - const PROTOCOL: &'static str = "git"; -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum SomeProtocol { - Git, -} - -impl minicbor::Encode for SomeProtocol { - fn encode( - &self, - e: &mut minicbor::Encoder, - ) -> Result<(), minicbor::encode::Error> { - match self { - Self::Git => e.u8(0), - }?; - - Ok(()) - } -} - -impl<'de> minicbor::Decode<'de> for SomeProtocol { - fn decode(d: &mut minicbor::Decoder) -> Result { - match d.u8()? { - 0 => Ok(Self::Git), - _ => Err(minicbor::decode::Error::Message("unknown protocol")), - } - } -} - -impl TryFrom<&str> for SomeProtocol { - type Error = &'static str; - - fn try_from(s: &str) -> Result { - match s { - "git" => Ok(SomeProtocol::Git), - _ => Err("unknown protocol"), - } - } -} - -#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct Urn { - pub id: R, - pub path: Option, -} - -impl Urn { - pub const fn new(id: R) -> Self { - Self { id, path: None } - } - - /// Render [`Self::id`] into the canonical string encoding. - pub fn encode_id<'a>(&'a self) -> String - where - &'a R: Into, - { - multibase::encode(multibase::Base::Base32Z, (&self.id).into()) - } - - pub fn try_from_id(s: impl AsRef) -> Result> - where - R: TryFrom, - R::Error: std::error::Error + Send + Sync + 'static, - { - let bytes = multibase::decode(s.as_ref()).map(|x| x.1)?; - let mhash = Multihash::from_bytes(bytes)?; - let id = R::try_from(mhash).map_err(error::DecodeId::InvalidId)?; - Ok(Self::new(id)) - } - - pub fn map(self, f: F) -> Urn - where - F: FnOnce(R) -> S, - { - Urn { - id: f(self.id), - path: self.path, - } - } - - pub fn map_path(self, f: F) -> Self - where - F: FnOnce(Option) -> Option, - { - Self { - id: self.id, - path: f(self.path), - } - } - - pub fn with_path

(self, path: P) -> Self - where - P: Into>, - { - self.map_path(|_| path.into()) - } -} - -impl From for Urn { - fn from(r: R) -> Self { - Self::new(r) - } -} - -impl<'a, R: Clone> From> for Cow<'a, Urn> { - fn from(urn: Urn) -> Self { - Cow::Owned(urn) - } -} - -impl<'a, R: Clone> From<&'a Urn> for Cow<'a, Urn> { - fn from(urn: &'a Urn) -> Self { - Cow::Borrowed(urn) - } -} - -// FIXME: For some inexplicable reason, rustc rejects an impl for Urn, -// claiming that the blanket impl `impl TryFrom for T where U: Into` -// overlaps. We absolutely do not have `Into> for ext::RefLike`. -impl TryFrom for Urn { - type Error = error::FromRefLike; - - fn try_from(refl: ext::RefLike) -> Result { - let refl = refl.strip_prefix("refs/namespaces/")?; - let mut suf = refl.split('/'); - let ns = suf.next().ok_or(Self::Error::Missing("namespace"))?; - let urn = Self::try_from_id(ns).map_err(Self::Error::InvalidId)?; - let path = { - let path = suf.collect::>().join("/"); - if path.is_empty() { - Ok(None) - } else { - ext::RefLike::try_from(path).map(Some) - } - }?; - - Ok(urn.with_path(path)) - } -} - -impl From> for ext::RefLike -where - R: HasProtocol, - for<'a> &'a R: Into, -{ - fn from(urn: Urn) -> Self { - Self::from(&urn) - } -} - -// FIXME: this is not kosher -- doesn't include `refs/namespaces`, but -// everything after that. Should have a better type for that. -impl<'a, R> From<&'a Urn> for ext::RefLike -where - R: HasProtocol, - &'a R: Into, -{ - fn from(urn: &'a Urn) -> Self { - let refl = Self::try_from(urn.encode_id()).unwrap(); - match &urn.path { - None => refl, - Some(path) => refl.join(ext::Qualified::from(path.clone())), - } - } -} - -#[cfg(feature = "git-ref-format")] -impl<'a, R> From<&'a Urn> for git_ref_format::Component<'_> -where - R: HasProtocol, - &'a R: Into, -{ - #[inline] - fn from(urn: &'a Urn) -> Self { - use git_ref_format::RefString; - - let rs = RefString::try_from(urn.encode_id()).expect("urn id is a valid ref string"); - Self::from_refstring(rs).expect("urn id is a valid ref component") - } -} - -impl Display for Urn -where - R: HasProtocol, - for<'a> &'a R: Into, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "rad:{}:{}", R::PROTOCOL, self.encode_id())?; - - if let Some(path) = &self.path { - write!(f, "/{}", path.percent_encode())?; - } - - Ok(()) - } -} - -impl FromStr for Urn -where - R: HasProtocol + TryFrom, - E: std::error::Error + Send + Sync + 'static, -{ - type Err = error::FromStr; - - fn from_str(s: &str) -> Result { - let mut components = s.split(':'); - - components - .next() - .ok_or(Self::Err::Missing("namespace")) - .and_then(|nid| { - (nid == "rad") - .then_some(()) - .ok_or_else(|| Self::Err::InvalidNID(nid.to_string())) - })?; - - components - .next() - .ok_or(Self::Err::Missing("protocol")) - .and_then(|proto| { - (R::PROTOCOL == proto) - .then_some(()) - .ok_or_else(|| Self::Err::InvalidProto(proto.to_string())) - })?; - - components - .next() - .ok_or(Self::Err::Missing("id[/path]")) - .and_then(|s| { - let decoded = percent_decode_str(s).decode_utf8()?; - let mut iter = decoded.splitn(2, '/'); - - let id = iter.next().ok_or(Self::Err::Missing("id"))?; - let urn = Self::try_from_id(id).map_err(|err| Self::Err::InvalidId { - id: id.to_string(), - source: err, - })?; - let path = iter.next().map(ext::RefLike::try_from).transpose()?; - Ok(urn.with_path(path)) - }) - } -} - -impl serde::Serialize for Urn -where - R: HasProtocol, - for<'a> &'a R: Into, -{ - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - self.to_string().serialize(serializer) - } -} - -impl<'de, R, E> serde::Deserialize<'de> for Urn -where - R: HasProtocol + TryFrom, - E: std::error::Error + Send + Sync + 'static, -{ - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let s: &str = serde::Deserialize::deserialize(deserializer)?; - s.parse().map_err(serde::de::Error::custom) - } -} - -#[derive(Debug, PartialEq, minicbor::Encode, minicbor::Decode)] -#[cbor(array)] -struct AsCbor<'a> { - #[b(0)] - #[cbor(with = "minicbor::bytes")] - id: &'a [u8], - - #[n(1)] - proto: SomeProtocol, - - #[b(2)] - path: Option<&'a str>, -} - -impl minicbor::Encode for Urn -where - R: HasProtocol, - for<'a> &'a R: Into, -{ - fn encode( - &self, - e: &mut minicbor::Encoder, - ) -> Result<(), minicbor::encode::Error> { - let id: Multihash = (&self.id).into(); - e.encode(AsCbor { - id: id.as_bytes(), - proto: SomeProtocol::try_from(R::PROTOCOL).unwrap(), - path: self.path.as_ref().map(|path| path.as_str()), - })?; - - Ok(()) - } -} - -impl<'de, R> minicbor::Decode<'de> for Urn -where - for<'a> R: HasProtocol + TryFrom>, -{ - fn decode(d: &mut minicbor::Decoder) -> Result { - use minicbor::decode::Error::Message as Error; - - let AsCbor { id, path, .. } = d.decode()?; - - let id = { - let mhash = MultihashRef::from_slice(id).or(Err(Error("invalid multihash")))?; - R::try_from(mhash).or(Err(Error("invalid id"))) - }?; - let path = path - .map(ext::RefLike::try_from) - .transpose() - .or(Err(Error("invalid path")))?; - - Ok(Self { id, path }) - } -} - -pub mod test { - use super::*; - - /// Fake `id` of a `Urn`. - /// - /// Not cryptographically secure, but cheap to create for tests. - #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] - pub struct FakeId(pub usize); - - impl sealed::Sealed for FakeId {} - - impl HasProtocol for FakeId { - const PROTOCOL: &'static str = "test"; - } - - impl From for FakeId { - fn from(sz: usize) -> FakeId { - Self(sz) - } - } - - impl From for Multihash { - fn from(id: FakeId) -> Self { - Self::from(&id) - } - } - - impl From<&FakeId> for Multihash { - fn from(id: &FakeId) -> Self { - multihash::wrap(multihash::Code::Identity, &id.0.to_be_bytes()) - } - } -}