refactor: Reorganize charging and errors modules into proper directory structure

- Move charging module files into charging/ directory (types.rs, engine.rs, credit_management.rs, rating_billing.rs)
- Move error module files into errors/ directory (types.rs, helpers.rs)
- Create mod.rs files for charging and errors modules with simplified exports
- Update import paths to use super:: for intra-module references
- Remove charging_new/errors_new aliases and use charging/errors directly
- Simplify module
This commit is contained in:
nutcas3
2026-04-14 16:29:27 +03:00
parent c1f408c789
commit c63c92245f
17 changed files with 199 additions and 209 deletions
+162
View File
@@ -0,0 +1,162 @@
use anyhow::Context;
pub type ChargingResult<T> = Result<T, ChargingError>;
pub trait ErrorContext<T> {
fn with_context(self, msg: &str) -> ChargingResult<T>;
}
impl<T, E> ErrorContext<T> for Result<T, E>
where
E: Into<ChargingError>,
{
fn with_context(self, msg: &str) -> ChargingResult<T> {
self.map_err(|e| {
let charging_err = e.into();
match charging_err {
ChargingError::InternalError(internal_msg) => {
ChargingError::InternalError(format!("{}: {}", msg, internal_msg))
}
_ => charging_err,
}
})
}
}
// Validation helper functions
pub fn validate_ip(ip: &str) -> ChargingResult<()> {
if ip.is_empty() {
return Err(ChargingError::InvalidInput("IP address cannot be empty".to_string()));
}
// Basic IP validation (simplified)
let parts: Vec<&str> = ip.split('.').collect();
if parts.len() != 4 {
return Err(ChargingError::InvalidInput("Invalid IP address format".to_string()));
}
for part in parts {
if let Ok(num) = part.parse::<u8>() {
if num == 0 {
return Err(ChargingError::InvalidInput("IP address cannot be 0.0.0.0".to_string()));
}
} else {
return Err(ChargingError::InvalidInput("Invalid IP address octet".to_string()));
}
}
Ok(())
}
pub fn validate_bytes(bytes: u64) -> ChargingResult<()> {
if bytes == 0 {
return Err(ChargingError::InvalidInput("Bytes cannot be zero".to_string()));
}
if bytes > 1_000_000_000_000 { // 1TB limit
return Err(ChargingError::InvalidInput("Bytes exceed maximum limit".to_string()));
}
Ok(())
}
pub fn validate_imsi(imsi: &str) -> ChargingResult<()> {
if imsi.is_empty() {
return Err(ChargingError::InvalidInput("IMSI cannot be empty".to_string()));
}
if imsi.len() < 15 || imsi.len() > 15 {
return Err(ChargingError::InvalidInput("IMSI must be exactly 15 digits".to_string()));
}
if !imsi.chars().all(|c| c.is_ascii_digit()) {
return Err(ChargingError::InvalidInput("IMSI must contain only digits".to_string()));
}
Ok(())
}
pub fn validate_session_id(session_id: &str) -> ChargingResult<()> {
if session_id.is_empty() {
return Err(ChargingError::InvalidInput("Session ID cannot be empty".to_string()));
}
if session_id.len() > 64 {
return Err(ChargingError::InvalidInput("Session ID too long".to_string()));
}
Ok(())
}
pub fn validate_amount(amount: f64) -> ChargingResult<()> {
if amount <= 0.0 {
return Err(ChargingError::InvalidInput("Amount must be positive".to_string()));
}
if amount > 10_000.0 {
return Err(ChargingError::InvalidInput("Amount exceeds maximum limit".to_string()));
}
Ok(())
}
// Error conversion helpers
impl From<redis::RedisError> for ChargingError {
fn from(err: redis::RedisError) -> Self {
ChargingError::RedisConnection(err.to_string())
}
}
impl From<serde_json::Error> for ChargingError {
fn from(err: serde_json::Error) -> Self {
ChargingError::SerializationError(err.to_string())
}
}
impl From<std::num::ParseIntError> for ChargingError {
fn from(err: std::num::ParseIntError) -> Self {
ChargingError::InvalidInput(format!("Parse error: {}", err))
}
}
impl From<std::num::ParseFloatError> for ChargingError {
fn from(err: std::num::ParseFloatError) -> Self {
ChargingError::InvalidInput(format!("Parse error: {}", err))
}
}
// Error logging helpers
pub fn log_error(error: &ChargingError) {
match error {
ChargingError::RedisConnection(msg) => {
tracing::error!("Redis connection error: {}", msg);
}
ChargingError::RedisOperation(msg) => {
tracing::error!("Redis operation error: {}", msg);
}
ChargingError::SubscriberNotFound(imsi) => {
tracing::warn!("Subscriber not found: {}", imsi);
}
ChargingError::RatingPlanNotFound(plan_id) => {
tracing::warn!("Rating plan not found: {}", plan_id);
}
ChargingError::InsufficientCredit { available, requested } => {
tracing::warn!("Insufficient credit: available={}, requested={}", available, requested);
}
ChargingError::UsageBlocked(reason) => {
tracing::warn!("Usage blocked: {}", reason);
}
ChargingError::InvalidInput(msg) => {
tracing::warn!("Invalid input: {}", msg);
}
ChargingError::SerializationError(msg) => {
tracing::error!("Serialization error: {}", msg);
}
ChargingError::ConfigurationError(msg) => {
tracing::error!("Configuration error: {}", msg);
}
ChargingError::InternalError(msg) => {
tracing::error!("Internal error: {}", msg);
}
}
}
+2
View File
@@ -0,0 +1,2 @@
pub mod types;
pub mod helpers;
+64
View File
@@ -0,0 +1,64 @@
use axum::response::{IntoResponse, Response};
use axum::http::StatusCode;
use serde_json::json;
use std::fmt;
#[derive(Debug, Clone)]
pub enum ChargingError {
RedisConnection(String),
RedisOperation(String),
SubscriberNotFound(String),
RatingPlanNotFound(String),
InsufficientCredit { available: u64, requested: u64 },
UsageBlocked(String),
InvalidInput(String),
SerializationError(String),
ConfigurationError(String),
InternalError(String),
}
impl fmt::Display for ChargingError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ChargingError::RedisConnection(msg) => write!(f, "Redis connection error: {}", msg),
ChargingError::RedisOperation(msg) => write!(f, "Redis operation error: {}", msg),
ChargingError::SubscriberNotFound(imsi) => write!(f, "Subscriber not found: {}", imsi),
ChargingError::RatingPlanNotFound(plan_id) => write!(f, "Rating plan not found: {}", plan_id),
ChargingError::InsufficientCredit { available, requested } => {
write!(f, "Insufficient credit: available={}, requested={}", available, requested)
}
ChargingError::UsageBlocked(reason) => write!(f, "Usage blocked: {}", reason),
ChargingError::InvalidInput(msg) => write!(f, "Invalid input: {}", msg),
ChargingError::SerializationError(msg) => write!(f, "Serialization error: {}", msg),
ChargingError::ConfigurationError(msg) => write!(f, "Configuration error: {}", msg),
ChargingError::InternalError(msg) => write!(f, "Internal error: {}", msg),
}
}
}
impl std::error::Error for ChargingError {}
impl IntoResponse for ChargingError {
fn into_response(self) -> Response {
let (status, error_message) = match self {
ChargingError::RedisConnection(_) => (StatusCode::SERVICE_UNAVAILABLE, "Redis connection error"),
ChargingError::RedisOperation(_) => (StatusCode::SERVICE_UNAVAILABLE, "Redis operation error"),
ChargingError::SubscriberNotFound(_) => (StatusCode::NOT_FOUND, "Subscriber not found"),
ChargingError::RatingPlanNotFound(_) => (StatusCode::NOT_FOUND, "Rating plan not found"),
ChargingError::InsufficientCredit { .. } => (StatusCode::PAYMENT_REQUIRED, "Insufficient credit"),
ChargingError::UsageBlocked(_) => (StatusCode::FORBIDDEN, "Usage blocked"),
ChargingError::InvalidInput(_) => (StatusCode::BAD_REQUEST, "Invalid input"),
ChargingError::SerializationError(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Serialization error"),
ChargingError::ConfigurationError(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Configuration error"),
ChargingError::InternalError(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Internal error"),
};
let body = json!({
"error": error_message,
"message": self.to_string(),
"code": status.as_u16()
});
(status, axum::Json(body)).into_response()
}
}