//! Some reference names are protected and cannot be used with canonical //! references. This module contains checks for these cases. //! //! Protected references are: //! 1. `refs/rad` //! 2. Any reference matching `refs/rad/*`, e.g. `refs/rad/id`, `refs/rad/foo/bar`. const REFS_RAD: &str = "refs/rad"; /// Reference-like types, which we encounter when working with canonical references. pub(crate) trait RefLike: AsRef + Ord + std::fmt::Display + serde::Serialize {} #[derive(Debug, thiserror::Error)] pub enum Error { #[error("reference-like string '{REFS_RAD}' is protected")] RefsRad, #[error("reference-like string '{reflike}' is protected because it starts with '{REFS_RAD}/'")] RefsRadChild { reflike: String }, } /// A witnesses that the inner reference-like value is not protected. #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] #[repr(transparent)] #[serde(transparent)] pub(super) struct Unprotected(T); impl Unprotected { pub fn new(reflike: T) -> Result { match reflike .as_ref() .strip_prefix(REFS_RAD) .map(|rest| rest.get(..1)) { Some(None) => Err(Error::RefsRad), Some(Some("/")) => Err(Error::RefsRadChild { reflike: reflike.to_string(), }), Some(_) | None => Ok(Self(reflike)), } } pub fn into_inner(self) -> T { self.0 } } impl AsRef for Unprotected { fn as_ref(&self) -> &T { &self.0 } } impl<'de, T: RefLike + serde::Deserialize<'de>> serde::Deserialize<'de> for Unprotected { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { Self::new(T::deserialize(deserializer)?).map_err(serde::de::Error::custom) } } impl std::fmt::Display for Unprotected { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) } } /// For types that are commonly used in conjunction with [`Unprotected`] /// have some `impl`s and companion infallible injections. mod impls { use crate::git::fmt::{RefString, refspec::QualifiedPattern}; use super::*; /// [`RefString`] models reference names, thus the prototype of what it /// means to be [`RefLike`]. impl RefLike for RefString {} /// A [`QualifiedPattern`] is [`RefLike`] in the sense that it matches a /// (possibly infinite) set of [`crate::git::Qualified`]. impl RefLike for QualifiedPattern<'_> {} } #[cfg(test)] #[allow(clippy::unwrap_used)] mod tests { use crate::assert_matches; use crate::git::fmt::refname; use super::{Error::*, Unprotected}; #[test] fn refs_rad() { assert_matches!(Unprotected::new(refname!("refs/rad")), Err(RefsRad)) } #[test] fn refs_rad_id() { assert_matches!( Unprotected::new(refname!("refs/rad/id")), Err(RefsRadChild { .. }) ) } #[test] fn refs_radieschen() { assert_matches!(Unprotected::new(refname!("refs/radieschen")), Ok(_)) } }