1use crate::constants::status_code;
2use crate::error::SamlError;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9pub enum TopLevelStatusCode {
10 Success,
12 Requester,
14 Responder,
16 VersionMismatch,
18}
19
20impl TopLevelStatusCode {
21 pub fn as_uri(self) -> &'static str {
23 match self {
24 Self::Success => status_code::SUCCESS,
25 Self::Requester => status_code::REQUESTER,
26 Self::Responder => status_code::RESPONDER,
27 Self::VersionMismatch => status_code::VERSION_MISMATCH,
28 }
29 }
30}
31
32#[derive(Debug, Clone, PartialEq, Eq, Hash)]
37pub struct SubordinateStatusCode {
38 uri: String,
39}
40
41impl SubordinateStatusCode {
42 pub fn partial_logout() -> Self {
44 Self {
45 uri: status_code::PARTIAL_LOGOUT.to_string(),
46 }
47 }
48
49 pub fn try_new(uri: impl Into<String>) -> Result<Self, SamlError> {
56 let uri = uri.into();
57 if uri.is_empty()
58 || uri.chars().any(char::is_whitespace)
59 || uri
60 .chars()
61 .any(|character| matches!(character, '&' | '"' | '\'' | '<' | '>'))
62 || url::Url::parse(&uri).is_err()
63 {
64 return Err(SamlError::Invalid(
65 "subordinate status code must be a non-empty absolute URI without whitespace or XML attribute markup"
66 .into(),
67 ));
68 }
69 Ok(Self { uri })
70 }
71
72 pub fn as_uri(&self) -> &str {
74 &self.uri
75 }
76}
77
78#[derive(Debug, Clone, PartialEq, Eq)]
80pub struct Status {
81 top_level: TopLevelStatusCode,
82 subordinate: Option<SubordinateStatusCode>,
83}
84
85impl Status {
86 pub fn success() -> Self {
88 Self::from_top_level(TopLevelStatusCode::Success)
89 }
90
91 pub fn requester() -> Self {
93 Self::from_top_level(TopLevelStatusCode::Requester)
94 }
95
96 pub fn responder() -> Self {
98 Self::from_top_level(TopLevelStatusCode::Responder)
99 }
100
101 pub fn version_mismatch() -> Self {
103 Self::from_top_level(TopLevelStatusCode::VersionMismatch)
104 }
105
106 fn from_top_level(top_level: TopLevelStatusCode) -> Self {
107 Self {
108 top_level,
109 subordinate: None,
110 }
111 }
112
113 #[must_use]
117 pub fn with_subordinate(mut self, subordinate: SubordinateStatusCode) -> Self {
118 self.subordinate = Some(subordinate);
119 self
120 }
121
122 pub fn top_level(&self) -> TopLevelStatusCode {
124 self.top_level
125 }
126
127 pub fn subordinate(&self) -> Option<&SubordinateStatusCode> {
129 self.subordinate.as_ref()
130 }
131}