use crate::namespaces::NamespaceError;
use crate::namespaces::constants::{
FRAGMENT_DELIMITER, REGISTERED_NAMESPACES, RESERVED_INVALID_NAMESPACES,
RESERVED_TEST_NAMESPACES, UNREGISTERED_PREFIX,
};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum BaseNamespace {
Registered {
name: String,
fragment: Option<String>,
},
Unregistered {
reverse_domain: String,
fragment: String,
},
}
impl std::fmt::Display for BaseNamespace {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BaseNamespace::Registered { name, fragment } => {
write!(f, "{}", name)?;
if let Some(frag) = fragment {
write!(f, "{}{}", FRAGMENT_DELIMITER, frag)?;
}
Ok(())
}
BaseNamespace::Unregistered {
reverse_domain,
fragment,
} => {
write!(
f,
"{}{}{}{}",
UNREGISTERED_PREFIX, reverse_domain, FRAGMENT_DELIMITER, fragment
)
}
}
}
}
impl BaseNamespace {
pub(crate) fn parse_base(
base: &str,
allow_test: bool,
) -> Result<BaseNamespace, NamespaceError> {
if base.starts_with(UNREGISTERED_PREFIX) {
Self::parse_unregistered_base(base, allow_test)
} else {
Self::parse_registered_base(base, allow_test)
}
}
fn parse_registered_base(
base: &str,
allow_test: bool,
) -> Result<BaseNamespace, NamespaceError> {
let (name, fragment) = if let Some((name, fragment)) = base.split_once(FRAGMENT_DELIMITER) {
if fragment.is_empty() {
return Err(NamespaceError::EmptyFragment);
}
(name, Some(fragment))
} else {
(base, None)
};
if name.is_empty() {
return Err(NamespaceError::EmptyBase);
}
if RESERVED_INVALID_NAMESPACES.contains(&name) {
return Err(NamespaceError::ReservedForbiddenNamespace {
namespace: name.to_string(),
});
}
if RESERVED_TEST_NAMESPACES.contains(&name) && !allow_test {
return Err(NamespaceError::ReservedTestNamespace {
namespace: name.to_string(),
});
}
if !REGISTERED_NAMESPACES.contains(&name) {
return Err(NamespaceError::InvalidRegisteredNamespace {
namespace: name.to_string(),
});
}
Ok(BaseNamespace::Registered {
name: name.to_string(),
fragment: fragment.map(|f| f.to_string()),
})
}
fn parse_unregistered_base(
base: &str,
allow_test: bool,
) -> Result<BaseNamespace, NamespaceError> {
let (prefixed_reverse_domain, fragment) = base
.split_once(FRAGMENT_DELIMITER)
.ok_or(NamespaceError::UnregisteredNamespaceMissingFragment)?;
if RESERVED_INVALID_NAMESPACES.contains(&prefixed_reverse_domain) {
return Err(NamespaceError::ReservedForbiddenNamespace {
namespace: prefixed_reverse_domain.to_string(),
});
}
if RESERVED_TEST_NAMESPACES.contains(&prefixed_reverse_domain) && !allow_test {
return Err(NamespaceError::ReservedTestNamespace {
namespace: prefixed_reverse_domain.to_string(),
});
}
let reverse_domain = &prefixed_reverse_domain[2..];
if reverse_domain.is_empty() {
return Err(NamespaceError::EmptyReverseDomain);
}
if fragment.is_empty() {
return Err(NamespaceError::EmptyFragment);
}
Ok(BaseNamespace::Unregistered {
reverse_domain: reverse_domain.to_string(),
fragment: fragment.to_string(),
})
}
}
#[cfg(test)]
mod test_registered {
use super::*;
use rstest::rstest;
#[test]
fn test_parse_ssvc() {
let result = BaseNamespace::parse_base("ssvc", true);
assert!(result.is_ok());
match result.unwrap() {
BaseNamespace::Registered { name, fragment } => {
assert_eq!(name, "ssvc");
assert!(fragment.is_none());
}
BaseNamespace::Unregistered { .. } => panic!("Expected Registered variant"),
}
}
#[test]
fn test_parse_nist_800_30() {
let result = BaseNamespace::parse_base("nist#800-30", true);
assert!(result.is_ok());
match result.unwrap() {
BaseNamespace::Registered { name, fragment } => {
assert_eq!(name, "nist");
assert_eq!(fragment, Some("800-30".to_string()));
}
BaseNamespace::Unregistered { .. } => panic!("Expected Registered variant"),
}
}
#[test]
fn test_parse_registered_forbidden_namespace() {
let result = BaseNamespace::parse_base("invalid", true);
assert!(matches!(
result,
Err(NamespaceError::ReservedForbiddenNamespace { .. })
));
}
#[rstest]
#[case("test", true, true)]
#[case("test", false, false)]
fn test_parse_registered_test_namespace(
#[case] base: &str,
#[case] allow_test: bool,
#[case] should_succeed: bool,
) {
let result = BaseNamespace::parse_base(base, allow_test);
if should_succeed {
assert!(matches!(result, Ok(BaseNamespace::Registered { .. })));
} else {
assert!(matches!(
result,
Err(NamespaceError::ReservedTestNamespace { .. })
));
}
}
#[test]
fn test_parse_invalid_registered_namespace() {
let result = BaseNamespace::parse_base("sxyz", true);
assert!(matches!(
result,
Err(NamespaceError::InvalidRegisteredNamespace { .. })
));
}
#[test]
fn test_parse_registered_empty_base() {
let result = BaseNamespace::parse_base("#some-fragment", true);
println!("result: {:?}", result);
assert!(matches!(result, Err(NamespaceError::EmptyBase)));
}
#[test]
fn test_parse_registered_empty_fragment() {
let result = BaseNamespace::parse_base("test#", true);
assert!(matches!(result, Err(NamespaceError::EmptyFragment)));
}
#[test]
fn test_parse_registered_with_fragment() {
let result = BaseNamespace::parse_base("test#some-fragment", true);
assert!(matches!(
result,
Ok(BaseNamespace::Registered {
fragment: Some(_),
..
})
));
}
#[test]
fn test_parse_registered_without_fragment() {
let result = BaseNamespace::parse_base("test", true);
assert!(matches!(
result,
Ok(BaseNamespace::Registered { fragment: None, .. })
));
}
}
#[cfg(test)]
mod test_unregistered {
use super::*;
use rstest::rstest;
#[test]
fn test_parse_unregistered_missing_fragment() {
let result = BaseNamespace::parse_base("x_com.example", true);
assert!(matches!(
result,
Err(NamespaceError::UnregisteredNamespaceMissingFragment)
));
}
#[test]
fn test_parse_unregistered_forbidden_namespace() {
let result = BaseNamespace::parse_base("x_invalid#fragment", true);
assert!(matches!(
result,
Err(NamespaceError::ReservedForbiddenNamespace { .. })
));
}
#[rstest]
#[case("x_test", true, true)]
#[case("x_test", false, false)]
fn test_parse_unregistered_test_namespace(
#[case] base: &str,
#[case] allow_test: bool,
#[case] should_succeed: bool,
) {
let test_base = format!("{}#fragment", base);
let result = BaseNamespace::parse_base(&test_base, allow_test);
if should_succeed {
assert!(matches!(result, Ok(BaseNamespace::Unregistered { .. })));
} else {
assert!(matches!(
result,
Err(NamespaceError::ReservedTestNamespace { .. })
));
}
}
#[test]
fn test_parse_unregistered_empty_domain() {
let result = BaseNamespace::parse_base("x_#fragment", true);
assert!(matches!(result, Err(NamespaceError::EmptyReverseDomain)));
}
#[test]
fn test_parse_unregistered_empty_fragment() {
let result = BaseNamespace::parse_base("x_com.example#", true);
assert!(matches!(result, Err(NamespaceError::EmptyFragment)));
}
#[test]
fn test_parse_unregistered_with_fragment() {
let result = BaseNamespace::parse_base("x_com.example#my-fragment", true);
assert!(matches!(result, Ok(BaseNamespace::Unregistered { .. })));
}
}