use std::fmt;
use std::str::FromStr;
use super::common::{ExecutionInfo, State};
use super::media::Media;
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub struct Endpoint {
#[cfg_attr(feature = "serde", serde(rename = "@entity"))]
pub entity: String,
#[cfg_attr(
feature = "serde",
serde(rename = "@state", default, skip_serializing_if = "Option::is_none")
)]
pub state: Option<State>,
#[cfg_attr(
feature = "serde",
serde(
rename = "display-text",
default,
skip_serializing_if = "Option::is_none"
)
)]
pub display_text: Option<String>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub referred: Option<ExecutionInfo>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub status: Option<EndpointStatus>,
#[cfg_attr(
feature = "serde",
serde(
rename = "joining-method",
default,
skip_serializing_if = "Option::is_none"
)
)]
pub joining_method: Option<JoiningMethod>,
#[cfg_attr(
feature = "serde",
serde(
rename = "joining-info",
default,
skip_serializing_if = "Option::is_none"
)
)]
pub joining_info: Option<ExecutionInfo>,
#[cfg_attr(
feature = "serde",
serde(
rename = "disconnection-method",
default,
skip_serializing_if = "Option::is_none"
)
)]
pub disconnection_method: Option<DisconnectionMethod>,
#[cfg_attr(
feature = "serde",
serde(
rename = "disconnection-info",
default,
skip_serializing_if = "Option::is_none"
)
)]
pub disconnection_info: Option<ExecutionInfo>,
#[cfg_attr(
feature = "serde",
serde(rename = "media", default, skip_serializing_if = "Vec::is_empty")
)]
pub media: Vec<Media>,
#[cfg_attr(
feature = "serde",
serde(rename = "call-info", default, skip_serializing_if = "Option::is_none")
)]
pub call_info: Option<CallInfo>,
}
impl Endpoint {
pub fn new(entity: impl Into<String>) -> Self {
Self {
entity: entity.into(),
state: None,
display_text: None,
referred: None,
status: None,
joining_method: None,
joining_info: None,
disconnection_method: None,
disconnection_info: None,
media: Vec::new(),
call_info: None,
}
}
pub fn with_status(mut self, status: EndpointStatus) -> Self {
self.status = Some(status);
self
}
pub fn with_joining_method(mut self, method: JoiningMethod) -> Self {
self.joining_method = Some(method);
self
}
pub fn with_joining_info(mut self, info: ExecutionInfo) -> Self {
self.joining_info = Some(info);
self
}
pub fn with_media(mut self, media: Media) -> Self {
self.media
.push(media);
self
}
pub fn with_call_info(mut self, call_info: CallInfo) -> Self {
self.call_info = Some(call_info);
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum EndpointStatus {
#[cfg_attr(feature = "serde", serde(rename = "pending"))]
Pending,
#[cfg_attr(feature = "serde", serde(rename = "dialing-out"))]
DialingOut,
#[cfg_attr(feature = "serde", serde(rename = "dialing-in"))]
DialingIn,
#[cfg_attr(feature = "serde", serde(rename = "alerting"))]
Alerting,
#[cfg_attr(feature = "serde", serde(rename = "on-hold"))]
OnHold,
#[cfg_attr(feature = "serde", serde(rename = "connected"))]
Connected,
#[cfg_attr(feature = "serde", serde(rename = "muted-via-focus"))]
MutedViaFocus,
#[cfg_attr(feature = "serde", serde(rename = "disconnecting"))]
Disconnecting,
#[cfg_attr(feature = "serde", serde(rename = "disconnected"))]
Disconnected,
}
impl fmt::Display for EndpointStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Pending => "pending",
Self::DialingOut => "dialing-out",
Self::DialingIn => "dialing-in",
Self::Alerting => "alerting",
Self::OnHold => "on-hold",
Self::Connected => "connected",
Self::MutedViaFocus => "muted-via-focus",
Self::Disconnecting => "disconnecting",
Self::Disconnected => "disconnected",
})
}
}
#[derive(Debug, Clone)]
pub struct ParseEndpointStatusError(pub String);
impl fmt::Display for ParseEndpointStatusError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "invalid endpoint status: {:?}", self.0)
}
}
impl std::error::Error for ParseEndpointStatusError {}
impl FromStr for EndpointStatus {
type Err = ParseEndpointStatusError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"pending" => Ok(Self::Pending),
"dialing-out" => Ok(Self::DialingOut),
"dialing-in" => Ok(Self::DialingIn),
"alerting" => Ok(Self::Alerting),
"on-hold" => Ok(Self::OnHold),
"connected" => Ok(Self::Connected),
"muted-via-focus" => Ok(Self::MutedViaFocus),
"disconnecting" => Ok(Self::Disconnecting),
"disconnected" => Ok(Self::Disconnected),
other => Err(ParseEndpointStatusError(other.to_owned())),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum JoiningMethod {
#[cfg_attr(feature = "serde", serde(rename = "dialed-in"))]
DialedIn,
#[cfg_attr(feature = "serde", serde(rename = "dialed-out"))]
DialedOut,
#[cfg_attr(feature = "serde", serde(rename = "focus-owner"))]
FocusOwner,
}
impl fmt::Display for JoiningMethod {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::DialedIn => "dialed-in",
Self::DialedOut => "dialed-out",
Self::FocusOwner => "focus-owner",
})
}
}
#[derive(Debug, Clone)]
pub struct ParseJoiningMethodError(pub String);
impl fmt::Display for ParseJoiningMethodError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "invalid joining method: {:?}", self.0)
}
}
impl std::error::Error for ParseJoiningMethodError {}
impl FromStr for JoiningMethod {
type Err = ParseJoiningMethodError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"dialed-in" => Ok(Self::DialedIn),
"dialed-out" => Ok(Self::DialedOut),
"focus-owner" => Ok(Self::FocusOwner),
other => Err(ParseJoiningMethodError(other.to_owned())),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum DisconnectionMethod {
#[cfg_attr(feature = "serde", serde(rename = "departed"))]
Departed,
#[cfg_attr(feature = "serde", serde(rename = "booted"))]
Booted,
#[cfg_attr(feature = "serde", serde(rename = "failed"))]
Failed,
#[cfg_attr(feature = "serde", serde(rename = "busy"))]
Busy,
}
impl fmt::Display for DisconnectionMethod {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Departed => "departed",
Self::Booted => "booted",
Self::Failed => "failed",
Self::Busy => "busy",
})
}
}
#[derive(Debug, Clone)]
pub struct ParseDisconnectionMethodError(pub String);
impl fmt::Display for ParseDisconnectionMethodError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "invalid disconnection method: {:?}", self.0)
}
}
impl std::error::Error for ParseDisconnectionMethodError {}
impl FromStr for DisconnectionMethod {
type Err = ParseDisconnectionMethodError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"departed" => Ok(Self::Departed),
"booted" => Ok(Self::Booted),
"failed" => Ok(Self::Failed),
"busy" => Ok(Self::Busy),
other => Err(ParseDisconnectionMethodError(other.to_owned())),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub struct CallInfo {
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub sip: Option<SipDialogId>,
}
impl CallInfo {
pub fn with_sip(sip: SipDialogId) -> Self {
Self { sip: Some(sip) }
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub struct SipDialogId {
#[cfg_attr(
feature = "serde",
serde(
rename = "display-text",
default,
skip_serializing_if = "Option::is_none"
)
)]
pub display_text: Option<String>,
#[cfg_attr(
feature = "serde",
serde(rename = "call-id", default, skip_serializing_if = "Option::is_none")
)]
pub call_id: Option<String>,
#[cfg_attr(
feature = "serde",
serde(rename = "from-tag", default, skip_serializing_if = "Option::is_none")
)]
pub from_tag: Option<String>,
#[cfg_attr(
feature = "serde",
serde(rename = "to-tag", default, skip_serializing_if = "Option::is_none")
)]
pub to_tag: Option<String>,
}
impl SipDialogId {
pub fn new(
call_id: impl Into<String>,
from_tag: impl Into<String>,
to_tag: impl Into<String>,
) -> Self {
Self {
display_text: None,
call_id: Some(call_id.into()),
from_tag: Some(from_tag.into()),
to_tag: Some(to_tag.into()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn endpoint_status_round_trip() {
for (s, expected) in [
("pending", EndpointStatus::Pending),
("dialing-out", EndpointStatus::DialingOut),
("dialing-in", EndpointStatus::DialingIn),
("alerting", EndpointStatus::Alerting),
("on-hold", EndpointStatus::OnHold),
("connected", EndpointStatus::Connected),
("muted-via-focus", EndpointStatus::MutedViaFocus),
("disconnecting", EndpointStatus::Disconnecting),
("disconnected", EndpointStatus::Disconnected),
] {
let parsed: EndpointStatus = s
.parse()
.unwrap();
assert_eq!(parsed, expected);
assert_eq!(parsed.to_string(), s);
}
}
#[test]
fn joining_method_round_trip() {
for (s, expected) in [
("dialed-in", JoiningMethod::DialedIn),
("dialed-out", JoiningMethod::DialedOut),
("focus-owner", JoiningMethod::FocusOwner),
] {
let parsed: JoiningMethod = s
.parse()
.unwrap();
assert_eq!(parsed, expected);
assert_eq!(parsed.to_string(), s);
}
}
#[test]
fn disconnection_method_round_trip() {
for (s, expected) in [
("departed", DisconnectionMethod::Departed),
("booted", DisconnectionMethod::Booted),
("failed", DisconnectionMethod::Failed),
("busy", DisconnectionMethod::Busy),
] {
let parsed: DisconnectionMethod = s
.parse()
.unwrap();
assert_eq!(parsed, expected);
assert_eq!(parsed.to_string(), s);
}
}
#[test]
fn sip_dialog_id_new() {
let id = SipDialogId::new("call-123", "from-abc", "to-xyz");
assert_eq!(
id.call_id
.as_deref(),
Some("call-123")
);
assert_eq!(
id.from_tag
.as_deref(),
Some("from-abc")
);
assert_eq!(
id.to_tag
.as_deref(),
Some("to-xyz")
);
assert!(id
.display_text
.is_none());
}
#[test]
fn endpoint_builder() {
let ep = Endpoint::new("sip:alice@example.com")
.with_status(EndpointStatus::Connected)
.with_joining_method(JoiningMethod::DialedIn)
.with_media(super::super::media::Media::new("1").with_type("audio"));
assert_eq!(ep.entity, "sip:alice@example.com");
assert_eq!(ep.status, Some(EndpointStatus::Connected));
assert_eq!(
ep.media
.len(),
1
);
}
}