use crate::sf::{self, Member};
pub const HEADER: &str = "aauth-requirement";
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Requirement {
AgentToken,
PersonToken,
AuthToken {
resource_token: String,
},
Approval,
Interaction {
url: Option<String>,
code: String,
},
Clarification,
Claims,
}
impl Requirement {
pub fn parse(raw: &str) -> Option<Self> {
let dict = sf::parse_dictionary(raw.trim()).ok()?;
let (_, Member::Item(item)) = dict.iter().find(|(k, _)| k == "requirement")? else {
return None;
};
let param = |name: &str| {
item.params
.iter()
.find(|(k, _)| k == name)
.and_then(|(_, v)| v.as_text())
.map(str::to_string)
};
Some(match item.bare.as_text()? {
"agent-token" => Self::AgentToken,
"person-token" => Self::PersonToken,
"auth-token" => Self::AuthToken {
resource_token: param("resource-token")?,
},
"approval" => Self::Approval,
"interaction" => Self::Interaction {
url: param("url"),
code: param("code")?,
},
"clarification" => Self::Clarification,
"claims" => Self::Claims,
_ => return None,
})
}
pub fn name(&self) -> &'static str {
match self {
Self::AgentToken => "agent-token",
Self::PersonToken => "person-token",
Self::AuthToken { .. } => "auth-token",
Self::Approval => "approval",
Self::Interaction { .. } => "interaction",
Self::Clarification => "clarification",
Self::Claims => "claims",
}
}
pub fn to_header(&self) -> String {
let mut out = format!("requirement={}", self.name());
match self {
Self::AuthToken { resource_token } => {
out.push_str(&format!(";resource-token={}", sf::quote(resource_token)));
}
Self::Interaction { url, code } => {
if let Some(url) = url {
out.push_str(&format!(";url={}", sf::quote(url)));
}
out.push_str(&format!(";code={}", sf::quote(code)));
}
_ => {}
}
out
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn requirements_parse_from_the_header() {
assert_eq!(
Requirement::parse(r#"requirement=auth-token; resource-token="a.b.c""#),
Some(Requirement::AuthToken {
resource_token: "a.b.c".into()
})
);
assert_eq!(
Requirement::parse("requirement=person-token"),
Some(Requirement::PersonToken)
);
assert_eq!(Requirement::parse("requirement=auth-token"), None);
assert_eq!(Requirement::parse("requirement=dance"), None);
assert_eq!(Requirement::parse("garbage(("), None);
}
#[test]
fn a_built_header_parses_back() {
for requirement in [
Requirement::AuthToken {
resource_token: "a.b.c".into(),
},
Requirement::Interaction {
url: Some("https://r.example/i".into()),
code: "ABCD-1234".into(),
},
Requirement::Approval,
] {
assert_eq!(
Requirement::parse(&requirement.to_header()),
Some(requirement)
);
}
}
}