1use crate::{CallOption, ReferOption, media::recorder::RecorderOption, synthesis::SynthesisOption};
2use serde::{Deserialize, Serialize};
3use serde_with::skip_serializing_none;
4use std::{
5 collections::HashMap,
6 sync::{Arc, Mutex},
7};
8
9pub mod active_call;
10pub mod sip;
11pub mod state;
12pub mod tracks;
13pub use active_call::ActiveCall;
14pub use active_call::ActiveCallRef;
15pub use active_call::ActiveCallType;
16
17pub type CommandSender = tokio::sync::broadcast::Sender<Command>;
18pub type CommandReceiver = tokio::sync::broadcast::Receiver<Command>;
19
20#[skip_serializing_none]
22#[derive(Debug, Deserialize, Serialize, Clone)]
23#[serde(
24 tag = "command",
25 rename_all = "camelCase",
26 rename_all_fields = "camelCase"
27)]
28pub enum Command {
29 Invite {
30 option: CallOption,
31 },
32 Accept {
33 option: CallOption,
34 },
35 Reject {
36 reason: String,
37 code: Option<u32>,
38 },
39 Ringing {
40 recorder: Option<RecorderOption>,
41 early_media: Option<bool>,
42 ringtone: Option<String>,
43 },
44 Tts {
45 text: String,
46 speaker: Option<String>,
47 play_id: Option<String>,
49 auto_hangup: Option<bool>,
51 streaming: Option<bool>,
55 end_of_stream: Option<bool>,
57 option: Option<SynthesisOption>,
58 wait_input_timeout: Option<u32>,
59 base64: Option<bool>,
61 cache_key: Option<String>,
63 },
64 Play {
65 url: String,
66 play_id: Option<String>,
67 auto_hangup: Option<bool>,
68 wait_input_timeout: Option<u32>,
69 offset_ms: Option<u32>,
70 },
71 Interrupt {
72 graceful: Option<bool>,
73 fade_out_ms: Option<u32>,
74 },
75 Pause {},
76 Resume {},
77 Hangup {
78 reason: Option<String>,
79 initiator: Option<String>,
80 headers: Option<HashMap<String, String>>,
81 refer: Option<bool>,
82 },
83 Refer {
84 caller: String,
85 callee: String,
87 options: Option<ReferOption>,
88 },
89 Message {
90 body: String,
92 content_type: Option<String>,
94 headers: Option<HashMap<String, String>>,
96 refer: Option<bool>,
98 },
99 Bridge {
104 target_session_id: String,
106 },
107 Unbridge {
109 target_session_id: String,
111 },
112 Mute {
113 track_id: Option<String>,
114 },
115 Unmute {
116 track_id: Option<String>,
117 },
118 History {
119 speaker: String,
120 text: String,
121 },
122 Custom {
123 sender: Option<String>,
124 data: serde_json::Value,
125 },
126 AddIceCandidate {
128 candidate: String,
129 sdp_mid: Option<String>,
130 sdp_mline_index: Option<u32>,
131 },
132}
133
134#[derive(Debug)]
136pub struct RoutingState {
137 round_robin_counters: Arc<Mutex<HashMap<String, usize>>>,
139}
140
141impl Default for RoutingState {
142 fn default() -> Self {
143 Self::new()
144 }
145}
146
147impl RoutingState {
148 pub fn new() -> Self {
149 Self {
150 round_robin_counters: Arc::new(Mutex::new(HashMap::new())),
151 }
152 }
153
154 pub fn next_round_robin_index(&self, destination_key: &str, trunk_count: usize) -> usize {
156 if trunk_count == 0 {
157 return 0;
158 }
159
160 let mut counters = self.round_robin_counters.lock().unwrap();
161 let counter = counters
162 .entry(destination_key.to_string())
163 .or_insert_with(|| 0);
164 let r = *counter % trunk_count;
165 *counter += 1;
166 return r;
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use super::Command;
173
174 #[test]
175 fn message_command_deserializes_body() {
176 let command: Command = serde_json::from_value(serde_json::json!({
177 "command": "message",
178 "body": "customer_id=12345",
179 "contentType": "text/plain"
180 }))
181 .unwrap();
182
183 assert!(matches!(
184 command,
185 Command::Message {
186 body,
187 content_type: Some(content_type),
188 ..
189 } if body == "customer_id=12345" && content_type == "text/plain"
190 ));
191 }
192
193 #[test]
194 fn message_command_deserializes_legacy_text() {
195 let command: Command = serde_json::from_value(serde_json::json!({
196 "command": "message",
197 "body": "customer_id=12345"
198 }))
199 .unwrap();
200
201 assert!(matches!(
202 command,
203 Command::Message { body, .. } if body == "customer_id=12345"
204 ));
205 }
206
207 #[test]
208 fn message_command_serializes_body() {
209 let command = Command::Message {
210 body: "customer_id=12345".to_string(),
211 content_type: None,
212 headers: None,
213 refer: None,
214 };
215 let value = serde_json::to_value(command).unwrap();
216
217 assert_eq!(value["body"], "customer_id=12345");
218 assert!(value.get("text").is_none());
219 }
220}