radicle_surf/
namespace.rs1use std::{
2 convert::TryFrom,
3 fmt,
4 str::{self, FromStr},
5};
6
7use nonempty::NonEmpty;
8use radicle_git_ref_format::{
9 self, Component, Namespaced, Qualified, RefStr, RefString,
10 refspec::{NamespacedPattern, PatternString, QualifiedPattern},
11};
12use thiserror::Error;
13
14#[derive(Debug, Error)]
15pub enum Error {
16 #[error("namespaces must not be empty")]
19 EmptyNamespace,
20 #[error(transparent)]
21 RefFormat(#[from] radicle_git_ref_format::Error),
22 #[error(transparent)]
23 Utf8(#[from] str::Utf8Error),
24}
25
26#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
36pub struct Namespace {
37 pub(super) namespaces: RefString,
40}
41
42impl Namespace {
43 pub(crate) fn to_namespaced<'a>(&self, name: &Qualified<'a>) -> Namespaced<'a> {
57 let mut components = self.namespaces.components().rev();
58 let mut namespaced = name.with_namespace(
59 components
60 .next()
61 .expect("BUG: 'namespaces' cannot be empty"),
62 );
63 for ns in components {
64 let qualified = namespaced.into_qualified();
65 namespaced = qualified.with_namespace(ns);
66 }
67 namespaced
68 }
69
70 pub(crate) fn to_namespaced_pattern<'a>(
84 &self,
85 pat: &QualifiedPattern<'a>,
86 ) -> NamespacedPattern<'a> {
87 let pattern = PatternString::from(self.namespaces.clone());
88 let mut components = pattern.components().rev();
89 let mut namespaced = pat
90 .with_namespace(
91 components
92 .next()
93 .expect("BUG: 'namespaces' cannot be empty"),
94 )
95 .expect("BUG: 'namespace' cannot have globs");
96 for ns in components {
97 let qualified = namespaced.into_qualified();
98 namespaced = qualified
99 .with_namespace(ns)
100 .expect("BUG: 'namespaces' cannot have globs");
101 }
102 namespaced
103 }
104}
105
106impl fmt::Display for Namespace {
107 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108 write!(f, "{}", self.namespaces)
109 }
110}
111
112impl<'a> From<NonEmpty<Component<'a>>> for Namespace {
113 fn from(cs: NonEmpty<Component<'a>>) -> Self {
114 Self {
115 namespaces: cs.into_iter().collect::<RefString>(),
116 }
117 }
118}
119
120impl TryFrom<&str> for Namespace {
121 type Error = Error;
122
123 fn try_from(name: &str) -> Result<Self, Self::Error> {
124 Self::from_str(name)
125 }
126}
127
128impl TryFrom<&[u8]> for Namespace {
129 type Error = Error;
130
131 fn try_from(namespace: &[u8]) -> Result<Self, Self::Error> {
132 str::from_utf8(namespace)
133 .map_err(Error::from)
134 .and_then(Self::from_str)
135 }
136}
137
138impl FromStr for Namespace {
139 type Err = Error;
140
141 fn from_str(name: &str) -> Result<Self, Self::Err> {
142 let namespaces = RefStr::try_from_str(name)?.to_ref_string();
143 Ok(Self { namespaces })
144 }
145}
146
147impl From<Namespaced<'_>> for Namespace {
148 fn from(namespaced: Namespaced<'_>) -> Self {
149 let mut namespaces = namespaced.namespace().to_ref_string();
150 let mut qualified = namespaced.strip_namespace();
151 while let Some(namespaced) = qualified.to_namespaced() {
152 namespaces.push(namespaced.namespace());
153 qualified = namespaced.strip_namespace();
154 }
155 Self { namespaces }
156 }
157}
158
159impl TryFrom<&git2::Reference<'_>> for Namespace {
160 type Error = Error;
161
162 fn try_from(reference: &git2::Reference) -> Result<Self, Self::Error> {
163 let name = RefStr::try_from_str(str::from_utf8(reference.name_bytes())?)?;
164 name.to_namespaced()
165 .ok_or(Error::EmptyNamespace)
166 .map(Self::from)
167 }
168}