//! Bank service layer providing business logic for bank management operations. //! //! Implements the service layer of the bank management system, handling business logic, //! validation, and orchestration. //! //! For full documentation, architecture, and examples, see the [crate README](../README.md). use forge_models::Bank; use forge_repositories::BankRepository; /// Service layer implementation for bank business logic and operations. /// /// Orchestrates bank management operations, handling business logic, validation, /// and data transformation. See [crate README](../README.md) for details. /// /// # Thread Safety /// Thread-safe when used with a thread-safe repository. pub struct BankService { /// The repository instance used for all data persistence operations. /// /// This repository handles the actual storage and retrieval of bank data, /// abstracting away the specific database implementation details. repository: R, } impl BankService { /// Creates a new bank service with the provided repository. /// /// The repository must be initialized and ready for use. pub fn new(repository: R) -> Self { Self { repository } } /// Creates a new bank with the provided ID and JSON data. /// /// Handles validation, duplicate checking, and persistence. /// See [crate README](../README.md) for JSON format and business rules. pub fn create(&self, key: String, json_data: String) -> Result { // Parse JSON data to Bank struct let mut bank: Bank = serde_json::from_str(&json_data).map_err(|e| format!("Invalid Bank JSON: {}", e))?; // Set UID from parameter (authoritative source) bank.uid = key; // Check if bank already exists to prevent duplicates if self.repository.exists(&bank.uid)? { return Err(format!("Bank with uid '{}' already exists", bank.uid)); } if let Err(e) = bank.validate() { return Err(format!("Invalid Bank JSON: {}", e)); } self.repository.create(&bank)?; Ok(bank) } /// Retrieves an bank by their unique identifier with automatic fallback creation. /// /// Implements a "get-or-create" pattern: if the bank doesn't exist, a new one /// with default values is returned (but not persisted). pub fn get_bank(&self, key: String) -> Result { // Attempt to retrieve bank from repository match self.repository.get_by_id(&key)? { // Bank found - return it Some(bank) => Ok(bank), // Bank not found - create fallback bank with default values None => Err(format!("Bank with UID '{}' not found", key)), } } /// Updates an existing bank with new data from JSON. /// /// Handles partial updates, validation, and persistence. /// See [crate README](../README.md) for JSON format and concurrency details. pub fn update_bank(&self, key: String, json_update: String) -> Result { // Retrieve existing bank from repository let mut bank = match self.repository.get_by_id(&key)? { Some(bank) => bank, None => return Err(format!("Bank with UID '{}' not found", key)), }; // Parse and validate JSON update data let update_data: serde_json::Value = serde_json::from_str(&json_update).map_err(|e| format!("Invalid JSON: {}", e))?; // Ensure update data is a JSON object if !update_data.is_object() { return Err("Update data must be a JSON object".to_string()); } // Create a temporary copy to safely apply updates with validation let mut updated_bank = bank.clone(); // Apply updates field by field if let Some(obj) = update_data.as_object() { for (field, value) in obj { match field.as_str() { "uid" => { // Skip UID - it's immutable and set by the system continue; } "name" => { if let Some(name) = value.as_str() { updated_bank.name = name.to_string(); } else { return Err("Name must be a string".to_string()); } } "bank" => { if let Some(bank_val) = value.as_f64() { updated_bank.bank = bank_val; } else { return Err("Bank balance must be a number".to_string()); } } "cash" => { if let Some(cash_val) = value.as_f64() { updated_bank.cash = cash_val; } else { return Err("Cash must be a number".to_string()); } } "earnings" => { if let Some(earnings_val) = value.as_f64() { updated_bank.earnings = earnings_val; } else { return Err("Earnings must be a number".to_string()); } } "pin" => { if let Some(pin_val) = value.as_u64() { updated_bank.pin = pin_val; } else { return Err("PIN must be a number".to_string()); } } "transactions" => { if let Some(arr) = value.as_array() { updated_bank.transactions = arr .iter() .filter_map(|v| v.as_str().map(|s| s.to_string())) .collect(); } else { return Err("Transactions must be an array".to_string()); } } _ => { return Err(format!("Unknown field: {}", field)); } } } } // Validate the updated bank before committing changes updated_bank .validate() .map_err(|e| format!("Validation failed: {}", e))?; // Only commit changes after validation passes bank = updated_bank; // Persist the updated bank to repository self.repository.update(&bank)?; Ok(bank) } /// Permanently deletes an bank from the system. /// /// Irreversible operation. Delegates to repository. pub fn delete_bank(&self, key: String) -> Result<(), String> { // Delegate deletion to repository layer // Future enhancements could add business logic here: // - Authorization checks // - Audit logging // - Cascade deletion // - Soft deletion self.repository.delete(&key) } /// Checks if an bank exists in the system. /// /// Lightweight check without data retrieval. pub fn bank_exists(&self, key: String) -> Result { // Delegate existence check to repository layer // This is a lightweight operation that doesn't retrieve data self.repository.exists(&key) } }