//! Telecom Platform Rust SDK //! //! Provides async/await support with tokio for the Telecom Platform API. use chrono::{DateTime, Utc}; use reqwest::{Client, StatusCode}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::time::Duration; use thiserror::Error; use tokio_tungstenite::{connect_async, tungstenite::Message}; use url::Url; use uuid::Uuid; #[derive(Error, Debug)] pub enum TelecomError { #[error("Authentication failed")] AuthenticationError, #[error("API error: {0}")] APIError(String), #[error("Network error: {0}")] NetworkError(#[from] reqwest::Error), #[error("Validation error: {0}")] ValidationError(String), #[error("Rate limit exceeded")] RateLimitError, #[error("Server error: {0}")] ServerError(StatusCode), #[error("WebSocket error: {0}")] WebSocketError(String), #[error("JSON error: {0}")] JsonError(#[from] serde_json::Error), } #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(rename_all = "snake_case")] pub enum SubscriberStatus { Active, Suspended, Terminated, } #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(rename_all = "snake_case")] pub enum UsageType { Data, Voice, Sms, } #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(rename_all = "snake_case")] pub enum PaymentStatus { Pending, Completed, Failed, Refunded, } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct Subscriber { pub id: i64, pub imsi: String, pub msisdn: String, pub first_name: String, pub last_name: String, pub email: String, pub organization_id: Option, pub status: SubscriberStatus, pub plan_id: i64, pub balance: f64, pub created_at: DateTime, pub updated_at: DateTime, } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct SubscriberList { pub subscribers: Vec, pub total: i64, pub page: i32, pub page_size: i32, pub has_next: bool, pub has_prev: bool, } #[derive(Debug, Clone, Serialize)] pub struct CreateSubscriberRequest { pub imsi: String, pub msisdn: String, pub first_name: String, pub last_name: String, pub email: String, pub plan_id: i64, pub organization_id: Option, } #[derive(Debug, Clone, Serialize)] pub struct UpdateSubscriberRequest { pub first_name: Option, pub last_name: Option, pub email: Option, pub plan_id: Option, pub status: Option, } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct UsageStats { pub subscriber_id: String, pub data_up: i64, pub data_down: i64, pub voice_seconds: i64, pub sms_count: i64, pub period_start: DateTime, pub period_end: DateTime, pub cost: f64, } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct UsageEvent { pub id: String, pub subscriber_id: String, pub usage_type: UsageType, pub amount: i64, pub cost: f64, pub timestamp: DateTime, pub metadata: Option>, } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct UsageEventList { pub events: Vec, pub total: i64, pub page: i32, pub page_size: i32, pub has_next: bool, pub has_prev: bool, } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct CurrentSession { pub session_id: String, pub start_time: DateTime, pub data_up: i64, pub data_down: i64, pub voice_seconds: i64, pub sms_count: i64, } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct RealTimeUsage { pub current_session: Option, pub today_usage: Option>, } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct PaymentTransaction { pub id: String, pub subscriber_id: String, pub amount: f64, pub currency: String, pub status: PaymentStatus, pub gateway: String, pub transaction_id: Option, pub created_at: DateTime, pub completed_at: Option>, pub metadata: Option>, } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct PaymentTransactionList { pub transactions: Vec, pub total: i64, pub page: i32, pub page_size: i32, pub has_next: bool, pub has_prev: bool, } #[derive(Debug, Clone, Serialize)] pub struct CreatePaymentRequest { pub subscriber_id: String, pub amount: f64, pub currency: String, pub gateway: String, pub metadata: Option>, } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct RatingPlan { pub plan_id: String, pub name: String, pub data_rate: f64, pub voice_rate: f64, pub sms_rate: f64, pub monthly_fee: f64, pub data_limit: i64, pub voice_limit: i64, pub sms_limit: i64, } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct SystemStats { pub active_sessions: i64, pub total_accounts: i64, pub blocked_users: i64, pub low_balance_alerts: i64, pub uptime: f64, pub cpu_usage: f64, pub memory_usage: f64, } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct HealthStatus { pub status: String, pub timestamp: DateTime, pub checks: HashMap, pub uptime: f64, } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct WebSocketMessage { pub r#type: String, pub data: HashMap, pub timestamp: DateTime, } #[derive(Debug, Clone, Serialize)] pub struct GraphQLRequest { pub query: String, pub variables: Option>, } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct GraphQLResponse { pub data: Option>, pub errors: Option>, } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct GraphQLError { pub message: String, pub locations: Option>, pub path: Option>, pub extensions: Option>, } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct GraphQLErrorLocation { pub line: i32, pub column: i32, } /// Telecom Platform SDK client #[derive(Clone)] pub struct TelecomClient { client: Client, config: TelecomConfig, } #[derive(Debug, Clone)] pub struct TelecomConfig { pub api_url: String, pub api_key: Option, pub timeout: Duration, pub max_retries: u32, } impl Default for TelecomConfig { fn default() -> Self { Self { api_url: "http://localhost:8000".to_string(), api_key: None, timeout: Duration::from_secs(30), max_retries: 3, } } } impl TelecomClient { /// Create a new Telecom client pub fn new(config: TelecomConfig) -> Self { let client = Client::builder() .timeout(config.timeout) .build() .expect("Failed to create HTTP client"); Self { client, config } } async fn handle_response(&self, response: reqwest::Response) -> Result { match response.status() { StatusCode::UNAUTHORIZED => Err(TelecomError::AuthenticationError), StatusCode::TOO_MANY_REQUESTS => Err(TelecomError::RateLimitError), status if status.is_client_error() => { let error_text = response.text().await.unwrap_or_else(|_| "Bad request".to_string()); Err(TelecomError::APIError(error_text)) } status if status.is_server_error() => Err(TelecomError::ServerError(status)), _ => Ok(response), } } /// Get subscriber by ID pub async fn get_subscriber(&self, id: i64) -> Result { let response = self .client .get(&format!("{}/v1/subscribers/{}", self.config.api_url, id)) .send() .await?; let response = self.handle_response(response).await?; Ok(response.json().await?) } /// List subscribers with pagination pub async fn list_subscribers( &self, page: i32, page_size: i32, status: Option, ) -> Result { let mut request = self .client .get(&format!("{}/v1/subscribers", self.config.api_url)) .query(&[("page", page), ("page_size", page_size)]); if let Some(status) = status { request = request.query(&[("status", serde_json::to_string(&status).unwrap())]); } let response = request.send().await?; let response = self.handle_response(response).await?; Ok(response.json().await?) } /// Create a new subscriber pub async fn create_subscriber(&self, request: CreateSubscriberRequest) -> Result { let response = self .client .post(&format!("{}/v1/subscribers", self.config.api_url)) .json(&request) .send() .await?; let response = self.handle_response(response).await?; Ok(response.json().await?) } /// Update an existing subscriber pub async fn update_subscriber(&self, id: i64, request: UpdateSubscriberRequest) -> Result { let response = self .client .put(&format!("{}/v1/subscribers/{}", self.config.api_url, id)) .json(&request) .send() .await?; let response = self.handle_response(response).await?; Ok(response.json().await?) } /// Suspend a subscriber pub async fn suspend_subscriber(&self, id: i64) -> Result { let response = self .client .post(&format!("{}/v1/subscribers/{}/suspend", self.config.api_url, id)) .send() .await?; let response = self.handle_response(response).await?; Ok(response.json().await?) } /// Activate a suspended subscriber pub async fn activate_subscriber(&self, id: i64) -> Result { let response = self .client .post(&format!("{}/v1/subscribers/{}/activate", self.config.api_url, id)) .send() .await?; let response = self.handle_response(response).await?; Ok(response.json().await?) } /// Terminate a subscriber pub async fn terminate_subscriber(&self, id: i64) -> Result { let response = self .client .delete(&format!("{}/v1/subscribers/{}", self.config.api_url, id)) .send() .await?; let response = self.handle_response(response).await?; let result: serde_json::Value = response.json().await?; Ok(result["success"].as_bool().unwrap_or(false)) } /// Get usage statistics for a subscriber pub async fn get_usage_stats( &self, subscriber_id: i64, start_date: DateTime, end_date: DateTime, ) -> Result { let response = self .client .get(&format!("{}/v1/subscribers/{}/usage", self.config.api_url, subscriber_id)) .query(&[ ("start_date", start_date.to_rfc3339()), ("end_date", end_date.to_rfc3339()), ]) .send() .await?; let response = self.handle_response(response).await?; Ok(response.json().await?) } /// Create a payment transaction pub async fn create_payment_transaction(&self, request: CreatePaymentRequest) -> Result { let response = self .client .post(&format!("{}/v1/payments/transactions", self.config.api_url)) .json(&request) .send() .await?; let response = self.handle_response(response).await?; Ok(response.json().await?) } /// Get system statistics pub async fn get_system_stats(&self) -> Result { let response = self .client .get(&format!("{}/v1/system/stats", self.config.api_url)) .send() .await?; let response = self.handle_response(response).await?; Ok(response.json().await?) } /// Get system health status pub async fn get_health_status(&self) -> Result { let response = self .client .get(&format!("{}/v1/health", self.config.api_url)) .send() .await?; let response = self.handle_response(response).await?; Ok(response.json().await?) } /// Execute a GraphQL query pub async fn execute_graphql_query( &self, query: String, variables: Option>, ) -> Result { let request = GraphQLRequest { query, variables }; let response = self .client .post(&format!("{}/graphql", self.config.api_url)) .json(&request) .send() .await?; let response = self.handle_response(response).await?; Ok(response.json().await?) } /// Connect to WebSocket for real-time updates pub async fn connect_websocket(&self, message_handler: F) -> Result<(), TelecomError> where F: Fn(WebSocketMessage) + Send + Sync + 'static, { let ws_url = self.config.api_url.replace("http://", "ws://") + "/ws"; let (ws_stream, _) = connect_async(&ws_url).await.map_err(|e| TelecomError::WebSocketError(e.to_string()))?; println!("WebSocket connected to {}", ws_url); let (mut write, mut read) = ws_stream.split(); let handler = Arc::new(message_handler); // Spawn task to handle incoming messages let handler_clone = handler.clone(); let read_task = tokio::spawn(async move { while let Some(msg) = read.next().await { match msg { Ok(tungstenite::Message::Text(text)) => { if let Ok(ws_msg) = serde_json::from_str::(&text) { handler_clone(ws_msg); } } Ok(tungstenite::Message::Binary(data)) => { if let Ok(text) = String::from_utf8(data) { if let Ok(ws_msg) = serde_json::from_str::(&text) { handler_clone(ws_msg); } } } Ok(tungstenite::Message::Close(_)) => { println!("WebSocket connection closed"); break; } Err(e) => { eprintln!("WebSocket error: {}", e); break; } _ => {} } } }); // Return a handle that can be used to manage the connection tokio::spawn(async move { let _ = read_task.await; }); Ok(()) } }