Files
telecom-platform/apps/packet-gateway/src/charging_client.rs
T
nutcas3 5ec4db009f feat: Add charging engine client with usage reporting and batch processing support
- Add ChargingEngineClient struct with reqwest HTTP client and configurable base URL
- Add report_usage method for single usage event reporting with volume conversion (bytes to MB)
- Add report_batch_usage method for processing multiple usage events with error tolerance
- Add UsageEvent and UsageResponse types for API request/response serialization
- Add tracing integration (info, warn, error) for usage reporting
2026-04-25 05:25:48 +03:00

99 lines
2.7 KiB
Rust

use anyhow::{Context, Result};
use chrono::Utc;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use tracing::{info, warn, error};
#[derive(Debug, Serialize)]
struct UsageEvent {
imsi: String,
session_id: String,
volume: f64,
usage_type: String,
timestamp: i64,
}
#[derive(Debug, Deserialize)]
struct UsageResponse {
success: bool,
message: Option<String>,
}
pub struct ChargingEngineClient {
client: Client,
base_url: String,
}
impl ChargingEngineClient {
pub fn new(base_url: String) -> Self {
Self {
client: Client::new(),
base_url,
}
}
pub async fn report_usage(
&self,
imsi: String,
session_id: String,
volume_bytes: u64,
) -> Result<()> {
let volume_mb = volume_bytes as f64 / (1024.0 * 1024.0);
let timestamp = Utc::now().timestamp();
let event = UsageEvent {
imsi: imsi.clone(),
session_id,
volume: volume_mb,
usage_type: "Data".to_string(),
timestamp,
};
let url = format!("{}/v1/usage/process", self.base_url);
match self
.client
.post(&url)
.json(&event)
.send()
.await
{
Ok(response) => {
if response.status().is_success() {
info!("Successfully reported usage for IMSI {}: {} MB", imsi, volume_mb);
Ok(())
} else {
let status = response.status();
let body = response.text().await.unwrap_or_default();
warn!(
"Failed to report usage for IMSI {}: {} - {}",
imsi, status, body
);
Err(anyhow::anyhow!(
"Charging engine returned error {}: {}",
status,
body
))
}
}
Err(e) => {
error!("Failed to send usage to charging engine: {}", e);
Err(e.context("Failed to send usage to charging engine"))
}
}
}
pub async fn report_batch_usage(
&self,
usage_data: Vec<(String, String, u64)>,
) -> Result<()> {
for (imsi, session_id, volume_bytes) in usage_data {
if let Err(e) = self.report_usage(imsi, session_id, volume_bytes).await {
warn!("Failed to report usage for one session: {}", e);
// Continue with other sessions instead of failing the entire batch
}
}
Ok(())
}
}