use std::fmt;
use super::{
after_prefix, check_field, parse_leg, undeliverable, write_module_text, write_variables,
EndpointFieldFault,
};
use crate::commands::originate::OriginateError;
use crate::commands::variables::{DialStringCarrier, DialStringTarget, Variables};
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(try_from = "config::AudioEndpoint"))]
#[non_exhaustive]
pub struct AudioEndpoint {
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub destination: Option<String>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub variables: Option<Variables>,
}
impl AudioEndpoint {
pub fn new() -> Self {
Self::default()
}
pub fn with_destination(mut self, destination: impl Into<String>) -> Self {
self.destination = Some(destination.into());
self
}
pub fn fmt_with_prefix(&self, f: &mut fmt::Formatter<'_>, prefix: &str) -> fmt::Result {
self.write_with_prefix(f, prefix, DialStringCarrier::EslApi.into())
}
pub(super) fn write_with_prefix(
&self,
f: &mut fmt::Formatter<'_>,
prefix: &str,
target: DialStringTarget,
) -> fmt::Result {
write_variables(f, &self.variables, target)?;
write_module_text(f, &self.module_text(prefix), target)
}
pub(crate) fn module_text(&self, prefix: &str) -> String {
match &self.destination {
Some(dest) => format!("{prefix}/{dest}"),
None => prefix.to_owned(),
}
}
pub(crate) fn check_deliverable(&self) -> Result<(), OriginateError> {
match self
.destination
.as_deref()
{
Some("") => Err(undeliverable(
"audio",
"destination",
EndpointFieldFault::EmptyReadsAsDefault,
)),
Some(destination) => check_field("audio", "destination", destination, &[]),
None => Ok(()),
}
}
pub fn parse_with_prefix(s: &str, prefix: &str) -> Result<Self, OriginateError> {
let (variables, ep) = parse_leg(s, DialStringCarrier::EslApi.into(), |text| {
Self::parse_bare(text, prefix)
})?;
Ok(Self { variables, ..ep })
}
pub(crate) fn parse_bare(text: &str, prefix: &str) -> Result<Self, OriginateError> {
let rest = after_prefix(text, prefix, prefix)?;
let ep = Self {
destination: rest
.strip_prefix('/')
.filter(|d| !d.is_empty())
.map(str::to_string),
variables: None,
};
ep.check_deliverable()?;
Ok(ep)
}
}
impl_dial_string_with_variables!(AudioEndpoint);
impl_endpoint_parse!(config:
AudioEndpoint {
;
destination: Option<String>,
variables: Option<Variables>,
}
);
impl fmt::Display for AudioEndpoint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.fmt_with_prefix(f, "audio")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_with_prefix_portaudio() {
let ep = AudioEndpoint::parse_with_prefix("portaudio/auto_answer", "portaudio").unwrap();
assert_eq!(
ep.destination
.as_deref(),
Some("auto_answer")
);
assert!(ep
.variables
.is_none());
}
#[test]
fn parse_with_prefix_bare() {
let ep = AudioEndpoint::parse_with_prefix("alsa", "alsa").unwrap();
assert!(ep
.destination
.is_none());
}
#[test]
fn parse_with_prefix_trailing_slash() {
let ep = AudioEndpoint::parse_with_prefix("pulseaudio/", "pulseaudio").unwrap();
assert!(ep
.destination
.is_none());
}
#[test]
fn parse_with_prefix_wrong_module() {
let result = AudioEndpoint::parse_with_prefix("portaudio/x", "alsa");
assert!(result.is_err());
}
#[test]
fn parse_with_prefix_rejects_a_longer_module_name() {
assert!(AudioEndpoint::parse_with_prefix("alsafoo/bar", "alsa").is_err());
assert!("alsafoo/bar"
.parse::<super::super::Endpoint>()
.is_err());
}
}