Skip to main content

freeswitch_types/sofia/
gateway.rs

1//! Gateway registration state and ping status enums.
2
3use std::fmt;
4use std::str::FromStr;
5
6/// Error returned when parsing an unrecognized gateway registration state.
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct ParseGatewayRegStateError(pub String);
9
10impl fmt::Display for ParseGatewayRegStateError {
11    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
12        write!(f, "unknown gateway reg state: {}", self.0)
13    }
14}
15
16impl std::error::Error for ParseGatewayRegStateError {}
17
18/// Gateway registration state from `sofia::gateway_state` events.
19///
20/// The `State` header value, mapping to `reg_state_t` / `sofia_state_names[]`
21/// in mod_sofia.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
24#[cfg_attr(feature = "serde", serde(rename_all = "SCREAMING_SNAKE_CASE"))]
25#[non_exhaustive]
26#[allow(missing_docs)]
27pub enum GatewayRegState {
28    #[cfg_attr(feature = "serde", serde(alias = "Unreged"))]
29    Unreged,
30    #[cfg_attr(feature = "serde", serde(alias = "Trying"))]
31    Trying,
32    #[cfg_attr(feature = "serde", serde(alias = "Register"))]
33    Register,
34    #[cfg_attr(feature = "serde", serde(alias = "Reged"))]
35    Reged,
36    #[cfg_attr(feature = "serde", serde(alias = "Unregister"))]
37    Unregister,
38    #[cfg_attr(feature = "serde", serde(alias = "Failed"))]
39    Failed,
40    #[cfg_attr(feature = "serde", serde(alias = "FailWait"))]
41    FailWait,
42    #[cfg_attr(feature = "serde", serde(alias = "Expired"))]
43    Expired,
44    #[cfg_attr(feature = "serde", serde(alias = "Noreg"))]
45    Noreg,
46    #[cfg_attr(feature = "serde", serde(alias = "Down"))]
47    Down,
48    #[cfg_attr(feature = "serde", serde(alias = "Timeout"))]
49    Timeout,
50}
51
52impl GatewayRegState {
53    /// Wire-format string matching `sofia_state_names[]`.
54    pub const fn as_str(&self) -> &'static str {
55        match self {
56            Self::Unreged => "UNREGED",
57            Self::Trying => "TRYING",
58            Self::Register => "REGISTER",
59            Self::Reged => "REGED",
60            Self::Unregister => "UNREGISTER",
61            Self::Failed => "FAILED",
62            Self::FailWait => "FAIL_WAIT",
63            Self::Expired => "EXPIRED",
64            Self::Noreg => "NOREG",
65            Self::Down => "DOWN",
66            Self::Timeout => "TIMEOUT",
67        }
68    }
69}
70
71impl fmt::Display for GatewayRegState {
72    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73        f.write_str(self.as_str())
74    }
75}
76
77impl FromStr for GatewayRegState {
78    type Err = ParseGatewayRegStateError;
79
80    fn from_str(s: &str) -> Result<Self, Self::Err> {
81        match s {
82            "UNREGED" => Ok(Self::Unreged),
83            "TRYING" => Ok(Self::Trying),
84            "REGISTER" => Ok(Self::Register),
85            "REGED" => Ok(Self::Reged),
86            "UNREGISTER" => Ok(Self::Unregister),
87            "FAILED" => Ok(Self::Failed),
88            "FAIL_WAIT" => Ok(Self::FailWait),
89            "EXPIRED" => Ok(Self::Expired),
90            "NOREG" => Ok(Self::Noreg),
91            "DOWN" => Ok(Self::Down),
92            "TIMEOUT" => Ok(Self::Timeout),
93            _ => Err(ParseGatewayRegStateError(s.to_string())),
94        }
95    }
96}
97
98/// Error returned when parsing an unrecognized gateway ping status.
99#[derive(Debug, Clone, PartialEq, Eq)]
100pub struct ParseGatewayPingStatusError(pub String);
101
102impl fmt::Display for ParseGatewayPingStatusError {
103    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104        write!(f, "unknown gateway ping status: {}", self.0)
105    }
106}
107
108impl std::error::Error for ParseGatewayPingStatusError {}
109
110/// Gateway ping status from `sofia::gateway_state` events.
111///
112/// The `Ping-Status` header value, mapping to `sofia_gateway_status_name()`.
113#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
114#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
115#[cfg_attr(feature = "serde", serde(rename_all = "SCREAMING_SNAKE_CASE"))]
116#[non_exhaustive]
117#[allow(missing_docs)]
118pub enum GatewayPingStatus {
119    #[cfg_attr(feature = "serde", serde(alias = "Down"))]
120    Down,
121    #[cfg_attr(feature = "serde", serde(alias = "Up"))]
122    Up,
123    #[cfg_attr(feature = "serde", serde(alias = "Invalid"))]
124    Invalid,
125}
126
127impl GatewayPingStatus {
128    /// Wire-format string.
129    pub const fn as_str(&self) -> &'static str {
130        match self {
131            Self::Down => "DOWN",
132            Self::Up => "UP",
133            Self::Invalid => "INVALID",
134        }
135    }
136}
137
138impl fmt::Display for GatewayPingStatus {
139    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140        f.write_str(self.as_str())
141    }
142}
143
144impl FromStr for GatewayPingStatus {
145    type Err = ParseGatewayPingStatusError;
146
147    fn from_str(s: &str) -> Result<Self, Self::Err> {
148        match s {
149            "DOWN" => Ok(Self::Down),
150            "UP" => Ok(Self::Up),
151            "INVALID" => Ok(Self::Invalid),
152            _ => Err(ParseGatewayPingStatusError(s.to_string())),
153        }
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    const ALL_REG_STATES: &[GatewayRegState] = &[
162        GatewayRegState::Unreged,
163        GatewayRegState::Trying,
164        GatewayRegState::Register,
165        GatewayRegState::Reged,
166        GatewayRegState::Unregister,
167        GatewayRegState::Failed,
168        GatewayRegState::FailWait,
169        GatewayRegState::Expired,
170        GatewayRegState::Noreg,
171        GatewayRegState::Down,
172        GatewayRegState::Timeout,
173    ];
174
175    #[test]
176    fn reg_state_round_trip() {
177        for &state in ALL_REG_STATES {
178            let wire = state.to_string();
179            let parsed: GatewayRegState = wire
180                .parse()
181                .unwrap();
182            assert_eq!(parsed, state, "round-trip failed for {wire}");
183        }
184    }
185
186    #[test]
187    fn reg_state_count() {
188        assert_eq!(ALL_REG_STATES.len(), 11);
189    }
190
191    #[test]
192    fn reg_state_rejects_unknown() {
193        assert!("BOGUS"
194            .parse::<GatewayRegState>()
195            .is_err());
196        assert!("reged"
197            .parse::<GatewayRegState>()
198            .is_err());
199    }
200
201    const ALL_PING_STATUSES: &[GatewayPingStatus] = &[
202        GatewayPingStatus::Down,
203        GatewayPingStatus::Up,
204        GatewayPingStatus::Invalid,
205    ];
206
207    #[test]
208    fn ping_status_round_trip() {
209        for &status in ALL_PING_STATUSES {
210            let wire = status.to_string();
211            let parsed: GatewayPingStatus = wire
212                .parse()
213                .unwrap();
214            assert_eq!(parsed, status, "round-trip failed for {wire}");
215        }
216    }
217
218    #[test]
219    fn ping_status_rejects_unknown() {
220        assert!("BOGUS"
221            .parse::<GatewayPingStatus>()
222            .is_err());
223        assert!("up"
224            .parse::<GatewayPingStatus>()
225            .is_err());
226    }
227
228    #[cfg(feature = "serde")]
229    mod serde_tests {
230        use super::*;
231
232        #[test]
233        fn reg_state_serde_uses_wire_format() {
234            for &state in ALL_REG_STATES {
235                let json = serde_json::to_string(&state).unwrap();
236                let expected = format!("\"{}\"", state.as_str());
237                assert_eq!(json, expected, "serde must serialize as wire format");
238            }
239        }
240
241        #[test]
242        fn reg_state_serde_round_trip() {
243            for &state in ALL_REG_STATES {
244                let json = serde_json::to_string(&state).unwrap();
245                let parsed: GatewayRegState = serde_json::from_str(&json).unwrap();
246                assert_eq!(parsed, state);
247            }
248        }
249
250        #[test]
251        fn reg_state_serde_accepts_pascal_case_alias() {
252            let cases = [
253                ("\"Noreg\"", GatewayRegState::Noreg),
254                ("\"Reged\"", GatewayRegState::Reged),
255                ("\"FailWait\"", GatewayRegState::FailWait),
256                ("\"Unreged\"", GatewayRegState::Unreged),
257            ];
258            for (json, expected) in cases {
259                let parsed: GatewayRegState = serde_json::from_str(json).unwrap();
260                assert_eq!(parsed, expected, "PascalCase alias failed for {json}");
261            }
262        }
263
264        #[test]
265        fn ping_status_serde_uses_wire_format() {
266            for &status in ALL_PING_STATUSES {
267                let json = serde_json::to_string(&status).unwrap();
268                let expected = format!("\"{}\"", status.as_str());
269                assert_eq!(json, expected, "serde must serialize as wire format");
270            }
271        }
272
273        #[test]
274        fn ping_status_serde_round_trip() {
275            for &status in ALL_PING_STATUSES {
276                let json = serde_json::to_string(&status).unwrap();
277                let parsed: GatewayPingStatus = serde_json::from_str(&json).unwrap();
278                assert_eq!(parsed, status);
279            }
280        }
281
282        #[test]
283        fn ping_status_serde_accepts_pascal_case_alias() {
284            let cases = [
285                ("\"Down\"", GatewayPingStatus::Down),
286                ("\"Up\"", GatewayPingStatus::Up),
287                ("\"Invalid\"", GatewayPingStatus::Invalid),
288            ];
289            for (json, expected) in cases {
290                let parsed: GatewayPingStatus = serde_json::from_str(json).unwrap();
291                assert_eq!(parsed, expected, "PascalCase alias failed for {json}");
292            }
293        }
294    }
295}