use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TrustProof {
Hmac,
JwtSig,
OAuthCodeExchange,
Ed25519,
}
impl TrustProof {
pub const ALL: &'static [TrustProof] = &[
TrustProof::Hmac,
TrustProof::JwtSig,
TrustProof::OAuthCodeExchange,
TrustProof::Ed25519,
];
pub fn slug(self) -> &'static str {
match self {
TrustProof::Hmac => "hmac",
TrustProof::JwtSig => "jwt_sig",
TrustProof::OAuthCodeExchange => "oauth_code_exchange",
TrustProof::Ed25519 => "ed25519",
}
}
pub fn from_slug(slug: &str) -> Option<TrustProof> {
Self::ALL.iter().copied().find(|p| p.slug() == slug)
}
}
impl fmt::Display for TrustProof {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.slug())
}
}
pub const TRUST_CATALOG: &[&str] = &["hmac", "jwt_sig", "oauth_code_exchange", "ed25519"];
pub const TRUSTED_TYPE_CTOR: &str = "Trusted";
pub const UNTRUSTED_TYPE_CTOR: &str = "Untrusted";
pub fn is_refinement_type(name: &str) -> bool {
name == TRUSTED_TYPE_CTOR || name == UNTRUSTED_TYPE_CTOR
}
pub fn is_trusted_type(name: &str) -> bool {
name == TRUSTED_TYPE_CTOR
}
pub fn is_untrusted_type(name: &str) -> bool {
name == UNTRUSTED_TYPE_CTOR
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RefinementAnnotation {
pub proof: TrustProof,
pub options: Vec<(String, String)>,
}
pub fn parse_refinement_annotation(body: &str) -> Option<RefinementAnnotation> {
let mut parts = body.split(',').map(|p| p.trim());
let proof_slug = parts.next()?.trim();
let proof = TrustProof::from_slug(proof_slug)?;
let mut options = Vec::new();
for raw in parts {
if raw.is_empty() {
continue;
}
if let Some((k, v)) = raw.split_once('=') {
options.push((k.trim().to_string(), v.trim().to_string()));
} else {
return None;
}
}
Some(RefinementAnnotation { proof, options })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn slug_roundtrip_covers_closed_catalog() {
for proof in TrustProof::ALL {
let slug = proof.slug();
assert_eq!(Some(*proof), TrustProof::from_slug(slug));
assert!(TRUST_CATALOG.contains(&slug));
}
assert_eq!(TrustProof::ALL.len(), TRUST_CATALOG.len());
}
#[test]
fn unknown_slug_is_rejected() {
assert!(TrustProof::from_slug("crc32").is_none());
assert!(TrustProof::from_slug("").is_none());
assert!(TrustProof::from_slug("HMAC").is_none()); }
#[test]
fn refinement_constructors_recognised() {
assert!(is_refinement_type("Trusted"));
assert!(is_refinement_type("Untrusted"));
assert!(!is_refinement_type("Option"));
assert!(!is_refinement_type("trusted")); }
#[test]
fn parse_annotation_minimal() {
let ann = parse_refinement_annotation("hmac").unwrap();
assert_eq!(ann.proof, TrustProof::Hmac);
assert!(ann.options.is_empty());
}
#[test]
fn parse_annotation_with_options() {
let ann = parse_refinement_annotation("hmac, key=env.HMAC_KEY, algorithm=SHA256").unwrap();
assert_eq!(ann.proof, TrustProof::Hmac);
assert_eq!(
ann.options,
vec![
("key".to_string(), "env.HMAC_KEY".to_string()),
("algorithm".to_string(), "SHA256".to_string()),
]
);
}
#[test]
fn parse_annotation_rejects_unknown_proof() {
assert!(parse_refinement_annotation("crc32, key=foo").is_none());
}
#[test]
fn parse_annotation_rejects_malformed_option() {
assert!(parse_refinement_annotation("hmac, key_without_value").is_none());
}
}