use crate::{CallOption, ReferOption, media::recorder::RecorderOption, synthesis::SynthesisOption};
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
use std::{
collections::HashMap,
sync::{Arc, Mutex},
};
pub mod active_call;
pub mod sip;
pub use active_call::ActiveCall;
pub use active_call::ActiveCallRef;
pub use active_call::ActiveCallType;
pub type CommandSender = tokio::sync::broadcast::Sender<Command>;
pub type CommandReceiver = tokio::sync::broadcast::Receiver<Command>;
#[skip_serializing_none]
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(
tag = "command",
rename_all = "camelCase",
rename_all_fields = "camelCase"
)]
pub enum Command {
Invite {
option: CallOption,
},
Accept {
option: CallOption,
},
Reject {
reason: String,
code: Option<u32>,
},
Ringing {
recorder: Option<RecorderOption>,
early_media: Option<bool>,
ringtone: Option<String>,
},
Tts {
text: String,
speaker: Option<String>,
play_id: Option<String>,
auto_hangup: Option<bool>,
streaming: Option<bool>,
end_of_stream: Option<bool>,
option: Option<SynthesisOption>,
wait_input_timeout: Option<u32>,
base64: Option<bool>,
cache_key: Option<String>,
},
Play {
url: String,
play_id: Option<String>,
auto_hangup: Option<bool>,
wait_input_timeout: Option<u32>,
offset_ms: Option<u32>,
},
Interrupt {
graceful: Option<bool>,
fade_out_ms: Option<u32>,
},
Pause {},
Resume {},
Hangup {
reason: Option<String>,
initiator: Option<String>,
headers: Option<HashMap<String, String>>,
refer: Option<bool>,
},
Refer {
caller: String,
callee: String,
options: Option<ReferOption>,
},
Message {
body: String,
content_type: Option<String>,
headers: Option<HashMap<String, String>>,
refer: Option<bool>,
},
Bridge {
target_session_id: String,
},
Unbridge {
target_session_id: String,
},
Mute {
track_id: Option<String>,
},
Unmute {
track_id: Option<String>,
},
History {
speaker: String,
text: String,
},
Custom {
sender: Option<String>,
data: serde_json::Value,
},
AddIceCandidate {
candidate: String,
sdp_mid: Option<String>,
sdp_mline_index: Option<u32>,
},
}
#[derive(Debug)]
pub struct RoutingState {
round_robin_counters: Arc<Mutex<HashMap<String, usize>>>,
}
impl Default for RoutingState {
fn default() -> Self {
Self::new()
}
}
impl RoutingState {
pub fn new() -> Self {
Self {
round_robin_counters: Arc::new(Mutex::new(HashMap::new())),
}
}
pub fn next_round_robin_index(&self, destination_key: &str, trunk_count: usize) -> usize {
if trunk_count == 0 {
return 0;
}
let mut counters = self.round_robin_counters.lock().unwrap();
let counter = counters
.entry(destination_key.to_string())
.or_insert_with(|| 0);
let r = *counter % trunk_count;
*counter += 1;
return r;
}
}
#[cfg(test)]
mod tests {
use super::Command;
#[test]
fn message_command_deserializes_body() {
let command: Command = serde_json::from_value(serde_json::json!({
"command": "message",
"body": "customer_id=12345",
"contentType": "text/plain"
}))
.unwrap();
assert!(matches!(
command,
Command::Message {
body,
content_type: Some(content_type),
..
} if body == "customer_id=12345" && content_type == "text/plain"
));
}
#[test]
fn message_command_deserializes_legacy_text() {
let command: Command = serde_json::from_value(serde_json::json!({
"command": "message",
"body": "customer_id=12345"
}))
.unwrap();
assert!(matches!(
command,
Command::Message { body, .. } if body == "customer_id=12345"
));
}
#[test]
fn message_command_serializes_body() {
let command = Command::Message {
body: "customer_id=12345".to_string(),
content_type: None,
headers: None,
refer: None,
};
let value = serde_json::to_value(command).unwrap();
assert_eq!(value["body"], "customer_id=12345");
assert!(value.get("text").is_none());
}
}