Radish alpha
r
rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt
Git libraries for Radicle
Radicle
Git
Merge remote-tracking branch 'origin/remove-multihash'
Fintan Halpenny committed 3 years ago
commit 7fd402891f6bb99284f83c2c9445de9fe12d9146
parent bf33be9
9 files changed +18 -754
modified radicle-git-ext/Cargo.toml
@@ -1,8 +1,11 @@
[package]
name = "radicle-git-ext"
-
version = "0.1.0"
-
authors = ["The Radicle Team <dev@radicle.xyz>"]
-
edition = "2018"
+
version = "0.2.0"
+
authors = [
+
  "Kim Altintop <kim@eagain.st>",
+
  "Fintan Halpenny <fintan.halpenny@gmail.com",
+
]
+
edition = "2021"
license = "GPL-3.0-or-later"
description = "Utilities and extensions to the git2 crate"
keywords = ["git", "radicle"]
@@ -12,7 +15,6 @@ doctest = false
test = false

[dependencies]
-
multihash = "0.11"
percent-encoding = "2"
thiserror = "1"

@@ -25,11 +27,6 @@ features = ["vendored-libgit2"]
version = "0.1.0"
path = "../git-ref-format"

-
[dependencies.minicbor]
-
version = "0.13"
-
features = ["std"]
-
optional = true
-

[dependencies.serde]
version = "1"
features = ["derive"]
modified radicle-git-ext/src/oid.rs
@@ -10,19 +10,10 @@ use std::{
    str::FromStr,
};

-
use multihash::{Multihash, MultihashRef};
-
use thiserror::Error;
-

/// Serializable [`git2::Oid`]
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Oid(git2::Oid);

-
impl Oid {
-
    pub fn into_multihash(self) -> 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<W: Write>(&self, e: &mut Encoder<W>) -> Result<(), encode::Error<W::Error>> {
-
            e.bytes(Multihash::from(self).as_bytes())?;
-
            Ok(())
-
        }
-
    }
-

-
    impl<'b> Decode<'b> for Oid {
-
        fn decode(d: &mut Decoder) -> Result<Self, decode::Error> {
-
            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<Multihash> for Oid {
-
    type Error = FromMultihashError;
-

-
    fn try_from(mhash: Multihash) -> Result<Self, Self::Error> {
-
        Self::try_from(mhash.as_ref())
-
    }
-
}
-

-
impl TryFrom<MultihashRef<'_>> for Oid {
-
    type Error = FromMultihashError;
-

-
    fn try_from(mhash: MultihashRef) -> Result<Self, Self::Error> {
-
        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<Oid> 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())
-
    }
-
}
modified 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<W: Write>(&self, e: &mut Encoder<W>) -> Result<(), encode::Error<W::Error>> {
-
            e.str(self.as_str())?;
-
            Ok(())
-
        }
-
    }
-

-
    impl<'b> Decode<'b> for RefLike {
-
        fn decode(d: &mut Decoder) -> Result<Self, decode::Error> {
-
            let path = d.str()?;
-
            Self::try_from(path).or(Err(decode::Error::Message("invalid reflike")))
-
        }
-
    }
-

-
    impl minicbor::Encode for OneLevel {
-
        fn encode<W: Write>(&self, e: &mut Encoder<W>) -> Result<(), encode::Error<W::Error>> {
-
            e.str(self.as_str())?;
-
            Ok(())
-
        }
-
    }
-

-
    impl<'b> Decode<'b> for OneLevel {
-
        fn decode(d: &mut Decoder) -> Result<Self, decode::Error> {
-
            let refl: RefLike = Decode::decode(d)?;
-
            Ok(Self::from(refl))
-
        }
-
    }
-

-
    impl Encode for Qualified {
-
        fn encode<W: encode::Write>(
-
            &self,
-
            e: &mut Encoder<W>,
-
        ) -> Result<(), encode::Error<W::Error>> {
-
            e.str(self.as_str())?;
-
            Ok(())
-
        }
-
    }
-

-
    impl<'b> Decode<'b> for Qualified {
-
        fn decode(d: &mut Decoder) -> Result<Self, decode::Error> {
-
            let refl: RefLike = Decode::decode(d)?;
-
            Ok(Self::from(refl))
-
        }
-
    }
-

-
    impl Encode for RefspecPattern {
-
        fn encode<W: encode::Write>(
-
            &self,
-
            e: &mut Encoder<W>,
-
        ) -> Result<(), encode::Error<W::Error>> {
-
            e.str(self.as_str())?;
-
            Ok(())
-
        }
-
    }
-

-
    impl<'b> Decode<'b> for RefspecPattern {
-
        fn decode(d: &mut Decoder) -> Result<Self, decode::Error> {
-
            Self::try_from(d.str()?).or(Err(decode::Error::Message("invalid refspec pattern")))
-
        }
-
    }
-
}
modified 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"
modified radicle-git-ext/t/src/tests.rs
@@ -135,22 +135,6 @@ mod reflike {
        assert!(serde_json::from_str::<OneLevel>(&json).is_err());
        assert!(serde_json::from_str::<Qualified>(&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::<RefLike>(&cbor).is_err());
-
        assert!(minicbor::decode::<OneLevel>(&cbor).is_err());
-
        assert!(minicbor::decode::<Qualified>(&cbor).is_err())
-
    }
}

mod pattern {
@@ -234,17 +218,6 @@ mod pattern {
    }

    #[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::<RefspecPattern>(&cbor).is_err())
-
    }
-

-
    #[test]
    fn strip_prefix_works_for_different_ends() {
        let refl = RefLike::try_from("refs/heads/next").unwrap();
        assert_eq!(
modified 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"
modified radicle-git-types/src/lib.rs
@@ -7,20 +7,14 @@ 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<git_ext::RefLike> {
+
    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<Flat> for Option<Namespace<git_ext::Oid>> {
+
impl<N> From<Flat> for Option<N>
+
where
+
    N: AsNamespace,
+
{
    fn from(_flat: Flat) -> Self {
        None
    }
}

/// Type specialised reference for the most common use within this crate.
-
pub type Reference<C, R> = GenericRef<Namespace<git_ext::Oid>, R, C>;
+
pub type Reference<N, C, R> = GenericRef<N, R, C>;

/// Whether we should force the overwriting of a reference or not.
#[derive(Debug, Clone, Copy)]
deleted radicle-git-types/src/namespace.rs
@@ -1,105 +0,0 @@
-
// Copyright © 2019-2020 The Radicle Foundation <hello@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<ext::RefLike> {
-
    fn into_namespace(self) -> ext::RefLike {
-
        self.into()
-
    }
-
}
-

-
#[derive(Debug, Clone, PartialEq, Eq)]
-
pub struct Namespace<R>(Urn<R>);
-

-
impl<R> AsNamespace for Namespace<R>
-
where
-
    R: urn::HasProtocol,
-
    for<'a> &'a R: Into<Multihash>,
-
{
-
}
-

-
impl<'a, R> AsNamespace for &'a Namespace<R>
-
where
-
    R: urn::HasProtocol,
-
    &'a R: Into<Multihash>,
-
{
-
}
-

-
impl<R> From<Urn<R>> for Namespace<R> {
-
    fn from(urn: Urn<R>) -> Self {
-
        Self(Urn { path: None, ..urn })
-
    }
-
}
-

-
impl<R: Clone> From<&Urn<R>> for Namespace<R> {
-
    fn from(urn: &Urn<R>) -> Self {
-
        Self(Urn {
-
            path: None,
-
            id: urn.id.clone(),
-
        })
-
    }
-
}
-

-
impl<R> Display for Namespace<R>
-
where
-
    R: urn::HasProtocol,
-
    for<'a> &'a R: Into<Multihash>,
-
{
-
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-
        f.write_str(ext::RefLike::from(self).as_str())
-
    }
-
}
-

-
impl<R> From<Namespace<R>> for ext::RefLike
-
where
-
    R: urn::HasProtocol,
-
    for<'a> &'a R: Into<Multihash>,
-
{
-
    fn from(ns: Namespace<R>) -> Self {
-
        Self::from(ns.0)
-
    }
-
}
-

-
impl<'a, R> From<&'a Namespace<R>> for ext::RefLike
-
where
-
    R: urn::HasProtocol,
-
    &'a R: Into<Multihash>,
-
{
-
    fn from(ns: &'a Namespace<R>) -> Self {
-
        Self::from(&ns.0)
-
    }
-
}
-

-
impl<R> From<Namespace<R>> for ext::RefspecPattern
-
where
-
    R: urn::HasProtocol,
-
    for<'a> &'a R: Into<Multihash>,
-
{
-
    fn from(ns: Namespace<R>) -> Self {
-
        ext::RefLike::from(ns).into()
-
    }
-
}
-

-
impl<'a, R> From<&'a Namespace<R>> for ext::RefspecPattern
-
where
-
    R: urn::HasProtocol,
-
    &'a R: Into<Multihash>,
-
{
-
    fn from(ns: &'a Namespace<R>) -> Self {
-
        ext::RefLike::from(ns).into()
-
    }
-
}
-

-
impl<R> From<Namespace<R>> for Urn<R> {
-
    fn from(ns: Namespace<R>) -> Self {
-
        ns.0
-
    }
-
}
deleted radicle-git-types/src/urn.rs
@@ -1,447 +0,0 @@
-
// Copyright © 2019-2020 The Radicle Foundation <hello@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<E: std::error::Error + Send + Sync + 'static> {
-
        #[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<E: std::error::Error + Send + Sync + 'static> {
-
        #[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<E>),
-

-
        #[error(transparent)]
-
        Path(#[from] ext::reference::name::Error),
-
    }
-

-
    #[derive(Debug, Error)]
-
    #[non_exhaustive]
-
    pub enum FromStr<E: std::error::Error + Send + Sync + 'static> {
-
        #[error("missing {0}")]
-
        Missing(&'static str),
-

-
        #[error("invalid namespace identifier: {0}, expected `rad:<protocol>:<id>`")]
-
        InvalidNID(String),
-

-
        #[error("invalid protocol: {0}, expected `rad:<protocol>:<id>`")]
-
        InvalidProto(String),
-

-
        #[error("invalid id: {id}, expected `rad:<protocol>:<id>`")]
-
        InvalidId {
-
            id: String,
-
            #[source]
-
            source: DecodeId<E>,
-
        },
-

-
        #[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<W: minicbor::encode::Write>(
-
        &self,
-
        e: &mut minicbor::Encoder<W>,
-
    ) -> Result<(), minicbor::encode::Error<W::Error>> {
-
        match self {
-
            Self::Git => e.u8(0),
-
        }?;
-

-
        Ok(())
-
    }
-
}
-

-
impl<'de> minicbor::Decode<'de> for SomeProtocol {
-
    fn decode(d: &mut minicbor::Decoder) -> Result<Self, minicbor::decode::Error> {
-
        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<Self, Self::Error> {
-
        match s {
-
            "git" => Ok(SomeProtocol::Git),
-
            _ => Err("unknown protocol"),
-
        }
-
    }
-
}
-

-
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
-
pub struct Urn<R> {
-
    pub id: R,
-
    pub path: Option<ext::RefLike>,
-
}
-

-
impl<R> Urn<R> {
-
    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<Multihash>,
-
    {
-
        multibase::encode(multibase::Base::Base32Z, (&self.id).into())
-
    }
-

-
    pub fn try_from_id(s: impl AsRef<str>) -> Result<Self, error::DecodeId<R::Error>>
-
    where
-
        R: TryFrom<Multihash>,
-
        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<F, S>(self, f: F) -> Urn<S>
-
    where
-
        F: FnOnce(R) -> S,
-
    {
-
        Urn {
-
            id: f(self.id),
-
            path: self.path,
-
        }
-
    }
-

-
    pub fn map_path<F>(self, f: F) -> Self
-
    where
-
        F: FnOnce(Option<ext::RefLike>) -> Option<ext::RefLike>,
-
    {
-
        Self {
-
            id: self.id,
-
            path: f(self.path),
-
        }
-
    }
-

-
    pub fn with_path<P>(self, path: P) -> Self
-
    where
-
        P: Into<Option<ext::RefLike>>,
-
    {
-
        self.map_path(|_| path.into())
-
    }
-
}
-

-
impl<R> From<R> for Urn<R> {
-
    fn from(r: R) -> Self {
-
        Self::new(r)
-
    }
-
}
-

-
impl<'a, R: Clone> From<Urn<R>> for Cow<'a, Urn<R>> {
-
    fn from(urn: Urn<R>) -> Self {
-
        Cow::Owned(urn)
-
    }
-
}
-

-
impl<'a, R: Clone> From<&'a Urn<R>> for Cow<'a, Urn<R>> {
-
    fn from(urn: &'a Urn<R>) -> Self {
-
        Cow::Borrowed(urn)
-
    }
-
}
-

-
// FIXME: For some inexplicable reason, rustc rejects an impl for Urn<R>,
-
// claiming that the blanket impl `impl<T, U> TryFrom<U> for T where U: Into<T>`
-
// overlaps. We absolutely do not have `Into<Urn<R>> for ext::RefLike`.
-
impl TryFrom<ext::RefLike> for Urn<ext::Oid> {
-
    type Error = error::FromRefLike<ext::oid::FromMultihashError>;
-

-
    fn try_from(refl: ext::RefLike) -> Result<Self, Self::Error> {
-
        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::<Vec<_>>().join("/");
-
            if path.is_empty() {
-
                Ok(None)
-
            } else {
-
                ext::RefLike::try_from(path).map(Some)
-
            }
-
        }?;
-

-
        Ok(urn.with_path(path))
-
    }
-
}
-

-
impl<R> From<Urn<R>> for ext::RefLike
-
where
-
    R: HasProtocol,
-
    for<'a> &'a R: Into<Multihash>,
-
{
-
    fn from(urn: Urn<R>) -> 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<R>> for ext::RefLike
-
where
-
    R: HasProtocol,
-
    &'a R: Into<Multihash>,
-
{
-
    fn from(urn: &'a Urn<R>) -> 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<R>> for git_ref_format::Component<'_>
-
where
-
    R: HasProtocol,
-
    &'a R: Into<Multihash>,
-
{
-
    #[inline]
-
    fn from(urn: &'a Urn<R>) -> 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<R> Display for Urn<R>
-
where
-
    R: HasProtocol,
-
    for<'a> &'a R: Into<Multihash>,
-
{
-
    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<R, E> FromStr for Urn<R>
-
where
-
    R: HasProtocol + TryFrom<Multihash, Error = E>,
-
    E: std::error::Error + Send + Sync + 'static,
-
{
-
    type Err = error::FromStr<E>;
-

-
    fn from_str(s: &str) -> Result<Self, Self::Err> {
-
        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<R> serde::Serialize for Urn<R>
-
where
-
    R: HasProtocol,
-
    for<'a> &'a R: Into<Multihash>,
-
{
-
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
-
    where
-
        S: serde::Serializer,
-
    {
-
        self.to_string().serialize(serializer)
-
    }
-
}
-

-
impl<'de, R, E> serde::Deserialize<'de> for Urn<R>
-
where
-
    R: HasProtocol + TryFrom<Multihash, Error = E>,
-
    E: std::error::Error + Send + Sync + 'static,
-
{
-
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
-
    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<R> minicbor::Encode for Urn<R>
-
where
-
    R: HasProtocol,
-
    for<'a> &'a R: Into<Multihash>,
-
{
-
    fn encode<W: minicbor::encode::Write>(
-
        &self,
-
        e: &mut minicbor::Encoder<W>,
-
    ) -> Result<(), minicbor::encode::Error<W::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<R>
-
where
-
    for<'a> R: HasProtocol + TryFrom<MultihashRef<'a>>,
-
{
-
    fn decode(d: &mut minicbor::Decoder) -> Result<Self, minicbor::decode::Error> {
-
        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<FakeId>`.
-
    ///
-
    /// 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<usize> for FakeId {
-
        fn from(sz: usize) -> FakeId {
-
            Self(sz)
-
        }
-
    }
-

-
    impl From<FakeId> 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())
-
        }
-
    }
-
}