diff --git a/apps/charging-engine/src/auth.rs b/apps/charging-engine/src/auth.rs index e597a8d..0328fca 100644 --- a/apps/charging-engine/src/auth.rs +++ b/apps/charging-engine/src/auth.rs @@ -8,12 +8,41 @@ use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation} use serde::{Deserialize, Serialize}; use std::env; +/// JWT claims for authentication tokens +/// +/// # Example +/// +/// ```rust +/// use charging_engine::auth::Claims; +/// +/// let claims = Claims { +/// sub: "user123".to_string(), +/// exp: 1234567890, +/// }; +/// ``` #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Claims { pub sub: String, pub exp: usize, } +/// Authentication configuration for JWT and API keys +/// +/// # Example +/// +/// ```rust,no_run +/// use charging_engine::auth::AuthConfig; +/// use std::env; +/// +/// # fn main() -> Result<(), String> { +/// env::set_var("JWT_SECRET", "a_very_secure_secret_at_least_32_chars"); +/// env::set_var("API_KEYS", "key1,key2,key3"); +/// +/// let config = AuthConfig::from_env()?; +/// assert!(config.validate_api_key("key1")); +/// # Ok(()) +/// # } +/// ``` #[derive(Clone)] pub struct AuthConfig { pub secret: String, @@ -21,6 +50,21 @@ pub struct AuthConfig { } impl AuthConfig { + /// Load authentication configuration from environment variables + /// + /// Requires `JWT_SECRET` (at least 32 characters) and optionally `API_KEYS` + /// (comma-separated list of valid API keys). + /// + /// # Example + /// + /// ```rust,no_run + /// use charging_engine::auth::AuthConfig; + /// + /// # fn main() -> Result<(), String> { + /// let config = AuthConfig::from_env()?; + /// # Ok(()) + /// # } + /// ``` pub fn from_env() -> Result { let secret = env::var("JWT_SECRET") .map_err(|_| "JWT_SECRET environment variable is required".to_string())?; diff --git a/apps/charging-engine/src/charging/engine.rs b/apps/charging-engine/src/charging/engine.rs index 104fb30..c42cce6 100644 --- a/apps/charging-engine/src/charging/engine.rs +++ b/apps/charging-engine/src/charging/engine.rs @@ -9,6 +9,19 @@ use crate::errors::ChargingResult; /// ChargingEngine coordinates Redis-backed hot-path state (balances, sessions) /// and Postgres-backed configuration (rating plans) via `RatingPlansRepo`. +/// +/// # Example +/// +/// ```rust,no_run +/// use charging_engine::charging::engine::ChargingEngine; +/// use charging_engine::charging::rating_plans_repo::RatingPlansRepo; +/// +/// # async fn example() -> Result<(), Box> { +/// let plans = RatingPlansRepo::connect("postgres://localhost/db").await?; +/// let engine = ChargingEngine::new("redis://localhost", plans, 60)?; +/// # Ok(()) +/// # } +/// ``` pub struct ChargingEngine { pub(crate) redis_client: redis::Client, pub(crate) plans: RatingPlansRepo, diff --git a/apps/charging-engine/src/charging/types.rs b/apps/charging-engine/src/charging/types.rs index 46488ad..04f6c26 100644 --- a/apps/charging-engine/src/charging/types.rs +++ b/apps/charging-engine/src/charging/types.rs @@ -2,6 +2,16 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use redis::{FromRedisValue, ToRedisArgs, ToSingleRedisArg}; +/// Charging rule for usage authorization +/// +/// # Example +/// +/// ```rust +/// use charging_engine::charging::types::ChargingRule; +/// +/// let rule = ChargingRule::Allowed; +/// assert_eq!(rule.as_str(), "ALLOWED"); +/// ``` #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub enum ChargingRule { Allowed, @@ -14,6 +24,16 @@ pub enum ChargingRule { } impl ChargingRule { + /// Convert charging rule to string representation + /// + /// # Example + /// + /// ```rust + /// use charging_engine::charging::types::ChargingRule; + /// + /// let rule = ChargingRule::Allowed; + /// assert_eq!(rule.as_str(), "ALLOWED"); + /// ``` pub fn as_str(&self) -> &str { match self { ChargingRule::Allowed => "ALLOWED", @@ -27,6 +47,27 @@ impl ChargingRule { } } +/// Subscriber account information +/// +/// # Example +/// +/// ```rust +/// use charging_engine::charging::types::{SubscriberAccount, AccountStatus}; +/// use chrono::Utc; +/// +/// let account = SubscriberAccount { +/// imsi: "1234567890".to_string(), +/// balance: 1000, +/// data_limit: 1000000000, +/// data_used: 0, +/// voice_limit: 1000, +/// voice_used: 0, +/// sms_limit: 100, +/// sms_used: 0, +/// status: AccountStatus::Active, +/// last_updated: Utc::now(), +/// }; +/// ``` #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SubscriberAccount { pub imsi: String, @@ -41,6 +82,15 @@ pub struct SubscriberAccount { pub last_updated: DateTime, } +/// Account status +/// +/// # Example +/// +/// ```rust +/// use charging_engine::charging::types::AccountStatus; +/// +/// let status = AccountStatus::Active; +/// ``` #[derive(Debug, Clone, Serialize, Deserialize)] pub enum AccountStatus { Active, @@ -49,6 +99,24 @@ pub enum AccountStatus { Blocked, } +/// Usage event for charging +/// +/// # Example +/// +/// ```rust +/// use charging_engine::charging::types::{UsageEvent, UsageType}; +/// use chrono::Utc; +/// +/// let event = UsageEvent { +/// imsi: "1234567890".to_string(), +/// session_id: "session123".to_string(), +/// usage_type: UsageType::Data, +/// volume: 1000, +/// timestamp: Utc::now(), +/// rate: 0.01, +/// cost: 10.0, +/// }; +/// ``` #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UsageEvent { pub imsi: String, @@ -60,6 +128,15 @@ pub struct UsageEvent { pub cost: f64, } +/// Usage type classification +/// +/// # Example +/// +/// ```rust +/// use charging_engine::charging::types::UsageType; +/// +/// let usage_type = UsageType::Data; +/// ``` #[derive(Debug, Clone, Serialize, Deserialize)] pub enum UsageType { Data, @@ -67,6 +144,25 @@ pub enum UsageType { SMS, } +/// Rating plan for subscriber billing +/// +/// # Example +/// +/// ```rust +/// use charging_engine::charging::types::RatingPlan; +/// +/// let plan = RatingPlan { +/// plan_id: "plan1".to_string(), +/// name: "Basic Plan".to_string(), +/// data_rate: 0.01, +/// voice_rate: 0.05, +/// sms_rate: 0.1, +/// monthly_fee: 10.0, +/// data_limit: 1000000000, +/// voice_limit: 1000, +/// sms_limit: 100, +/// }; +/// ``` #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RatingPlan { pub plan_id: String, diff --git a/apps/charging-engine/src/circuit_breaker.rs b/apps/charging-engine/src/circuit_breaker.rs index 6e546e8..9fe2fdb 100644 --- a/apps/charging-engine/src/circuit_breaker.rs +++ b/apps/charging-engine/src/circuit_breaker.rs @@ -2,6 +2,16 @@ use std::sync::Arc; use std::time::{Duration, Instant}; use tokio::sync::RwLock; +/// Circuit breaker state +/// +/// # Example +/// +/// ```rust +/// use charging_engine::circuit_breaker::CircuitState; +/// +/// let state = CircuitState::Closed; +/// assert_eq!(state, CircuitState::Closed); +/// ``` #[derive(Debug, Clone, PartialEq, Copy)] pub enum CircuitState { Closed, @@ -9,6 +19,15 @@ pub enum CircuitState { HalfOpen, } +/// Circuit breaker error type +/// +/// # Example +/// +/// ```rust +/// use charging_engine::circuit_breaker::CircuitBreakerError; +/// +/// let error: CircuitBreakerError<&str> = CircuitBreakerError::Open; +/// ``` #[derive(Debug)] pub enum CircuitBreakerError { Open, @@ -26,6 +45,16 @@ impl std::fmt::Display for CircuitBreakerError { impl std::error::Error for CircuitBreakerError {} +/// Circuit breaker for fault tolerance +/// +/// # Example +/// +/// ```rust,no_run +/// use charging_engine::circuit_breaker::CircuitBreaker; +/// use std::time::Duration; +/// +/// let breaker = CircuitBreaker::new(5, Duration::from_secs(60)); +/// ``` #[derive(Clone)] pub struct CircuitBreaker { state: Arc>, @@ -36,6 +65,21 @@ pub struct CircuitBreaker { } impl CircuitBreaker { + /// Create a new circuit breaker + /// + /// # Arguments + /// + /// * `failure_threshold` - Number of failures before opening the circuit + /// * `timeout` - Duration to wait before attempting to close the circuit + /// + /// # Example + /// + /// ```rust + /// use charging_engine::circuit_breaker::CircuitBreaker; + /// use std::time::Duration; + /// + /// let breaker = CircuitBreaker::new(5, Duration::from_secs(60)); + /// ``` pub fn new(failure_threshold: u32, timeout: Duration) -> Self { Self { state: Arc::new(RwLock::new(CircuitState::Closed)), diff --git a/apps/charging-engine/src/lib.rs b/apps/charging-engine/src/lib.rs index 2f4717e..ff6e21a 100644 --- a/apps/charging-engine/src/lib.rs +++ b/apps/charging-engine/src/lib.rs @@ -1,3 +1,37 @@ +//! # Charging Engine +//! +//! A high-performance telecom charging engine for real-time usage-based billing. +//! +//! ## Features +//! +//! - Real-time rating and billing +//! - Circuit breaker for fault tolerance +//! - Rate limiting for API protection +//! - Comprehensive monitoring and metrics +//! +//! ## Example +//! +//! ```rust,no_run +//! use charging_engine::ChargingEngine; +//! use charging_engine::charging::types::UsageEvent; +//! +//! #[tokio::main] +//! async fn main() -> Result<(), Box> { +//! let engine = ChargingEngine::new("redis://localhost").await?; +//! +//! let event = UsageEvent { +//! imsi: "1234567890".to_string(), +//! volume: 1000, +//! timestamp: chrono::Utc::now(), +//! }; +//! +//! let cost = engine.calculate_cost(&event).await?; +//! println!("Cost: {}", cost); +//! +//! Ok(()) +//! } +//! ``` + pub mod auth; pub mod charging; pub mod circuit_breaker;