Skip to main content

radicle_git_ref_format/
lib.rs

1//! [`git_ref_format`]: https://crates.io/crates/git-ref-format
2//! [`radicle-git-ext`]: https://crates.io/crates/radicle-git-ext
3//!
4//! ## Macros
5//!
6//! Instead of providing procedural macros, like [`git_ref_format`] this
7//! crate just provides much simpler declarative macros, guarded by the feature
8//! flag `macro`.
9//!
10//! ## Comparison to [`git_ref_format`]
11//!
12//! ### Benefits
13//!
14//! - Does not depend on [`radicle-git-ext`].
15//! - Does not pull in procedural macro dependencies.
16//! - Has much smaller compile-time overhead than [`git_ref_format`].
17//!
18//! ### Drawback
19//!
20//! The main drawback is that the macros in this crate cannot provide compile
21//! time validation of the argument. Thus, these macros must be used in
22//! conjunction with testing: If all generated objects are used in tests, and
23//! these tests are run, then the guarantees are equally strong. Consumers that
24//! do not or cannot test their code should not use the macros then.
25
26mod check;
27pub use check::{Error, Options, ref_format as check_ref_format};
28
29mod deriv;
30pub use deriv::{Namespaced, Qualified};
31
32pub mod lit;
33
34pub mod name;
35#[cfg(feature = "percent-encoding")]
36pub use name::PercentEncode;
37pub use name::{Component, RefStr, RefString};
38
39pub mod refspec;
40pub use refspec::DuplicateGlob;
41
42#[cfg(feature = "minicbor")]
43mod cbor;
44
45#[cfg(feature = "serde")]
46mod serde;
47
48/// Create a [`crate::RefString`] from a string literal.
49///
50/// Similar to [`core::debug_assert`], an optimized build will not validate
51/// (but rather perform an unsafe conversion) unless `-C debug-assertions` is
52/// passed to the compiler.
53#[cfg(any(feature = "macro", test))]
54#[macro_export]
55macro_rules! refname {
56    ($arg:literal) => {{
57        use $crate::RefString;
58
59        #[cfg(debug_assertions)]
60        {
61            RefString::try_from($arg).expect(core::concat!(
62                "literal `",
63                $arg,
64                "` must be a valid reference name"
65            ))
66        }
67
68        #[cfg(not(debug_assertions))]
69        {
70            extern crate alloc;
71
72            use alloc::string::String;
73
74            let s: String = $arg.to_owned();
75            unsafe { core::mem::transmute::<_, RefString>(s) }
76        }
77    }};
78}
79
80/// Create a [`crate::Qualified`] from a string literal.
81///
82/// Similar to [`core::debug_assert`], an optimized build will not validate
83/// (but rather perform an unsafe conversion) unless `-C debug-assertions` is
84/// passed to the compiler.
85#[cfg(any(feature = "macro", test))]
86#[macro_export]
87macro_rules! qualified {
88    ($arg:literal) => {{
89        use $crate::Qualified;
90
91        #[cfg(debug_assertions)]
92        {
93            Qualified::from_refstr($crate::refname!($arg)).expect(core::concat!(
94                "literal `",
95                $arg,
96                "` must be of the form 'refs/<category>/<name>'"
97            ))
98        }
99
100        #[cfg(not(debug_assertions))]
101        {
102            extern crate alloc;
103
104            use core::mem::transmute;
105
106            use alloc::borrow::Cow;
107            use alloc::string::String;
108
109            use $crate::{RefStr, RefString};
110
111            let s: String = $arg.to_owned();
112            let refstring: RefString = unsafe { transmute(s) };
113            let cow: Cow<'_, RefStr> = Cow::Owned(refstring);
114            let qualified: Qualified = unsafe { transmute(cow) };
115
116            qualified
117        }
118    }};
119}
120
121/// Create a [`crate::Component`] from a string literal.
122///
123/// Similar to [`core::debug_assert`], an optimized build will not validate
124/// (but rather perform an unsafe conversion) unless `-C debug-assertions` is
125/// passed to the compiler.
126#[cfg(any(feature = "macro", test))]
127#[macro_export]
128macro_rules! component {
129    ($arg:literal) => {{
130        use $crate::Component;
131
132        #[cfg(debug_assertions)]
133        {
134            Component::from_refstr($crate::refname!($arg)).expect(core::concat!(
135                "literal `",
136                $arg,
137                "` must be a valid component (cannot contain '/')"
138            ))
139        }
140
141        #[cfg(not(debug_assertions))]
142        {
143            extern crate alloc;
144
145            use core::mem::transmute;
146
147            use alloc::borrow::Cow;
148            use alloc::string::String;
149
150            use $crate::{RefStr, RefString};
151
152            let s: String = $arg.to_owned();
153            let refstring: RefString = unsafe { transmute(s) };
154            let cow: Cow<'_, RefStr> = Cow::Owned(refstring);
155            let component: Component = unsafe { transmute(cow) };
156
157            component
158        }
159    }};
160}
161
162/// Create a [`crate::refspec::PatternString`] from a string literal.
163///
164/// Similar to [`core::debug_assert`], an optimized build will not validate
165/// (but rather perform an unsafe conversion) unless `-C debug-assertions` is
166/// passed to the compiler.
167#[cfg(any(feature = "macro", test))]
168#[macro_export]
169macro_rules! pattern {
170    ($arg:literal) => {{
171        use $crate::refspec::PatternString;
172
173        #[cfg(debug_assertions)]
174        {
175            PatternString::try_from($arg).expect(core::concat!(
176                "literal `",
177                $arg,
178                "` must be a valid refspec pattern"
179            ))
180        }
181
182        #[cfg(not(debug_assertions))]
183        {
184            extern crate alloc;
185
186            use alloc::string::String;
187
188            let s: String = $arg.to_owned();
189            unsafe { core::mem::transmute::<_, PatternString>(s) }
190        }
191    }};
192}
193
194/// Create a [`crate::refspec::QualifiedPattern`] from a string literal.
195///
196/// Similar to [`core::debug_assert`], an optimized build will not validate
197/// (but rather perform an unsafe conversion) unless `-C debug-assertions` is
198/// passed to the compiler.
199#[cfg(any(feature = "macro", test))]
200#[macro_export]
201macro_rules! qualified_pattern {
202    ($arg:literal) => {{
203        use $crate::refspec::QualifiedPattern;
204
205        #[cfg(debug_assertions)]
206        {
207            use core::concat;
208
209            use $crate::refspec::PatternStr;
210
211            let pattern = PatternStr::try_from_str($arg).expect(concat!(
212                "literal `",
213                $arg,
214                "` must be a valid refspec pattern"
215            ));
216
217            QualifiedPattern::from_patternstr(pattern).expect(concat!(
218                "literal `",
219                $arg,
220                "` must be a valid qualified refspec pattern"
221            ))
222        }
223
224        #[cfg(not(debug_assertions))]
225        {
226            extern crate alloc;
227
228            use core::mem::transmute;
229
230            use alloc::borrow::Cow;
231            use alloc::string::String;
232
233            use $crate::refspec::{PatternStr, PatternString};
234
235            let s: String = $arg.to_owned();
236            let pattern: PatternString = unsafe { transmute(s) };
237            let cow: Cow<'_, PatternStr> = Cow::Owned(pattern);
238            let qualified: QualifiedPattern = unsafe { transmute(cow) };
239
240            qualified
241        }
242    }};
243}
244
245#[cfg(test)]
246mod test {
247    #[test]
248    fn refname() {
249        let _ = crate::refname!("refs/heads/main");
250        let _ = crate::refname!("refs/tags/v1.0.0");
251        let _ = crate::refname!("refs/remotes/origin/main");
252        let _ = crate::refname!("a");
253    }
254
255    #[test]
256    #[should_panic]
257    fn refname_invalid() {
258        let _ = crate::refname!("a~b");
259    }
260
261    #[test]
262    fn qualified() {
263        let _ = crate::qualified!("refs/heads/main");
264        let _ = crate::qualified!("refs/tags/v1.0.0");
265        let _ = crate::qualified!("refs/remotes/origin/main");
266    }
267
268    #[test]
269    #[should_panic]
270    fn qualified_invalid() {
271        let _ = crate::qualified!("a");
272    }
273
274    #[test]
275    fn component() {
276        let _ = crate::component!("a");
277    }
278
279    #[test]
280    #[should_panic]
281    fn component_invalid() {
282        let _ = crate::component!("a/b");
283    }
284
285    #[test]
286    fn pattern() {
287        let _ = crate::pattern!("refs/heads/main");
288        let _ = crate::pattern!("refs/tags/v1.0.0");
289        let _ = crate::pattern!("refs/remotes/origin/main");
290
291        let _ = crate::pattern!("a");
292        let _ = crate::pattern!("a/*");
293        let _ = crate::pattern!("*");
294        let _ = crate::pattern!("a/b*");
295        let _ = crate::pattern!("a/b*/c");
296        let _ = crate::pattern!("a/*/c");
297    }
298
299    #[test]
300    fn qualified_pattern() {
301        let _ = crate::qualified_pattern!("refs/heads/main");
302        let _ = crate::qualified_pattern!("refs/tags/v1.0.0");
303        let _ = crate::qualified_pattern!("refs/remotes/origin/main");
304
305        let _ = crate::qualified_pattern!("refs/heads/main/*");
306        let _ = crate::qualified_pattern!("refs/tags/v*");
307        let _ = crate::qualified_pattern!("refs/remotes/origin/main");
308        let _ = crate::qualified_pattern!("refs/remotes/origin/department/*/person");
309    }
310
311    #[test]
312    #[should_panic]
313    fn qualified_pattern_invalid() {
314        let _ = crate::qualified_pattern!("a/*/b");
315    }
316}