cal_core/
core.rs

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