Skip to main content

freeswitch_types/commands/endpoint/
audio.rs

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