//! Bank repository implementation for data persistence operations. //! //! This module provides the data access layer for bank account management, //! implementing the repository pattern to abstract database operations. //! //! For full documentation and examples, see the [crate README](../README.md). use forge_models::Bank; use forge_shared::{RedisClient, parse_json_value, parse_redis_value}; /// Repository trait defining the contract for bank data operations. /// /// This trait abstracts the data persistence layer, allowing different /// implementations (Redis, PostgreSQL, etc.) while maintaining a consistent /// interface for the service layer. All implementations must be thread-safe. pub trait BankRepository: Send + Sync { /// Creates a new bank in the repository. fn create(&self, bank: &Bank) -> Result<(), String>; /// Retrieves an bank by their unique identifier. fn get_by_id(&self, id: &str) -> Result, String>; /// Updates an existing bank with new data. fn update(&self, bank: &Bank) -> Result<(), String>; /// Permanently removes an bank from the repository. fn delete(&self, id: &str) -> Result<(), String>; /// Checks if an bank exists in the repository. fn exists(&self, id: &str) -> Result; } /// Redis-based implementation of the BankRepository trait. /// /// This implementation uses Redis hash maps to store bank data, providing /// efficient field-level access and atomic operations. Each bank is stored /// as a separate hash with the key format `bank:{uid}`. pub struct RedisBankRepository { /// The Redis client used for all database operations. /// /// This client handles the actual communication with Redis, including /// connection management, command execution, and error handling. client: C, } impl RedisBankRepository { /// Creates a new Redis bank repository with the provided client. pub fn new(client: C) -> Self { Self { client } } } impl BankRepository for RedisBankRepository { /// Creates a new bank in Redis using hash map storage. /// /// Stores each bank as a Redis hash map with the key format `bank:{uid}`. /// Each field of the bank struct becomes a field in the Redis hash. fn create(&self, bank: &Bank) -> Result<(), String> { // Generate Redis key using bank UID let redis_key = format!("bank:{}", bank.uid()); // Serialize bank to JSON string let bank_json = serde_json::to_string(bank).map_err(|e| format!("Failed to serialize bank: {}", e))?; // Parse JSON string back to Value for field extraction let json_value: serde_json::Value = serde_json::from_str(&bank_json) .map_err(|e| format!("Failed to parse bank JSON: {}", e))?; // Extract fields from JSON object if let serde_json::Value::Object(bank_map) = json_value { // Convert each field to Redis-compatible format let fields: Vec<(String, String)> = bank_map .into_iter() .map(|(field, value)| (field, parse_json_value(&value))) .collect(); // Store all fields atomically using Redis HMSET self.client.hash_mset(redis_key, fields) } else { Err("Failed to convert bank to object".to_string()) } } /// Retrieves an bank from Redis by their unique identifier. /// /// Uses Redis HGETALL to retrieve all fields of the bank hash map, /// then reconstructs the Bank struct through JSON deserialization. fn get_by_id(&self, id: &str) -> Result, String> { // Generate Redis key using bank UID let redis_key = format!("bank:{}", id); // Retrieve all hash fields from Redis let bank_string = self.client.hash_get_all(redis_key)?; // Return None if no data found (bank doesn't exist) if bank_string.is_empty() { return Ok(None); } // Parse comma-separated field-value pairs let parts: Vec<&str> = bank_string.split(", ").collect(); let mut json_map = serde_json::Map::new(); let mut i = 0; // Process pairs of field names and values while i + 1 < parts.len() { let key = parts[i]; let value = parts[i + 1]; // Convert Redis string value back to proper JSON type let json_value = parse_redis_value(value); json_map.insert(key.to_string(), json_value); i += 2; // Move to next field-value pair } // Reconstruct Bank from JSON object let json_obj = serde_json::Value::Object(json_map); match serde_json::from_value::(json_obj) { Ok(bank) => Ok(Some(bank)), // Return None for any deserialization errors (corrupted data) Err(_) => Ok(None), } } /// Updates an existing bank with the provided data. /// /// Uses `HMSET` to atomically update the provided fields. Fields present in Redis /// but missing from the input are preserved. fn update(&self, bank: &Bank) -> Result<(), String> { // Delegate to create() which handles both creation and updates // Redis HMSET naturally supports upsert behavior self.create(bank) } /// Permanently deletes an bank and all associated data from Redis. /// /// Removes the entire Redis hash containing the bank's data. /// This operation is irreversible. fn delete(&self, id: &str) -> Result<(), String> { // Generate Redis key using bank UID let redis_key = format!("bank:{}", id); // Delete the entire hash key from Redis // This removes all fields and the key itself atomically self.client.delete_key(redis_key) } /// Checks if an bank exists in Redis without retrieving the data. /// /// Uses Redis EXISTS command for a lightweight check. fn exists(&self, id: &str) -> Result { // Generate Redis key using bank UID let redis_key = format!("bank:{}", id); // Check if the key exists in Redis // This is a lightweight operation that doesn't retrieve data self.client.key_exists(redis_key) } }