162 lines
6.2 KiB
Rust
162 lines
6.2 KiB
Rust
//! 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<Option<Bank>, 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<bool, String>;
|
|
}
|
|
|
|
/// 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<C: RedisClient> {
|
|
/// 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<C: RedisClient> RedisBankRepository<C> {
|
|
/// Creates a new Redis bank repository with the provided client.
|
|
pub fn new(client: C) -> Self {
|
|
Self { client }
|
|
}
|
|
}
|
|
|
|
impl<C: RedisClient> BankRepository for RedisBankRepository<C> {
|
|
/// 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<Option<Bank>, 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::<Bank>(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<bool, String> {
|
|
// 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)
|
|
}
|
|
}
|