use serde::{Deserialize, Serialize};
pub type PcmAudioFrame = Vec<i16>;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct LegId(pub String);
impl LegId {
pub fn new(id: impl Into<String>) -> Self {
Self(id.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl std::fmt::Display for LegId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<String> for LegId {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for LegId {
fn from(s: &str) -> Self {
Self(s.to_string())
}
}
impl From<LegId> for String {
fn from(leg_id: LegId) -> Self {
leg_id.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[derive(Default)]
pub enum LegState {
#[default]
Initializing,
Ringing,
EarlyMedia,
Connected,
Hold,
Ending,
Ended,
}
impl std::fmt::Display for LegState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
LegState::Initializing => write!(f, "initializing"),
LegState::Ringing => write!(f, "ringing"),
LegState::EarlyMedia => write!(f, "early_media"),
LegState::Connected => write!(f, "connected"),
LegState::Hold => write!(f, "hold"),
LegState::Ending => write!(f, "ending"),
LegState::Ended => write!(f, "ended"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Leg {
pub id: LegId,
pub state: LegState,
pub endpoint: Option<String>,
pub display_name: Option<String>,
pub dialog_id: Option<String>,
pub is_initiator: bool,
pub answer_sdp: Option<String>,
}
impl Leg {
pub fn new(id: LegId) -> Self {
Self {
id,
state: LegState::default(),
endpoint: None,
display_name: None,
dialog_id: None,
is_initiator: false,
answer_sdp: None,
}
}
pub fn with_endpoint(mut self, endpoint: impl Into<String>) -> Self {
self.endpoint = Some(endpoint.into());
self
}
pub fn with_display_name(mut self, name: impl Into<String>) -> Self {
self.display_name = Some(name.into());
self
}
pub fn with_dialog_id(mut self, dialog_id: impl Into<String>) -> Self {
self.dialog_id = Some(dialog_id.into());
self
}
pub fn as_initiator(mut self) -> Self {
self.is_initiator = true;
self
}
pub fn is_active(&self) -> bool {
matches!(self.state, LegState::Connected | LegState::EarlyMedia)
}
pub fn can_answer(&self) -> bool {
matches!(self.state, LegState::Ringing | LegState::EarlyMedia)
}
pub fn can_hold(&self) -> bool {
matches!(self.state, LegState::Connected)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn leg_id_display() {
let id = LegId::new("leg-123");
assert_eq!(id.to_string(), "leg-123");
}
#[test]
fn leg_state_transitions() {
let mut leg = Leg::new(LegId::new("test"));
assert_eq!(leg.state, LegState::Initializing);
assert!(!leg.is_active());
leg.state = LegState::Ringing;
assert!(leg.can_answer());
assert!(!leg.can_hold());
leg.state = LegState::Connected;
assert!(leg.is_active());
assert!(leg.can_hold());
assert!(!leg.can_answer());
}
}