use std::{fmt, str::FromStr};
use crate::error::UrnParseError;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Urn {
pub source_id: String,
pub name: String,
}
impl Urn {
pub fn parse(s: &str) -> Result<Self, UrnParseError> {
s.parse()
}
}
impl FromStr for Urn {
type Err = UrnParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: Vec<&str> = s.splitn(4, ':').collect();
if parts.len() != 4 {
return Err(UrnParseError::WrongSegmentCount(parts.len()));
}
if !parts[0].eq_ignore_ascii_case("urn") {
return Err(UrnParseError::InvalidScheme(parts[0].to_owned()));
}
if !parts[1].eq_ignore_ascii_case("secrets-rs") {
return Err(UrnParseError::InvalidNid(parts[1].to_owned()));
}
if parts[2].is_empty() {
return Err(UrnParseError::EmptySourceId);
}
if parts[3].is_empty() {
return Err(UrnParseError::EmptyName);
}
Ok(Urn {
source_id: parts[2].to_owned(),
name: parts[3].to_owned(),
})
}
}
impl fmt::Display for Urn {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "urn:secrets-rs:{}:{}", self.source_id, self.name)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_valid_urn() {
let urn: Urn = "urn:secrets-rs:env:MY_SECRET".parse().unwrap();
assert_eq!(urn.source_id, "env");
assert_eq!(urn.name, "MY_SECRET");
}
#[test]
fn case_insensitive_scheme_and_nid() {
let urn: Urn = "URN:SECRETS-RS:env:KEY".parse().unwrap();
assert_eq!(urn.source_id, "env");
assert_eq!(urn.name, "KEY");
}
#[test]
fn display_uses_lowercase_prefix() {
let urn: Urn = "urn:secrets-rs:env:KEY".parse().unwrap();
assert_eq!(urn.to_string(), "urn:secrets-rs:env:KEY");
}
#[test]
fn name_may_contain_colons() {
let urn: Urn = "urn:secrets-rs:env:a:b:c".parse().unwrap();
assert_eq!(urn.name, "a:b:c");
}
#[test]
fn error_on_wrong_scheme() {
let err = "nrn:secrets-rs:env:KEY".parse::<Urn>().unwrap_err();
assert!(matches!(err, UrnParseError::InvalidScheme(_)));
}
#[test]
fn error_on_wrong_nid() {
let err = "urn:other:env:KEY".parse::<Urn>().unwrap_err();
assert!(matches!(err, UrnParseError::InvalidNid(_)));
}
#[test]
fn error_on_too_few_segments() {
let err = "urn:secrets-rs:env".parse::<Urn>().unwrap_err();
assert!(matches!(err, UrnParseError::WrongSegmentCount(_)));
}
#[test]
fn error_on_empty_source_id() {
let err = "urn:secrets-rs::KEY".parse::<Urn>().unwrap_err();
assert_eq!(err, UrnParseError::EmptySourceId);
}
#[test]
fn error_on_empty_name() {
let err = "urn:secrets-rs:env:".parse::<Urn>().unwrap_err();
assert_eq!(err, UrnParseError::EmptyName);
}
}