use crate::config::Config;
use chrono::{DateTime, Utc};
use sea_orm::DatabaseConnection;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CallInfo {
pub session_id: String,
pub caller: String,
pub callee: String,
pub direction: String,
pub started_at: DateTime<Utc>,
}
pub struct AppSharedState {
pub custom_data: Arc<RwLock<HashMap<String, Box<dyn std::any::Any + Send + Sync>>>>,
}
impl AppSharedState {
pub fn new() -> Self {
Self {
custom_data: Arc::new(RwLock::new(HashMap::new())),
}
}
}
impl Default for AppSharedState {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for AppSharedState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AppSharedState").finish()
}
}
#[derive(Clone)]
pub struct ApplicationContext {
pub session_vars: Arc<RwLock<HashMap<String, String>>>,
pub shared_state: Arc<AppSharedState>,
pub db: DatabaseConnection,
pub http_client: reqwest::Client,
pub call_info: CallInfo,
pub config: Arc<Config>,
pub storage: crate::storage::Storage,
}
impl ApplicationContext {
pub fn new(
db: DatabaseConnection,
call_info: CallInfo,
config: Arc<Config>,
storage: crate::storage::Storage,
) -> Self {
Self {
session_vars: Arc::new(RwLock::new(HashMap::new())),
shared_state: Arc::new(AppSharedState::new()),
db,
http_client: reqwest::Client::new(),
call_info,
config,
storage,
}
}
pub async fn set_var(&self, key: impl Into<String>, value: impl Into<String>) {
let mut vars = self.session_vars.write().await;
vars.insert(key.into(), value.into());
}
pub async fn get_var(&self, key: &str) -> Option<String> {
let vars = self.session_vars.read().await;
vars.get(key).cloned()
}
}
impl std::fmt::Debug for ApplicationContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ApplicationContext")
.field("call_info", &self.call_info)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_call_info() -> CallInfo {
CallInfo {
session_id: "test-session-1".to_string(),
caller: "sip:alice@example.com".to_string(),
callee: "sip:bob@example.com".to_string(),
direction: "inbound".to_string(),
started_at: Utc::now(),
}
}
#[test]
fn test_call_info_serialization() {
let info = make_call_info();
let json = serde_json::to_string(&info).unwrap();
assert!(json.contains("test-session-1"));
assert!(json.contains("alice"));
}
#[test]
fn test_shared_state_default() {
let state = AppSharedState::default();
let debug = format!("{:?}", state);
assert!(debug.contains("AppSharedState"));
}
#[tokio::test]
async fn test_session_vars() {
let db = sea_orm::Database::connect("sqlite::memory:").await.unwrap();
let storage_config = crate::storage::StorageConfig::Local {
path: std::env::temp_dir()
.join("rustpbx-test-storage")
.to_string_lossy()
.to_string(),
};
let storage = crate::storage::Storage::new(&storage_config).unwrap();
let ctx =
ApplicationContext::new(db, make_call_info(), Arc::new(Config::default()), storage);
assert!(ctx.get_var("lang").await.is_none());
ctx.set_var("lang", "zh").await;
assert_eq!(ctx.get_var("lang").await, Some("zh".to_string()));
ctx.set_var("lang", "en").await;
assert_eq!(ctx.get_var("lang").await, Some("en".to_string()));
}
}