feat: Add Rust SDK with tokio async/await support and comprehensive Telecom Platform API integration

- Add Cargo.toml with tokio 1.52.1 (full features), reqwest 0.13.2 (json), tokio-tungstenite 0.29.0, serde 1.0.228, chrono 0.4.44
- Add TelecomClient with reqwest Client and TelecomConfig with 30s timeout, 3 retries
- Add subscriber management: get_subscriber, list_subscribers with pagination/status filter, create_subscriber, update_subscriber, suspend_subscriber, activate_subscriber, terminate_subscriber
This commit is contained in:
nutcas3
2026-04-17 17:53:11 +03:00
parent f2f7c0ec86
commit 7758afd150
2 changed files with 548 additions and 0 deletions
+23
View File
@@ -0,0 +1,23 @@
[package]
name = "telecom-sdk"
version = "1.0.0"
edition = "2024"
authors = ["nutcas3 <[email protected]>"]
description = "Rust SDK for Telecom Platform"
license = "MIT"
repository = "https://github.com/nutcas3/telecom-platform"
[dependencies]
tokio = { version = "1.52.1", features = ["full"] }
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.149"
reqwest = { version = "0.13.2", features = ["json"] }
tokio-tungstenite = "0.29.0"
futures-util = "0.3.32"
uuid = { version = "1.23.1", features = ["v4", "serde"] }
chrono = { version = "0.4.44", features = ["serde"] }
thiserror = "2.0.18"
url = "2.5.8"
[dev-dependencies]
tokio-test = "0.4.5"
+525
View File
@@ -0,0 +1,525 @@
//! 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<String>,
pub status: SubscriberStatus,
pub plan_id: i64,
pub balance: f64,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SubscriberList {
pub subscribers: Vec<Subscriber>,
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<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct UpdateSubscriberRequest {
pub first_name: Option<String>,
pub last_name: Option<String>,
pub email: Option<String>,
pub plan_id: Option<i64>,
pub status: Option<SubscriberStatus>,
}
#[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<Utc>,
pub period_end: DateTime<Utc>,
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<Utc>,
pub metadata: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct UsageEventList {
pub events: Vec<UsageEvent>,
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<Utc>,
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<CurrentSession>,
pub today_usage: Option<HashMap<String, i64>>,
}
#[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<String>,
pub created_at: DateTime<Utc>,
pub completed_at: Option<DateTime<Utc>>,
pub metadata: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct PaymentTransactionList {
pub transactions: Vec<PaymentTransaction>,
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<HashMap<String, serde_json::Value>>,
}
#[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<Utc>,
pub checks: HashMap<String, serde_json::Value>,
pub uptime: f64,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct WebSocketMessage {
pub r#type: String,
pub data: HashMap<String, serde_json::Value>,
pub timestamp: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize)]
pub struct GraphQLRequest {
pub query: String,
pub variables: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GraphQLResponse {
pub data: Option<HashMap<String, serde_json::Value>>,
pub errors: Option<Vec<GraphQLError>>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GraphQLError {
pub message: String,
pub locations: Option<Vec<GraphQLErrorLocation>>,
pub path: Option<Vec<String>>,
pub extensions: Option<HashMap<String, serde_json::Value>>,
}
#[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<String>,
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<reqwest::Response, TelecomError> {
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<Subscriber, TelecomError> {
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<SubscriberStatus>,
) -> Result<SubscriberList, TelecomError> {
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<Subscriber, TelecomError> {
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<Subscriber, TelecomError> {
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<Subscriber, TelecomError> {
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<Subscriber, TelecomError> {
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<bool, TelecomError> {
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<Utc>,
end_date: DateTime<Utc>,
) -> Result<UsageStats, TelecomError> {
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<PaymentTransaction, TelecomError> {
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<SystemStats, TelecomError> {
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<HealthStatus, TelecomError> {
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<HashMap<String, serde_json::Value>>,
) -> Result<GraphQLResponse, TelecomError> {
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<F>(&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::<WebSocketMessage>(&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::<WebSocketMessage>(&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(())
}
}