use std::fmt;
use std::str::FromStr;
use super::{extract_variables, write_variables};
use crate::commands::originate::{OriginateError, Variables};
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub struct LoopbackEndpoint {
pub extension: String,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub context: Option<String>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub variables: Option<Variables>,
}
impl LoopbackEndpoint {
pub fn new(extension: impl Into<String>) -> Self {
Self {
extension: extension.into(),
context: None,
variables: None,
}
}
pub fn with_context(mut self, context: impl Into<String>) -> Self {
self.context = Some(context.into());
self
}
pub fn with_variables(mut self, variables: Variables) -> Self {
self.variables = Some(variables);
self
}
}
impl fmt::Display for LoopbackEndpoint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write_variables(f, &self.variables)?;
match &self.context {
Some(ctx) => write!(f, "loopback/{}/{}", self.extension, ctx),
None => write!(f, "loopback/{}", self.extension),
}
}
}
impl FromStr for LoopbackEndpoint {
type Err = OriginateError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (variables, uri) = extract_variables(s)?;
let rest = uri
.strip_prefix("loopback/")
.ok_or_else(|| OriginateError::ParseError("not a loopback endpoint".into()))?;
let (extension, context) = match rest.split_once('/') {
Some((ext, ctx)) => (ext, Some(ctx.to_string())),
None => (rest, None),
};
Ok(Self {
extension: extension.into(),
context,
variables,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::commands::originate::VariablesType;
#[test]
fn loopback_display_no_context() {
let ep = LoopbackEndpoint::new("9199");
assert_eq!(ep.to_string(), "loopback/9199");
}
#[test]
fn loopback_display_with_context() {
let ep = LoopbackEndpoint::new("9199").with_context("default");
assert_eq!(ep.to_string(), "loopback/9199/default");
}
#[test]
fn loopback_display_with_variables() {
let mut vars = Variables::new(VariablesType::Default);
vars.insert("loopback_initial_codec", "L16@48000h");
let ep = LoopbackEndpoint::new("100")
.with_context("test")
.with_variables(vars);
assert_eq!(
ep.to_string(),
"{loopback_initial_codec=L16@48000h}loopback/100/test"
);
}
#[test]
fn loopback_from_str_with_context() {
let ep: LoopbackEndpoint = "loopback/9199/test"
.parse()
.unwrap();
assert_eq!(ep.extension, "9199");
assert_eq!(
ep.context
.as_deref(),
Some("test")
);
}
#[test]
fn loopback_from_str_no_context() {
let ep: LoopbackEndpoint = "loopback/9199"
.parse()
.unwrap();
assert_eq!(ep.extension, "9199");
assert!(ep
.context
.is_none());
}
#[test]
fn loopback_round_trip_with_context() {
let ep = LoopbackEndpoint::new("100").with_context("myctx");
let s = ep.to_string();
let parsed: LoopbackEndpoint = s
.parse()
.unwrap();
assert_eq!(parsed, ep);
}
#[test]
fn loopback_round_trip_no_context() {
let ep = LoopbackEndpoint::new("9199");
let s = ep.to_string();
let parsed: LoopbackEndpoint = s
.parse()
.unwrap();
assert_eq!(parsed, ep);
}
#[test]
fn loopback_display_parse_display_stable() {
let inputs = [
"loopback/9199",
"loopback/100/default",
"loopback/ext123/custom_ctx",
];
for input in inputs {
let parsed: LoopbackEndpoint = input
.parse()
.unwrap();
let displayed = parsed.to_string();
assert_eq!(displayed, input, "round-trip failed for: {}", input);
let reparsed: LoopbackEndpoint = displayed
.parse()
.unwrap();
assert_eq!(reparsed, parsed);
}
}
#[test]
fn serde_loopback_endpoint() {
let ep = LoopbackEndpoint::new("9199").with_context("default");
let json = serde_json::to_string(&ep).unwrap();
let parsed: LoopbackEndpoint = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, ep);
}
}