1use crate::error::SamlError;
2
3pub const MAX_RELAY_STATE_BYTES: usize = 80;
8
9#[derive(Debug, Clone, PartialEq, Eq, Hash)]
11pub struct RelayState(String);
12
13impl RelayState {
14 pub fn try_new(value: impl Into<String>) -> Result<Self, SamlError> {
24 let value = value.into();
25 validate_relay_state_bytes(&value)?;
26 Ok(Self(value))
27 }
28
29 pub fn as_str(&self) -> &str {
31 &self.0
32 }
33}
34
35#[derive(Debug, Clone, PartialEq, Eq, Hash)]
37pub enum RelayStateParam {
38 Absent,
40 PresentEmpty,
42 PresentValue(RelayState),
44}
45
46impl RelayStateParam {
47 pub fn absent() -> Self {
49 Self::Absent
50 }
51
52 pub fn present_empty() -> Self {
54 Self::PresentEmpty
55 }
56
57 pub fn try_from_option(value: Option<impl Into<String>>) -> Result<Self, SamlError> {
64 match value {
65 None => Ok(Self::Absent),
66 Some(value) => {
67 let value = value.into();
68 if value.is_empty() {
69 Ok(Self::PresentEmpty)
70 } else {
71 Ok(Self::PresentValue(RelayState::try_new(value)?))
72 }
73 }
74 }
75 }
76
77 pub fn as_deref(&self) -> Option<&str> {
79 match self {
80 Self::Absent => None,
81 Self::PresentEmpty => Some(""),
82 Self::PresentValue(value) => Some(value.as_str()),
83 }
84 }
85
86 pub(crate) fn validate(&self) -> Result<(), SamlError> {
87 if matches!(self, Self::PresentValue(value) if value.as_str().is_empty()) {
88 return Err(SamlError::Invalid(
89 "RelayState PresentValue must not be empty".into(),
90 ));
91 }
92 if let Some(value) = self.as_deref() {
93 validate_relay_state_bytes(value)?;
94 }
95 Ok(())
96 }
97}
98
99impl TryFrom<Option<String>> for RelayStateParam {
100 type Error = SamlError;
101
102 fn try_from(value: Option<String>) -> Result<Self, Self::Error> {
103 Self::try_from_option(value)
104 }
105}
106
107fn validate_relay_state_bytes(value: &str) -> Result<(), SamlError> {
108 if value.len() > MAX_RELAY_STATE_BYTES {
109 return Err(SamlError::Invalid(
110 "RelayState exceeds SAML Bindings 80-byte limit".into(),
111 ));
112 }
113 Ok(())
114}