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