Skip to main content

freeswitch_types/commands/endpoint/
user.rs

1use super::{after_prefix, check_field, undeliverable, EndpointFieldFault};
2use crate::commands::originate::OriginateError;
3use crate::commands::variables::Variables;
4
5/// Directory-based endpoint: `user/{name}[@{domain}]`.
6#[derive(Debug, Clone, PartialEq, Eq)]
7#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
8#[cfg_attr(feature = "serde", serde(try_from = "config::UserEndpoint"))]
9#[non_exhaustive]
10pub struct UserEndpoint {
11    /// User name from the directory.
12    pub name: String,
13    /// Domain name (optional, uses default domain if absent).
14    #[cfg_attr(
15        feature = "serde",
16        serde(default, skip_serializing_if = "Option::is_none")
17    )]
18    pub domain: Option<String>,
19    /// Per-channel variables prepended as `{key=value}`.
20    #[cfg_attr(
21        feature = "serde",
22        serde(default, skip_serializing_if = "Option::is_none")
23    )]
24    pub variables: Option<Variables>,
25}
26
27impl UserEndpoint {
28    /// Create a new user endpoint.
29    pub fn new(name: impl Into<String>) -> Self {
30        Self {
31            name: name.into(),
32            domain: None,
33            variables: None,
34        }
35    }
36
37    /// Set the domain.
38    pub fn with_domain(mut self, domain: impl Into<String>) -> Self {
39        self.domain = Some(domain.into());
40        self
41    }
42}
43
44impl_dial_string_with_variables!(UserEndpoint, write_module_text, |this| match &this.domain {
45    Some(d) => format!("user/{}@{d}", this.name),
46    None => format!("user/{}", this.name),
47});
48
49impl UserEndpoint {
50    /// Refuse an `@` in the name, where `user_outgoing_channel` starts the domain, and an empty
51    /// name with no domain, on which it stops before its lookup.
52    pub(crate) fn check_deliverable(&self) -> Result<(), OriginateError> {
53        check_field("user", "name", &self.name, &["@"])?;
54        if self
55            .name
56            .is_empty()
57            && self
58                .domain
59                .is_none()
60        {
61            return Err(undeliverable(
62                "user",
63                "name",
64                EndpointFieldFault::EmptyDialsNothing,
65            ));
66        }
67        match &self.domain {
68            Some(domain) => check_field("user", "domain", domain, &[]),
69            None => Ok(()),
70        }
71    }
72
73    pub(crate) fn parse_bare(text: &str) -> Result<Self, OriginateError> {
74        let rest = after_prefix(text, "user/", "user")?;
75        let ep = match rest.split_once('@') {
76            Some((name, domain)) => Self::new(name).with_domain(domain),
77            None => Self::new(rest),
78        };
79        ep.check_deliverable()?;
80        Ok(ep)
81    }
82}
83
84impl_endpoint_parse!(from_str: UserEndpoint);
85
86impl_endpoint_parse!(config:
87    UserEndpoint {
88        name: String,
89        ;
90        domain: Option<String>,
91        variables: Option<Variables>,
92    }
93);
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn user_endpoint_display() {
101        let ep = UserEndpoint {
102            name: "1000".into(),
103            domain: Some("example.com".into()),
104            variables: None,
105        };
106        assert_eq!(ep.to_string(), "user/1000@example.com");
107    }
108
109    #[test]
110    fn user_endpoint_display_no_domain() {
111        let ep = UserEndpoint {
112            name: "1000".into(),
113            domain: None,
114            variables: None,
115        };
116        assert_eq!(ep.to_string(), "user/1000");
117    }
118
119    #[test]
120    fn user_endpoint_from_str() {
121        let ep: UserEndpoint = "user/1000@example.com"
122            .parse()
123            .unwrap();
124        assert_eq!(ep.name, "1000");
125        assert_eq!(
126            ep.domain
127                .as_deref(),
128            Some("example.com")
129        );
130    }
131
132    #[test]
133    fn user_endpoint_from_str_no_domain() {
134        let ep: UserEndpoint = "user/1000"
135            .parse()
136            .unwrap();
137        assert_eq!(ep.name, "1000");
138        assert!(ep
139            .domain
140            .is_none());
141    }
142
143    #[test]
144    fn user_endpoint_round_trip() {
145        let ep = UserEndpoint {
146            name: "bob".into(),
147            domain: Some("example.com".into()),
148            variables: None,
149        };
150        let s = ep.to_string();
151        let parsed: UserEndpoint = s
152            .parse()
153            .unwrap();
154        assert_eq!(parsed, ep);
155    }
156
157    #[test]
158    fn serde_user_endpoint() {
159        let ep = UserEndpoint {
160            name: "1000".into(),
161            domain: Some("example.com".into()),
162            variables: None,
163        };
164        let json = serde_json::to_string(&ep).unwrap();
165        let parsed: UserEndpoint = serde_json::from_str(&json).unwrap();
166        assert_eq!(parsed, ep);
167    }
168
169    #[test]
170    fn serde_user_endpoint_no_domain() {
171        let ep = UserEndpoint {
172            name: "1000".into(),
173            domain: None,
174            variables: None,
175        };
176        let json = serde_json::to_string(&ep).unwrap();
177        let parsed: UserEndpoint = serde_json::from_str(&json).unwrap();
178        assert_eq!(parsed, ep);
179    }
180}