Skip to main content

freeswitch_types/commands/endpoint/
audio.rs

1use std::fmt;
2
3use super::{extract_variables, write_variables};
4use crate::commands::originate::OriginateError;
5use crate::commands::variables::Variables;
6
7/// Audio device endpoint for portaudio, pulseaudio, or ALSA modules.
8///
9/// Wire format: `{module}[/{destination}]` where destination is typically
10/// empty or `auto_answer` (recognized by portaudio and pulseaudio).
11#[derive(Debug, Clone, Default, PartialEq, Eq)]
12#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
13#[non_exhaustive]
14pub struct AudioEndpoint {
15    /// Destination string (e.g. `auto_answer`). `None` for bare module name.
16    #[cfg_attr(
17        feature = "serde",
18        serde(default, skip_serializing_if = "Option::is_none")
19    )]
20    pub destination: Option<String>,
21    /// Per-channel variables prepended as `{key=value}`.
22    #[cfg_attr(
23        feature = "serde",
24        serde(default, skip_serializing_if = "Option::is_none")
25    )]
26    pub variables: Option<Variables>,
27}
28
29impl AudioEndpoint {
30    /// Create a new audio endpoint with no destination.
31    pub fn new() -> Self {
32        Self::default()
33    }
34
35    /// Set the destination string.
36    pub fn with_destination(mut self, destination: impl Into<String>) -> Self {
37        self.destination = Some(destination.into());
38        self
39    }
40
41    /// Set per-channel variables.
42    pub fn with_variables(mut self, variables: Variables) -> Self {
43        self.variables = Some(variables);
44        self
45    }
46
47    /// Format with the given module prefix (`portaudio`, `pulseaudio`, `alsa`).
48    pub fn fmt_with_prefix(&self, f: &mut fmt::Formatter<'_>, prefix: &str) -> fmt::Result {
49        write_variables(f, &self.variables)?;
50        match &self.destination {
51            Some(dest) => write!(f, "{}/{}", prefix, dest),
52            None => f.write_str(prefix),
53        }
54    }
55
56    /// Parse from a dial string with the given module prefix.
57    pub fn parse_with_prefix(s: &str, prefix: &str) -> Result<Self, OriginateError> {
58        let (variables, uri) = extract_variables(s)?;
59        let rest = uri
60            .strip_prefix(prefix)
61            .ok_or_else(|| OriginateError::ParseError(format!("not a {} endpoint", prefix)))?;
62        let destination = rest
63            .strip_prefix('/')
64            .and_then(|d| {
65                if d.is_empty() {
66                    None
67                } else {
68                    Some(d.to_string())
69                }
70            });
71        Ok(Self {
72            destination,
73            variables,
74        })
75    }
76}
77
78/// **Warning:** This `Display` impl exists only to satisfy the `DialString: Display`
79/// trait bound. The `"audio"` prefix is not a valid FreeSWITCH endpoint.
80/// Always use `AudioEndpoint` through [`Endpoint::PortAudio`](super::Endpoint::PortAudio),
81/// [`Endpoint::PulseAudio`](super::Endpoint::PulseAudio), or
82/// [`Endpoint::Alsa`](super::Endpoint::Alsa) which call
83/// [`fmt_with_prefix`](AudioEndpoint::fmt_with_prefix) with the correct module name.
84impl fmt::Display for AudioEndpoint {
85    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86        self.fmt_with_prefix(f, "audio")
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn parse_with_prefix_portaudio() {
96        let ep = AudioEndpoint::parse_with_prefix("portaudio/auto_answer", "portaudio").unwrap();
97        assert_eq!(
98            ep.destination
99                .as_deref(),
100            Some("auto_answer")
101        );
102        assert!(ep
103            .variables
104            .is_none());
105    }
106
107    #[test]
108    fn parse_with_prefix_bare() {
109        let ep = AudioEndpoint::parse_with_prefix("alsa", "alsa").unwrap();
110        assert!(ep
111            .destination
112            .is_none());
113    }
114
115    #[test]
116    fn parse_with_prefix_trailing_slash() {
117        let ep = AudioEndpoint::parse_with_prefix("pulseaudio/", "pulseaudio").unwrap();
118        assert!(ep
119            .destination
120            .is_none());
121    }
122
123    #[test]
124    fn parse_with_prefix_wrong_module() {
125        let result = AudioEndpoint::parse_with_prefix("portaudio/x", "alsa");
126        assert!(result.is_err());
127    }
128}