use crate::constants::status_code;
use crate::error::SamlError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TopLevelStatusCode {
Success,
Requester,
Responder,
VersionMismatch,
}
impl TopLevelStatusCode {
pub fn as_uri(self) -> &'static str {
match self {
Self::Success => status_code::SUCCESS,
Self::Requester => status_code::REQUESTER,
Self::Responder => status_code::RESPONDER,
Self::VersionMismatch => status_code::VERSION_MISMATCH,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SubordinateStatusCode {
uri: String,
}
impl SubordinateStatusCode {
pub fn partial_logout() -> Self {
Self {
uri: status_code::PARTIAL_LOGOUT.to_string(),
}
}
pub fn try_new(uri: impl Into<String>) -> Result<Self, SamlError> {
let uri = uri.into();
if uri.is_empty()
|| uri.chars().any(char::is_whitespace)
|| uri
.chars()
.any(|character| matches!(character, '&' | '"' | '\'' | '<' | '>'))
|| url::Url::parse(&uri).is_err()
{
return Err(SamlError::Invalid(
"subordinate status code must be a non-empty absolute URI without whitespace or XML attribute markup"
.into(),
));
}
Ok(Self { uri })
}
pub fn as_uri(&self) -> &str {
&self.uri
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Status {
top_level: TopLevelStatusCode,
subordinate: Option<SubordinateStatusCode>,
}
impl Status {
pub fn success() -> Self {
Self::from_top_level(TopLevelStatusCode::Success)
}
pub fn requester() -> Self {
Self::from_top_level(TopLevelStatusCode::Requester)
}
pub fn responder() -> Self {
Self::from_top_level(TopLevelStatusCode::Responder)
}
pub fn version_mismatch() -> Self {
Self::from_top_level(TopLevelStatusCode::VersionMismatch)
}
fn from_top_level(top_level: TopLevelStatusCode) -> Self {
Self {
top_level,
subordinate: None,
}
}
#[must_use]
pub fn with_subordinate(mut self, subordinate: SubordinateStatusCode) -> Self {
self.subordinate = Some(subordinate);
self
}
pub fn top_level(&self) -> TopLevelStatusCode {
self.top_level
}
pub fn subordinate(&self) -> Option<&SubordinateStatusCode> {
self.subordinate.as_ref()
}
}