use crate::core::{InsuranceConfig, TaxConfig};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CalculatorConfig {
pub insurance: InsuranceConfig,
pub tax: TaxConfig,
pub enable_logging: bool,
pub log_level: String,
}
impl Default for CalculatorConfig {
fn default() -> Self {
Self {
insurance: InsuranceConfig::default(),
tax: TaxConfig::default(),
enable_logging: true,
log_level: "info".to_string(),
}
}
}
impl CalculatorConfig {
#[must_use]
pub fn builder() -> ConfigBuilder {
ConfigBuilder::new()
}
}
#[derive(Debug, Default)]
pub struct ConfigBuilder {
insurance_percentage: Option<f64>,
admin_fee: Option<f64>,
enable_logging: bool,
log_level: String,
}
impl ConfigBuilder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn insurance_percentage(mut self, percent: f64) -> Self {
self.insurance_percentage = Some(percent);
self
}
pub fn admin_fee(mut self, fee: f64) -> Self {
self.admin_fee = Some(fee);
self
}
pub fn enable_logging(mut self, enable: bool) -> Self {
self.enable_logging = enable;
self
}
pub fn log_level(mut self, level: &str) -> Self {
self.log_level = level.to_string();
self
}
#[must_use]
pub fn build(self) -> CalculatorConfig {
let mut config = CalculatorConfig::default();
if let Some(percent) = self.insurance_percentage {
config.insurance.percentage = rust_decimal::Decimal::from_f64_retain(percent / 100.0)
.expect("Failed to convert insurance percentage to Decimal");
}
if let Some(fee) = self.admin_fee {
config.tax.admin_fee = rust_decimal::Decimal::from_f64_retain(fee)
.expect("Failed to convert admin fee to Decimal");
}
config.enable_logging = self.enable_logging;
config.log_level = if self.log_level.is_empty() {
"info".to_string()
} else {
self.log_level
};
config
}
}
#[cfg(test)]
mod tests {
use super::*;
use rust_decimal_macros::dec;
#[test]
fn test_default_values() {
let config = CalculatorConfig::default();
assert_eq!(config.insurance.percentage, dec!(0.015));
assert_eq!(config.tax.admin_fee, dec!(120_000));
assert!(config.enable_logging);
assert_eq!(config.log_level, "info");
}
#[test]
fn test_builder_full_config() {
let config = CalculatorConfig::builder()
.insurance_percentage(2.5)
.admin_fee(200_000.0)
.enable_logging(false)
.log_level("error")
.build();
assert_eq!(config.insurance.percentage, dec!(0.025));
assert_eq!(config.tax.admin_fee, dec!(200_000));
assert!(!config.enable_logging);
assert_eq!(config.log_level, "error");
}
#[test]
fn test_builder_partial_config() {
let config = CalculatorConfig::builder().admin_fee(150_000.0).build();
assert_eq!(config.tax.admin_fee, dec!(150_000));
assert_eq!(config.insurance.percentage, dec!(0.015));
assert!(config.enable_logging);
assert_eq!(config.log_level, "info");
}
#[test]
fn test_percentage_conversion() {
let test_cases = vec![
(1.5, dec!(0.015)),
(2.0, dec!(0.02)),
(0.5, dec!(0.005)),
(10.0, dec!(0.1)),
(0.1, dec!(0.001)),
];
for (input, expected) in test_cases {
let config = CalculatorConfig::builder()
.insurance_percentage(input)
.build();
assert_eq!(
config.insurance.percentage, expected,
"Failed for input: {}",
input
);
}
}
#[test]
fn test_admin_fee_precision() {
let config = CalculatorConfig::builder().admin_fee(120_000.50).build();
assert_eq!(config.tax.admin_fee.to_string(), "120000.50");
}
#[test]
fn test_log_level() {
let config = CalculatorConfig::builder().log_level("debug").build();
assert_eq!(config.log_level, "debug");
let config = CalculatorConfig::builder().log_level("").build();
assert_eq!(config.log_level, "info");
}
#[test]
fn test_enable_logging() {
let config = CalculatorConfig::builder().enable_logging(false).build();
assert!(!config.enable_logging);
let config = CalculatorConfig::builder().enable_logging(true).build();
assert!(config.enable_logging);
}
#[test]
fn test_serialization() {
let config = CalculatorConfig::builder()
.insurance_percentage(2.0)
.admin_fee(150_000.0)
.build();
let json = serde_json::to_string(&config).unwrap();
assert!(json.contains("insurance"));
assert!(json.contains("tax"));
assert!(json.contains("0.02"));
assert!(json.contains("150000"));
}
#[test]
fn test_deserialization() {
let json = r#"{
"insurance": {"percentage": "0.025"},
"tax": {"base_amount": "0", "admin_fee": "175000"},
"enable_logging": false,
"log_level": "warn"
}"#;
let config: CalculatorConfig = serde_json::from_str(json).unwrap();
assert_eq!(config.insurance.percentage, dec!(0.025));
assert_eq!(config.tax.admin_fee, dec!(175_000));
assert!(!config.enable_logging);
assert_eq!(config.log_level, "warn");
}
#[test]
fn test_roundtrip_serialization() {
let original = CalculatorConfig::builder()
.insurance_percentage(1.75)
.admin_fee(135_000.0)
.enable_logging(true)
.log_level("trace")
.build();
let json = serde_json::to_string(&original).unwrap();
let restored: CalculatorConfig = serde_json::from_str(&json).unwrap();
assert_eq!(original.insurance.percentage, restored.insurance.percentage);
assert_eq!(original.tax.admin_fee, restored.tax.admin_fee);
assert_eq!(original.enable_logging, restored.enable_logging);
assert_eq!(original.log_level, restored.log_level);
}
#[test]
fn test_clone() {
let config1 = CalculatorConfig::builder()
.insurance_percentage(2.0)
.build();
let config2 = config1.clone();
assert_eq!(config1.insurance.percentage, config2.insurance.percentage);
assert_eq!(config1.tax.admin_fee, config2.tax.admin_fee);
}
#[test]
fn test_debug_format() {
let config = CalculatorConfig::default();
let debug_str = format!("{:?}", config);
assert!(debug_str.contains("CalculatorConfig"));
assert!(debug_str.contains("insurance"));
assert!(debug_str.contains("tax"));
}
}