cal_core/
core.rs

1use crate::device::device::{DeviceStruct, DeviceTag};
2use crate::{clean_str_parse_country, AccountLite, CodeCache, Region, VoiceServer};
3use bson::oid::ObjectId;
4use cal_jambonz::rest::{InitialRequest, Request};
5use cal_jambonz::shared::shared::SIPStatus;
6use cal_jambonz::TenantType;
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::fmt::{Debug, Formatter};
10use std::sync::Arc;
11
12pub const HANG_UP_CONNECT: &'static str = "HANGUP";
13pub const DEFAULT_TTS_VOICE: &'static str = "en-GB-Standard-B";
14pub const DEFAULT_TTS_LANGUAGE: &'static str = "en-GB";
15pub const DEFAULT_TTS_VENDOR: &'static str = "google";
16pub const DEFAULT_ASR_LANGUAGE: &'static str = "en-GB";
17pub const DEFAULT_ASR_VENDOR: &'static str = "google";
18pub const DEFAULT_RECORDING_RETENTION: &'static str = "P30D";
19pub const DEFAULT_CLIENT_PROFILE: &'static str = "generic_e164_plus";
20pub const DEFAULT_COUNTRY_CODE: &'static str = "gb";
21pub const DEFAULT_DIGITS_FINISH_KEY: &'static str = "#";
22pub const DEFAULT_ENDPOINT_PRESENT: &'static str = "pass-through";
23pub const DEFAULT_REMOTE_DESTINATION: &'static str = "[request.tp]";
24pub const DEFAULT_VM_FINISH_KEY: &'static str = "#";
25pub const DEFAULT_ENDPOINT_FORMAT: &'static str = "e164p";
26pub const DEFAULT_TIMEZONE: &'static str = "Europe/London";
27pub const DEFAULT_TRUNK_DESCRIPTION: &'static str = "SIP Trunk";
28pub const DEFAULT_TRUNK_PORT: u16 = 5060;
29pub const DEFAULT_RING_TIME: u8 = 180;
30pub const DEFAULT_DIGIT_TIMEOUT: u8 = 10;
31pub const DEFAULT_MAX_DIGITS: u8 = 1;
32pub const DEFAULT_VM_TIMEOUT: u8 = 30;
33pub const DEFAULT_VM_MAX_LENGTH: u8 = 30;
34pub const DEFAULT_CALL_TIME: u16 = 14400;
35
36#[derive(Clone)]
37pub struct FlowState {
38    // Owned small fields
39    pub base_url: String,
40    pub initial_request: InitialRequest,
41    pub current_request: Option<Request>,
42
43    // Arc for shared complex structures
44    pub account: Arc<AccountLite>,
45    pub region: Arc<Region>,
46
47    // Small value type
48    pub voice_server: VoiceServer,
49
50    // Arc for optional large structure
51    pub device: Option<Arc<DeviceStruct>>,
52
53    // Modified during traversal
54    pub connect: Option<ConnectState>,
55
56    // Small value type
57    pub tenant_type: TenantType,
58    pub code: CodeCache,
59
60    // Modified during traversal
61    pub data: HashMap<String, DeviceTag>,
62
63    // Limited history collections
64    pub connect_states: Vec<ConnectState>,
65    pub devices: Vec<String>, // Store device IDs instead of full structures
66}
67
68impl FlowState {
69    // Constants for limiting history size
70    const MAX_CONNECT_HISTORY: usize = 20;
71    const MAX_DEVICE_HISTORY: usize = 20;
72
73    // Get methods that avoid cloning where possible
74    pub fn get_country_code(&self) -> &str {
75        &self.account.environment.country_code
76    }
77
78    pub fn get_from(&self) -> String {
79        clean_str_parse_country(
80            self.get_country_code(),
81            self.initial_request.from.as_str(),
82        )
83    }
84
85    pub fn get_to(&self) -> String {
86        clean_str_parse_country(
87            self.get_country_code(),
88            self.initial_request.to.as_str(),
89        )
90    }
91
92    pub fn get_paid_or_from(&self) -> String {
93        match &self.initial_request.sip.headers.p_asserted_identity {
94            Some(str) => clean_str_parse_country(self.get_country_code(), str.as_str()),
95            None => self.get_from(),
96        }
97    }
98
99    pub fn get_timezone(&self) -> &str {
100        &self.account.environment.time_zone
101    }
102
103    pub fn get_dial_status(&self) -> Option<SIPStatus> {
104        match &self.current_request {
105            Some(request) => match request {
106                Request::Dial(value) => Some(value.sip_status.clone()),
107                _ => None,
108            },
109            _ => None,
110        }
111    }
112
113    pub fn get_digits(&self) -> Option<String> {
114        match &self.current_request {
115            Some(request) => match request {
116                Request::Subsequent(value) => value.digits.clone(),
117                _ => None,
118            },
119            _ => None,
120        }
121    }
122
123    // Modified to limit history size
124    pub fn with_connect_state(mut self, connect: Option<ConnectState>) -> Self {
125        self.connect = connect.clone();
126        if let Some(connect_val) = connect {
127            if self.connect_states.len() < Self::MAX_CONNECT_HISTORY {
128                self.connect_states.push(connect_val);
129            }
130        }
131        self
132    }
133
134    // Modified to store only device IDs in history
135    pub fn with_device(mut self, device: Option<Arc<DeviceStruct>>) -> Self {
136        if let Some(dev) = &device {
137            if self.devices.len() < Self::MAX_DEVICE_HISTORY {
138                self.devices.push(dev.id.clone());
139                for dt in dev.tags.clone() {
140                    self.data.insert(dt.name.clone(), dt);
141                }
142            }
143        }
144        self.device = device;
145        self
146    }
147
148    // Helper to find a region by ID
149    pub fn find_region(&self, region_id: &str, regions: Vec<Arc<Region>>) -> Option<Arc<Region>> {
150        regions
151            .iter()
152            .find(|r| r.id.to_string() == region_id)
153            .map(|r| r.clone())
154    }
155
156    // Create a new state with an updated request
157    pub fn with_request(mut self, request: Request) -> Self {
158        self.current_request = Some(request);
159        self
160    }
161}
162
163// Serializable version without references
164#[derive(Serialize, Deserialize, Clone)]
165pub struct SerializableFlowState {
166    pub base_url: String,
167    pub initial_request: InitialRequest,
168    pub current_request: Option<Request>,
169    pub account_id: String,
170    pub region_id: ObjectId,
171    pub voice_server: VoiceServer,
172    pub device_id: Option<String>,
173    pub connect: Option<ConnectState>,
174    pub tenant_type: TenantType,
175    pub code: CodeCache,
176    pub data: HashMap<String, DeviceTag>,
177    pub connect_states: Vec<ConnectState>,
178    pub device_ids: Vec<String>,
179}
180
181
182#[derive(Serialize, Deserialize, Clone)]
183pub struct ConnectState {
184    pub status: ConnectStatus,
185    pub value: Option<String>,
186    pub match_value: Option<String>,
187    pub connect_to: String,
188}
189
190impl Debug for ConnectState {
191    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
192        f.debug_struct("ConnectState")
193            .field("status", &self.status)
194            .field("connect_to", &self.connect_to)
195            .field("value", &self.value)
196            .field("match_value", &self.match_value)
197            .finish()
198    }
199}
200
201impl ConnectState {
202    pub fn complete(connect_to: &str) -> ConnectState {
203        ConnectState {
204            connect_to: connect_to.to_string(),
205            status: ConnectStatus::OnCompleted,
206            match_value: None,
207            value: Some("Call Completed".to_string()),
208        }
209    }
210    pub fn fail(connect_to: &str) -> ConnectState {
211        ConnectState {
212            connect_to: connect_to.to_string(),
213            status: ConnectStatus::OnFail,
214            match_value: None,
215            value: Some("Call Failed".to_string()),
216        }
217    }
218
219    pub fn device_error(connect_to: &str) -> ConnectState {
220        ConnectState {
221            connect_to: connect_to.to_string(),
222            status: ConnectStatus::OnFail,
223            match_value: None,
224            value: Some("Unknown Device".to_string()),
225        }
226    }
227
228    pub fn no_answer(connect_to: &str) -> ConnectState {
229        ConnectState {
230            connect_to: connect_to.to_string(),
231            status: ConnectStatus::OnNoAnswer,
232            match_value: None,
233            value: Some("No Answer".to_string()),
234        }
235    }
236
237    pub fn busy(connect_to: &str) -> ConnectState {
238        ConnectState {
239            connect_to: connect_to.to_string(),
240            status: ConnectStatus::OnBusy,
241            match_value: None,
242            value: Some("Call Busy".to_string()),
243        }
244    }
245
246    pub fn waiting() -> ConnectState {
247        ConnectState {
248            connect_to: "WAITING".to_string(),
249            status: ConnectStatus::OnWaiting,
250            match_value: None,
251            value: None,
252        }
253    }
254
255    pub fn dialling() -> ConnectState {
256        ConnectState {
257            connect_to: "DIALLING".to_string(),
258            status: ConnectStatus::OnDialling,
259            match_value: None,
260            value: None,
261        }
262    }
263
264    pub fn matched(
265        connect_to: &str,
266        value: Option<String>,
267        match_value: Option<String>,
268    ) -> ConnectState {
269        ConnectState {
270            connect_to: connect_to.to_string(),
271            status: ConnectStatus::OnMatch,
272            match_value,
273            value,
274        }
275    }
276
277    pub fn default(connect_to: &str, value: Option<String>) -> ConnectState {
278        ConnectState {
279            connect_to: connect_to.to_string(),
280            status: ConnectStatus::OnDefault,
281            match_value: None,
282            value,
283        }
284    }
285}
286
287#[derive(Serialize, Deserialize, Clone)]
288pub enum ConnectStatus {
289    OnDefault,
290    OnMatch,
291    OnCompleted,
292    OnFail,
293    OnBusy,
294    OnNoAnswer,
295    OnTransfer,
296    OnError,
297    OnWaiting,
298    OnDialling,
299    OnDeviceFailure
300}
301
302impl Debug for ConnectStatus {
303    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
304        match self {
305            ConnectStatus::OnMatch => write!(f, "OnMatch"),
306            ConnectStatus::OnDefault => write!(f, "OnDefault"),
307            ConnectStatus::OnCompleted => write!(f, "OnCompleted"),
308            ConnectStatus::OnFail => write!(f, "OnFail"),
309            ConnectStatus::OnNoAnswer => write!(f, "OnNoAnswer"),
310            ConnectStatus::OnBusy => write!(f, "OnBusy"),
311            ConnectStatus::OnWaiting => write!(f, "OnWaiting"),
312            ConnectStatus::OnDialling => write!(f, "OnDialling"),
313            _ => {
314                write!(f, "Unknown")
315            }
316        }
317    }
318}