use std::str::FromStr;
use serde::{Deserialize, Serialize};
use radicle::{
cob::patch::PatchId,
git::{BranchName, Component, Namespaced, Qualified, RefStr, RefString},
};
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
pub struct GenericRefName(RefString);
impl GenericRefName {
pub fn as_str(&self) -> &str {
self.as_ref()
}
}
impl AsRef<str> for GenericRefName {
fn as_ref(&self) -> &str {
self.0.as_str()
}
}
impl From<Qualified<'_>> for GenericRefName {
fn from(from: Qualified<'_>) -> Self {
Self::from(&from)
}
}
impl From<&Qualified<'_>> for GenericRefName {
fn from(from: &Qualified<'_>) -> Self {
Self(from.to_ref_string())
}
}
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
pub struct TagName(RefString);
impl TagName {
pub fn starts_with(&self, s: &str) -> bool {
self.as_ref().starts_with(s)
}
pub fn as_str(&self) -> &str {
self.as_ref()
}
}
impl TryFrom<&str> for TagName {
type Error = RefError;
fn try_from(from: &str) -> Result<Self, RefError> {
Ok(Self(RefString::try_from(from).map_err(|err| {
RefError::RefStrCreate(from.to_string(), err)
})?))
}
}
impl From<RefString> for TagName {
fn from(from: RefString) -> Self {
Self(from)
}
}
impl AsRef<str> for TagName {
fn as_ref(&self) -> &str {
self.0.as_str()
}
}
pub fn branch_ref(name: &RefStr) -> Result<BranchName, RefError> {
if name.starts_with("refs/") {
return Err(RefError::RefsInName(name.to_ref_string()));
}
ref_string(name)
}
pub fn branch_from_str(s: &str) -> Result<BranchName, RefError> {
branch_ref(&ref_string(s)?)
}
pub fn qualified_branch(name: &BranchName) -> Result<Qualified<'_>, RefError> {
if name.starts_with("refs/") {
return Err(RefError::RefsInName(name.to_ref_string()));
}
let qualified_name = ref_string(&format!("refs/heads/{name}"))?;
let x = Qualified::from_refstr(qualified_name);
x.ok_or(RefError::QualifiedCreate(name.clone()))
}
pub fn ref_string(s: &str) -> Result<RefString, RefError> {
RefString::try_from(s).map_err(|err| RefError::RefStrCreate(s.into(), err))
}
pub fn namespaced_branch<'a>(
ns: &RefStr,
branch: &'a BranchName,
) -> Result<Namespaced<'a>, RefError> {
let ns = Component::from_refstr(ns).ok_or(RefError::NamespaceName(ns.to_ref_string()))?;
Ok(qualified_branch(branch)?.with_namespace(ns))
}
pub fn namespaced_from_str(s: &str) -> Result<Namespaced<'_>, RefError> {
assert!(s.starts_with("refs/namespaces/"));
let rs = ref_string(s)?;
Ok(rs
.to_namespaced()
.ok_or(RefError::NamespacedCreate(s.into()))?
.to_owned())
}
pub fn branch_from_namespaced(ns: &Namespaced) -> Result<BranchName, RefError> {
let plain_ref = ns.strip_namespace();
let (refs, heads, first, rest) = plain_ref.non_empty_components();
if refs.as_str() != "refs" || heads.as_str() != "heads" {
return Err(RefError::NotABranch(ns.to_ref_string()));
}
Ok(BranchName::from_iter(
[first.into_inner().to_ref_string()]
.iter()
.cloned()
.chain(rest.map(|c| c.into_inner().to_ref_string())),
))
}
pub fn patch_from_str(s: &str) -> Result<PatchId, RefError> {
PatchId::from_str(s).map_err(|_| RefError::PatchIdFromStr(s.into()))
}
#[derive(Debug, thiserror::Error, Eq, PartialEq)]
pub enum RefError {
#[error("programming error: plain branch name must not start with refs/: {0:?}")]
RefsInName(RefString),
#[error("failed to create a RefStr from a string {0:?}")]
RefStrCreate(String, #[source] radicle::git::fmt::Error),
#[error("failed to create a qualified branch name from a branch ref {0:?}")]
QualifiedCreate(BranchName),
#[error("failed to create a name spaced Git ref from {0:?}")]
NamespacedCreate(String),
#[error("failed to get branch name from {0:?})")]
NotABranch(RefString),
#[error("failed to create a name space component from its name {0:?}")]
NamespaceName(RefString),
#[error("failed to create a patch id from string: {0:?}")]
PatchIdFromStr(String),
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod test {
use std::str::FromStr;
use super::*;
#[test]
fn ref_string_from_plain_branch_name() {
assert_eq!(ref_string("main").map(|x| x.to_string()), Ok("main".into()));
}
#[test]
fn plain_branch_name() {
assert_eq!(
branch_ref(&ref_string("main").unwrap()).map(|x| x.to_string()),
Ok("main".into())
);
}
#[test]
fn qualified_branch_name_from_plain() {
let name = branch_ref(&ref_string("main").unwrap()).unwrap();
assert_eq!(
qualified_branch(&name).map(|x| x.to_string()),
Ok("refs/heads/main".into())
);
}
#[test]
fn namespaced_branch_from_plain() {
let branch = branch_ref(&ref_string("main").unwrap()).unwrap();
let ns = ref_string("node1").unwrap();
let name = namespaced_branch(&ns, &branch);
assert_eq!(
name.map(|x| x.to_string()),
Ok("refs/namespaces/node1/refs/heads/main".into())
);
}
#[test]
fn extracts_branch_namespaced_branch() {
let branch = branch_ref(&ref_string("main").unwrap()).unwrap();
let ns = ref_string("node1").unwrap();
let name = namespaced_branch(&ns, &branch).unwrap();
let extracted = branch_from_namespaced(&name);
assert_eq!(extracted.map(|x| x.to_string()), Ok("main".into()));
}
#[test]
fn creates_patch_from_str() {
let oid = PatchId::from_str("e76d814f6934f24d45f628f8ff9533dcdefc1bd8").unwrap();
assert_eq!(
patch_from_str("e76d814f6934f24d45f628f8ff9533dcdefc1bd8"),
Ok(oid)
);
}
}