use crate::release::version::Ybf;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct CrateTag {
pub(crate) crate_name: String,
pub(crate) version: Ybf,
}
impl CrateTag {
pub(crate) fn render(crate_name: &str, version: &Ybf) -> String {
format!("{crate_name}-v{version}")
}
}
impl std::fmt::Display for CrateTag {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(formatter, "{}-v{}", self.crate_name, self.version)
}
}
pub(crate) fn parse_crate_tag(tag: &str) -> Option<CrateTag> {
let dash_v = tag.rfind("-v")?;
let after = &tag[dash_v + 2..];
let first = after.chars().next()?;
if !first.is_ascii_digit() {
return None;
}
let crate_name = &tag[..dash_v];
let version = Ybf::parse(after).ok()?;
if crate_name.is_empty() {
return None;
}
Some(CrateTag {
crate_name: crate_name.to_string(),
version,
})
}
#[allow(dead_code)]
pub(crate) fn is_crate_tag(tag: &str) -> bool {
parse_crate_tag(tag).is_some()
}
#[allow(dead_code)]
pub(crate) fn is_platform_tag(tag: &str) -> bool {
if let Some(rest) = tag.strip_prefix('v') {
let parts: Vec<&str> = rest.split('.').collect();
if parts.len() == 2 {
return parts
.iter()
.all(|p| !p.is_empty() && p.bytes().all(|b| b.is_ascii_digit()));
}
}
false
}
#[allow(dead_code)]
pub(crate) fn is_transaction_tag(tag: &str) -> bool {
tag.starts_with("release-")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn render_crate_tag() {
let v = Ybf::parse("2026.1.7").unwrap();
assert_eq!(
CrateTag::render("arcature-auth", &v),
"arcature-auth-v2026.1.7"
);
}
#[test]
fn parse_valid_crate_tag() {
let tag = parse_crate_tag("arcature-auth-v2026.1.7").expect("parses");
assert_eq!(tag.crate_name, "arcature-auth");
assert_eq!(tag.version, Ybf::parse("2026.1.7").unwrap());
}
#[test]
fn parse_crate_tag_with_multi_hyphen_name() {
let tag = parse_crate_tag("arcature-dx-v2026.2.0").expect("parses");
assert_eq!(tag.crate_name, "arcature-dx");
assert_eq!(tag.version, Ybf::parse("2026.2.0").unwrap());
}
#[test]
fn parse_invalid_crate_tag() {
assert!(parse_crate_tag("v2026.1.0").is_none());
assert!(parse_crate_tag("release-2026-08-15.01").is_none());
assert!(parse_crate_tag("arcature-auth-vnot-a-version").is_none());
assert!(parse_crate_tag("-v2026.1.0").is_none());
}
#[test]
fn is_crate_tag_classifier() {
assert!(is_crate_tag("arcature-auth-v2026.1.7"));
assert!(!is_crate_tag("v2026.1.0"));
assert!(!is_crate_tag("release-2026-08-15.01"));
}
#[test]
fn is_platform_tag_classifier() {
assert!(is_platform_tag("v2026.1"));
assert!(is_platform_tag("v2027.0"));
assert!(!is_platform_tag("arcature-auth-v2026.1.7"));
assert!(!is_platform_tag("release-2026-08-15.01"));
}
#[test]
fn is_transaction_tag_classifier() {
assert!(is_transaction_tag("release-2026-08-15.01"));
assert!(!is_transaction_tag("v2026.1"));
assert!(!is_transaction_tag("arcature-auth-v2026.1.7"));
}
#[test]
fn display_tag() {
let tag = CrateTag {
crate_name: "arcature-auth".to_string(),
version: Ybf::parse("2026.1.7").unwrap(),
};
assert_eq!(tag.to_string(), "arcature-auth-v2026.1.7");
}
#[test]
fn three_tag_kinds_mutually_exclusive() {
assert!(!is_crate_tag("v2026.1"));
assert!(!is_crate_tag("release-2026-08-15.01"));
assert!(!is_platform_tag("arcature-auth-v2026.1.7"));
assert!(!is_platform_tag("release-2026-08-15.01"));
assert!(!is_transaction_tag("v2026.1"));
assert!(!is_transaction_tag("arcature-auth-v2026.1.7"));
}
}