#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum SpoolFacet {
Content,
Governance,
Membership,
Named(String),
}
impl SpoolFacet {
pub fn token(&self) -> &str {
match self {
SpoolFacet::Content => "content",
SpoolFacet::Governance => "governance",
SpoolFacet::Membership => "membership",
SpoolFacet::Named(token) => token.as_str(),
}
}
pub fn is_default(&self) -> bool {
matches!(self, SpoolFacet::Content) || self.token() == "content"
}
pub fn scope_token(&self, base_scope: &str) -> String {
if self.is_default() {
base_scope.to_string()
} else {
format!("{base_scope}/{}", self.token())
}
}
pub fn ref_prefix(&self) -> String {
format!("refs/spool/{}", self.token())
}
pub fn thread_ref(&self, name: &str) -> String {
format!("{}/threads/{name}", self.ref_prefix())
}
pub fn marker_ref(&self, name: &str) -> String {
format!("{}/markers/{name}", self.ref_prefix())
}
}
impl std::fmt::Display for SpoolFacet {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.token())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::validate_ref_name;
#[test]
fn content_facet_scope_token_is_unchanged_base() {
let base = "wt-0123456789abcdef";
assert_eq!(SpoolFacet::Content.scope_token(base), base);
assert_eq!(
SpoolFacet::Named("content".into()).scope_token(base),
base
);
}
#[test]
fn named_facets_get_independent_suffixed_scopes() {
let base = "wt-0123456789abcdef";
assert_eq!(
SpoolFacet::Governance.scope_token(base),
"wt-0123456789abcdef/governance"
);
assert_eq!(
SpoolFacet::Membership.scope_token(base),
"wt-0123456789abcdef/membership"
);
assert_ne!(
SpoolFacet::Governance.scope_token(base),
SpoolFacet::Membership.scope_token(base)
);
assert_ne!(
SpoolFacet::Governance.scope_token(base),
SpoolFacet::Content.scope_token(base)
);
}
#[test]
fn facet_ref_names_validate() {
for facet in [
SpoolFacet::Content,
SpoolFacet::Governance,
SpoolFacet::Membership,
SpoolFacet::Named("audit".into()),
] {
assert!(validate_ref_name(&facet.ref_prefix()).is_ok());
assert!(validate_ref_name(&facet.thread_ref("main")).is_ok());
assert!(validate_ref_name(&facet.marker_ref("v1")).is_ok());
}
}
#[test]
fn distinct_facets_have_distinct_ref_prefixes() {
assert_eq!(
SpoolFacet::Governance.thread_ref("main"),
"refs/spool/governance/threads/main"
);
assert_ne!(
SpoolFacet::Governance.thread_ref("main"),
SpoolFacet::Content.thread_ref("main")
);
}
}