cal-core 0.2.158

Callable core lib
Documentation
use crate::device::device::{DeviceStruct, DeviceTag};
use crate::{clean_str_parse_country, AccountLite, CodeCache, Region, VoiceServer};
use bson::oid::ObjectId;
use cal_jambonz::rest::{InitialRequest, Request};
use cal_jambonz::shared::shared::SIPStatus;
use cal_jambonz::TenantType;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt::{Debug, Formatter};
use std::sync::Arc;

pub const HANG_UP_CONNECT: &'static str = "HANGUP";
pub const DEFAULT_TTS_VOICE: &'static str = "en-GB-Standard-B";
pub const DEFAULT_TTS_LANGUAGE: &'static str = "en-GB";
pub const DEFAULT_TTS_VENDOR: &'static str = "google";
pub const DEFAULT_ASR_LANGUAGE: &'static str = "en-GB";
pub const DEFAULT_ASR_VENDOR: &'static str = "google";
pub const DEFAULT_RECORDING_RETENTION: &'static str = "P30D";
pub const DEFAULT_CLIENT_PROFILE: &'static str = "generic_e164_plus";
pub const DEFAULT_COUNTRY_CODE: &'static str = "gb";
pub const DEFAULT_DIGITS_FINISH_KEY: &'static str = "#";
pub const DEFAULT_ENDPOINT_PRESENT: &'static str = "pass-through";
pub const DEFAULT_REMOTE_DESTINATION: &'static str = "[request.tp]";
pub const DEFAULT_VM_FINISH_KEY: &'static str = "#";
pub const DEFAULT_ENDPOINT_FORMAT: &'static str = "e164p";
pub const DEFAULT_TIMEZONE: &'static str = "Europe/London";
pub const DEFAULT_TRUNK_DESCRIPTION: &'static str = "SIP Trunk";
pub const DEFAULT_TRUNK_PORT: u16 = 5060;
pub const DEFAULT_RING_TIME: u8 = 180;
pub const DEFAULT_DIGIT_TIMEOUT: u8 = 10;
pub const DEFAULT_MAX_DIGITS: u8 = 1;
pub const DEFAULT_VM_TIMEOUT: u8 = 30;
pub const DEFAULT_VM_MAX_LENGTH: u8 = 30;
pub const DEFAULT_CALL_TIME: u16 = 14400;

#[derive(Clone)]
pub struct FlowState {
    // Owned small fields
    pub base_url: String,
    pub initial_request: InitialRequest,
    pub current_request: Option<Request>,

    // Arc for shared complex structures
    pub account: Arc<AccountLite>,
    pub region: Arc<Region>,

    // Small value type
    pub voice_server: VoiceServer,

    // Arc for optional large structure
    pub device: Option<Arc<DeviceStruct>>,

    // Modified during traversal
    pub connect: Option<ConnectState>,

    // Small value type
    pub tenant_type: TenantType,
    pub code: CodeCache,

    // Modified during traversal
    pub data: HashMap<String, DeviceTag>,

    // Limited history collections
    pub connect_states: Vec<ConnectState>,
    pub devices: Vec<String>, // Store device IDs instead of full structures
}

impl FlowState {
    // Constants for limiting history size
    const MAX_CONNECT_HISTORY: usize = 20;
    const MAX_DEVICE_HISTORY: usize = 20;

    // Get methods that avoid cloning where possible
    pub fn get_country_code(&self) -> &str {
        &self.account.environment.country_code
    }

    pub fn get_from(&self) -> String {
        clean_str_parse_country(
            self.get_country_code(),
            self.initial_request.from.as_str(),
        )
    }

    pub fn get_to(&self) -> String {
        clean_str_parse_country(
            self.get_country_code(),
            self.initial_request.to.as_str(),
        )
    }

    pub fn get_paid_or_from(&self) -> String {
        match &self.initial_request.sip.headers.p_asserted_identity {
            Some(str) => clean_str_parse_country(self.get_country_code(), str.as_str()),
            None => self.get_from(),
        }
    }

    pub fn get_timezone(&self) -> &str {
        &self.account.environment.time_zone
    }

    pub fn get_dial_status(&self) -> Option<SIPStatus> {
        match &self.current_request {
            Some(request) => match request {
                Request::Dial(value) => Some(value.sip_status.clone()),
                _ => None,
            },
            _ => None,
        }
    }

    pub fn get_digits(&self) -> Option<String> {
        match &self.current_request {
            Some(request) => match request {
                Request::Subsequent(value) => value.digits.clone(),
                _ => None,
            },
            _ => None,
        }
    }

    // Modified to limit history size
    pub fn with_connect_state(mut self, connect: Option<ConnectState>) -> Self {
        self.connect = connect.clone();
        if let Some(connect_val) = connect {
            if self.connect_states.len() < Self::MAX_CONNECT_HISTORY {
                self.connect_states.push(connect_val);
            }
        }
        self
    }

    // Modified to store only device IDs in history
    pub fn with_device(mut self, device: Option<Arc<DeviceStruct>>) -> Self {
        if let Some(dev) = &device {
            if self.devices.len() < Self::MAX_DEVICE_HISTORY {
                self.devices.push(dev.id.clone());
                for dt in dev.tags.clone() {
                    self.data.insert(dt.name.clone(), dt);
                }
            }
        }
        self.device = device;
        self
    }

    // Helper to find a region by ID
    pub fn find_region(&self, region_id: &str, regions: Vec<Arc<Region>>) -> Option<Arc<Region>> {
        regions
            .iter()
            .find(|r| r.id.to_string() == region_id)
            .map(|r| r.clone())
    }

    // Create a new state with an updated request
    pub fn with_request(mut self, request: Request) -> Self {
        self.current_request = Some(request);
        self
    }
}

// Serializable version without references
#[derive(Serialize, Deserialize, Clone)]
pub struct SerializableFlowState {
    pub base_url: String,
    pub initial_request: InitialRequest,
    pub current_request: Option<Request>,
    pub account_id: String,
    pub region_id: ObjectId,
    pub voice_server: VoiceServer,
    pub device_id: Option<String>,
    pub connect: Option<ConnectState>,
    pub tenant_type: TenantType,
    pub code: CodeCache,
    pub data: HashMap<String, DeviceTag>,
    pub connect_states: Vec<ConnectState>,
    pub device_ids: Vec<String>,
}


#[derive(Serialize, Deserialize, Clone)]
pub struct ConnectState {
    pub status: ConnectStatus,
    pub value: Option<String>,
    pub match_value: Option<String>,
    pub connect_to: String,
}

impl Debug for ConnectState {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
        f.debug_struct("ConnectState")
            .field("status", &self.status)
            .field("connect_to", &self.connect_to)
            .field("value", &self.value)
            .field("match_value", &self.match_value)
            .finish()
    }
}

impl ConnectState {
    pub fn complete(connect_to: &str) -> ConnectState {
        ConnectState {
            connect_to: connect_to.to_string(),
            status: ConnectStatus::OnCompleted,
            match_value: None,
            value: Some("Call Completed".to_string()),
        }
    }
    pub fn fail(connect_to: &str) -> ConnectState {
        ConnectState {
            connect_to: connect_to.to_string(),
            status: ConnectStatus::OnFail,
            match_value: None,
            value: Some("Call Failed".to_string()),
        }
    }

    pub fn device_error(connect_to: &str) -> ConnectState {
        ConnectState {
            connect_to: connect_to.to_string(),
            status: ConnectStatus::OnFail,
            match_value: None,
            value: Some("Unknown Device".to_string()),
        }
    }

    pub fn no_answer(connect_to: &str) -> ConnectState {
        ConnectState {
            connect_to: connect_to.to_string(),
            status: ConnectStatus::OnNoAnswer,
            match_value: None,
            value: Some("No Answer".to_string()),
        }
    }

    pub fn busy(connect_to: &str) -> ConnectState {
        ConnectState {
            connect_to: connect_to.to_string(),
            status: ConnectStatus::OnBusy,
            match_value: None,
            value: Some("Call Busy".to_string()),
        }
    }

    pub fn waiting() -> ConnectState {
        ConnectState {
            connect_to: "WAITING".to_string(),
            status: ConnectStatus::OnWaiting,
            match_value: None,
            value: None,
        }
    }

    pub fn dialling() -> ConnectState {
        ConnectState {
            connect_to: "DIALLING".to_string(),
            status: ConnectStatus::OnDialling,
            match_value: None,
            value: None,
        }
    }

    pub fn matched(
        connect_to: &str,
        value: Option<String>,
        match_value: Option<String>,
    ) -> ConnectState {
        ConnectState {
            connect_to: connect_to.to_string(),
            status: ConnectStatus::OnMatch,
            match_value,
            value,
        }
    }

    pub fn default(connect_to: &str, value: Option<String>) -> ConnectState {
        ConnectState {
            connect_to: connect_to.to_string(),
            status: ConnectStatus::OnDefault,
            match_value: None,
            value,
        }
    }
}

#[derive(Serialize, Deserialize, Clone)]
pub enum ConnectStatus {
    OnDefault,
    OnMatch,
    OnCompleted,
    OnFail,
    OnBusy,
    OnNoAnswer,
    OnTransfer,
    OnError,
    OnWaiting,
    OnDialling,
    OnDeviceFailure
}

impl Debug for ConnectStatus {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
        match self {
            ConnectStatus::OnMatch => write!(f, "OnMatch"),
            ConnectStatus::OnDefault => write!(f, "OnDefault"),
            ConnectStatus::OnCompleted => write!(f, "OnCompleted"),
            ConnectStatus::OnFail => write!(f, "OnFail"),
            ConnectStatus::OnNoAnswer => write!(f, "OnNoAnswer"),
            ConnectStatus::OnBusy => write!(f, "OnBusy"),
            ConnectStatus::OnWaiting => write!(f, "OnWaiting"),
            ConnectStatus::OnDialling => write!(f, "OnDialling"),
            _ => {
                write!(f, "Unknown")
            }
        }
    }
}