radicle-git-ref-format 0.2.0

Everything you never knew you wanted for handling git ref names.
Documentation
//! [`git_ref_format`]: https://crates.io/crates/git-ref-format
//! [`radicle-git-ext`]: https://crates.io/crates/radicle-git-ext
//!
//! ## Macros
//!
//! Instead of providing procedural macros, like [`git_ref_format`] this
//! crate just provides much simpler declarative macros, guarded by the feature
//! flag `macro`.
//!
//! ## Comparison to [`git_ref_format`]
//!
//! ### Benefits
//!
//! - Does not depend on [`radicle-git-ext`].
//! - Does not pull in procedural macro dependencies.
//! - Has much smaller compile-time overhead than [`git_ref_format`].
//!
//! ### Drawback
//!
//! The main drawback is that the macros in this crate cannot provide compile
//! time validation of the argument. Thus, these macros must be used in
//! conjunction with testing: If all generated objects are used in tests, and
//! these tests are run, then the guarantees are equally strong. Consumers that
//! do not or cannot test their code should not use the macros then.

mod check;
pub use check::{Error, Options, ref_format as check_ref_format};

mod deriv;
pub use deriv::{Namespaced, Qualified};

pub mod lit;

pub mod name;
#[cfg(feature = "percent-encoding")]
pub use name::PercentEncode;
pub use name::{Component, RefStr, RefString};

pub mod refspec;
pub use refspec::DuplicateGlob;

#[cfg(feature = "minicbor")]
mod cbor;

#[cfg(feature = "serde")]
mod serde;

/// Create a [`crate::RefString`] from a string literal.
///
/// Similar to [`core::debug_assert`], an optimized build will not validate
/// (but rather perform an unsafe conversion) unless `-C debug-assertions` is
/// passed to the compiler.
#[cfg(any(feature = "macro", test))]
#[macro_export]
macro_rules! refname {
    ($arg:literal) => {{
        use $crate::RefString;

        #[cfg(debug_assertions)]
        {
            RefString::try_from($arg).expect(core::concat!(
                "literal `",
                $arg,
                "` must be a valid reference name"
            ))
        }

        #[cfg(not(debug_assertions))]
        {
            extern crate alloc;

            use alloc::string::String;

            let s: String = $arg.to_owned();
            unsafe { core::mem::transmute::<_, RefString>(s) }
        }
    }};
}

/// Create a [`crate::Qualified`] from a string literal.
///
/// Similar to [`core::debug_assert`], an optimized build will not validate
/// (but rather perform an unsafe conversion) unless `-C debug-assertions` is
/// passed to the compiler.
#[cfg(any(feature = "macro", test))]
#[macro_export]
macro_rules! qualified {
    ($arg:literal) => {{
        use $crate::Qualified;

        #[cfg(debug_assertions)]
        {
            Qualified::from_refstr($crate::refname!($arg)).expect(core::concat!(
                "literal `",
                $arg,
                "` must be of the form 'refs/<category>/<name>'"
            ))
        }

        #[cfg(not(debug_assertions))]
        {
            extern crate alloc;

            use core::mem::transmute;

            use alloc::borrow::Cow;
            use alloc::string::String;

            use $crate::{RefStr, RefString};

            let s: String = $arg.to_owned();
            let refstring: RefString = unsafe { transmute(s) };
            let cow: Cow<'_, RefStr> = Cow::Owned(refstring);
            let qualified: Qualified = unsafe { transmute(cow) };

            qualified
        }
    }};
}

/// Create a [`crate::Component`] from a string literal.
///
/// Similar to [`core::debug_assert`], an optimized build will not validate
/// (but rather perform an unsafe conversion) unless `-C debug-assertions` is
/// passed to the compiler.
#[cfg(any(feature = "macro", test))]
#[macro_export]
macro_rules! component {
    ($arg:literal) => {{
        use $crate::Component;

        #[cfg(debug_assertions)]
        {
            Component::from_refstr($crate::refname!($arg)).expect(core::concat!(
                "literal `",
                $arg,
                "` must be a valid component (cannot contain '/')"
            ))
        }

        #[cfg(not(debug_assertions))]
        {
            extern crate alloc;

            use core::mem::transmute;

            use alloc::borrow::Cow;
            use alloc::string::String;

            use $crate::{RefStr, RefString};

            let s: String = $arg.to_owned();
            let refstring: RefString = unsafe { transmute(s) };
            let cow: Cow<'_, RefStr> = Cow::Owned(refstring);
            let component: Component = unsafe { transmute(cow) };

            component
        }
    }};
}

/// Create a [`crate::refspec::PatternString`] from a string literal.
///
/// Similar to [`core::debug_assert`], an optimized build will not validate
/// (but rather perform an unsafe conversion) unless `-C debug-assertions` is
/// passed to the compiler.
#[cfg(any(feature = "macro", test))]
#[macro_export]
macro_rules! pattern {
    ($arg:literal) => {{
        use $crate::refspec::PatternString;

        #[cfg(debug_assertions)]
        {
            PatternString::try_from($arg).expect(core::concat!(
                "literal `",
                $arg,
                "` must be a valid refspec pattern"
            ))
        }

        #[cfg(not(debug_assertions))]
        {
            extern crate alloc;

            use alloc::string::String;

            let s: String = $arg.to_owned();
            unsafe { core::mem::transmute::<_, PatternString>(s) }
        }
    }};
}

/// Create a [`crate::refspec::QualifiedPattern`] from a string literal.
///
/// Similar to [`core::debug_assert`], an optimized build will not validate
/// (but rather perform an unsafe conversion) unless `-C debug-assertions` is
/// passed to the compiler.
#[cfg(any(feature = "macro", test))]
#[macro_export]
macro_rules! qualified_pattern {
    ($arg:literal) => {{
        use $crate::refspec::QualifiedPattern;

        #[cfg(debug_assertions)]
        {
            use core::concat;

            use $crate::refspec::PatternStr;

            let pattern = PatternStr::try_from_str($arg).expect(concat!(
                "literal `",
                $arg,
                "` must be a valid refspec pattern"
            ));

            QualifiedPattern::from_patternstr(pattern).expect(concat!(
                "literal `",
                $arg,
                "` must be a valid qualified refspec pattern"
            ))
        }

        #[cfg(not(debug_assertions))]
        {
            extern crate alloc;

            use core::mem::transmute;

            use alloc::borrow::Cow;
            use alloc::string::String;

            use $crate::refspec::{PatternStr, PatternString};

            let s: String = $arg.to_owned();
            let pattern: PatternString = unsafe { transmute(s) };
            let cow: Cow<'_, PatternStr> = Cow::Owned(pattern);
            let qualified: QualifiedPattern = unsafe { transmute(cow) };

            qualified
        }
    }};
}

#[cfg(test)]
mod test {
    #[test]
    fn refname() {
        let _ = crate::refname!("refs/heads/main");
        let _ = crate::refname!("refs/tags/v1.0.0");
        let _ = crate::refname!("refs/remotes/origin/main");
        let _ = crate::refname!("a");
    }

    #[test]
    #[should_panic]
    fn refname_invalid() {
        let _ = crate::refname!("a~b");
    }

    #[test]
    fn qualified() {
        let _ = crate::qualified!("refs/heads/main");
        let _ = crate::qualified!("refs/tags/v1.0.0");
        let _ = crate::qualified!("refs/remotes/origin/main");
    }

    #[test]
    #[should_panic]
    fn qualified_invalid() {
        let _ = crate::qualified!("a");
    }

    #[test]
    fn component() {
        let _ = crate::component!("a");
    }

    #[test]
    #[should_panic]
    fn component_invalid() {
        let _ = crate::component!("a/b");
    }

    #[test]
    fn pattern() {
        let _ = crate::pattern!("refs/heads/main");
        let _ = crate::pattern!("refs/tags/v1.0.0");
        let _ = crate::pattern!("refs/remotes/origin/main");

        let _ = crate::pattern!("a");
        let _ = crate::pattern!("a/*");
        let _ = crate::pattern!("*");
        let _ = crate::pattern!("a/b*");
        let _ = crate::pattern!("a/b*/c");
        let _ = crate::pattern!("a/*/c");
    }

    #[test]
    fn qualified_pattern() {
        let _ = crate::qualified_pattern!("refs/heads/main");
        let _ = crate::qualified_pattern!("refs/tags/v1.0.0");
        let _ = crate::qualified_pattern!("refs/remotes/origin/main");

        let _ = crate::qualified_pattern!("refs/heads/main/*");
        let _ = crate::qualified_pattern!("refs/tags/v*");
        let _ = crate::qualified_pattern!("refs/remotes/origin/main");
        let _ = crate::qualified_pattern!("refs/remotes/origin/department/*/person");
    }

    #[test]
    #[should_panic]
    fn qualified_pattern_invalid() {
        let _ = crate::qualified_pattern!("a/*/b");
    }
}