use super::SecretError;
const PREFIX: &str = "vault:";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VaultRef {
pub mount: String,
pub path: String,
pub field: String,
}
impl std::fmt::Display for VaultRef {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{PREFIX}{}/{}#{}", self.mount, self.path, self.field)
}
}
#[must_use]
pub fn is_reference(raw: &str) -> bool {
raw.starts_with(PREFIX)
}
pub fn parse_reference(raw: &str) -> Result<Option<VaultRef>, SecretError> {
let Some(rest) = raw.strip_prefix(PREFIX) else {
return Ok(None);
};
let invalid =
|| SecretError::InvalidReference("expected vault:<mount>/<path>#<field>".to_string());
let (location, field) = rest.split_once('#').ok_or_else(invalid)?;
let (mount, path) = location.split_once('/').ok_or_else(invalid)?;
if mount.is_empty() || path.is_empty() || field.is_empty() {
return Err(invalid());
}
Ok(Some(VaultRef {
mount: mount.to_string(),
path: path.to_string(),
field: field.to_string(),
}))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_reference_is_told_by_its_prefix_alone() {
assert!(is_reference("vault:secret/overbrainer/nanogpt#api_key"));
assert!(is_reference("vault:"), "malformed, still a reference");
assert!(!is_reference("sk-abc"));
assert!(!is_reference(" vault:x/y#z"));
}
#[test]
fn literal_values_are_not_references() -> Result<(), SecretError> {
assert_eq!(parse_reference("sk-abc")?, None);
Ok(())
}
#[test]
fn full_reference_is_parsed() -> Result<(), SecretError> {
let parsed = parse_reference("vault:secret/overbrainer/nanogpt#api_key")?;
assert_eq!(
parsed,
Some(VaultRef {
mount: "secret".into(),
path: "overbrainer/nanogpt".into(),
field: "api_key".into(),
})
);
Ok(())
}
#[test]
fn malformed_references_are_rejected() {
for raw in [
"vault:",
"vault:secret#k",
"vault:secret/path",
"vault:/path#k",
"vault:secret/#k",
"vault:secret/p#",
] {
assert!(
matches!(parse_reference(raw), Err(SecretError::InvalidReference(_))),
"{raw}"
);
}
}
}