Documentation: - Add detailed README files for all services (auth, character, database, launcher, packet, utils, world) - Create API documentation for the database service with detailed endpoint specifications - Document database schema and relationships - Add service architecture overviews and configuration instructions Unit Tests: - Implement comprehensive test suite for database repositories (user, character, session) - Add gRPC service tests for database interactions - Create tests for packet service components (bufferpool, connection, packets) - Add utility service tests (health check, logging, load balancer, redis cache, service discovery) - Implement auth service user tests - Add character service tests Code Structure: - Reorganize test files into a more consistent structure - Create a dedicated tests crate for integration testing - Add test helpers and mock implementations for easier testing
150 lines
4.6 KiB
Rust
150 lines
4.6 KiB
Rust
use async_trait::async_trait;
|
|
use deadpool_redis::{Config, Pool, Runtime};
|
|
use redis::{AsyncCommands, RedisError};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
#[async_trait]
|
|
pub trait Cache {
|
|
async fn set<T: Serialize + Send + Sync>(&self, key: &String, value: &T, ttl: u64)
|
|
-> Result<(), redis::RedisError>;
|
|
|
|
async fn update<T: Serialize + Send + Sync>(
|
|
&self,
|
|
key: &String,
|
|
value: Option<&T>,
|
|
ttl: Option<u64>,
|
|
) -> Result<(), redis::RedisError>;
|
|
|
|
async fn get<T: for<'de> serde::Deserialize<'de> + Send + Sync>(
|
|
&self,
|
|
key: &String,
|
|
) -> Result<Option<T>, redis::RedisError>;
|
|
|
|
async fn delete(&mut self, key: &str) -> redis::RedisResult<()>;
|
|
|
|
async fn refresh(&self, key: &str, ttl: i64) -> redis::RedisResult<()>;
|
|
}
|
|
|
|
pub struct RedisCache {
|
|
pub pool: Pool,
|
|
}
|
|
|
|
impl RedisCache {
|
|
pub fn new(redis_url: &str) -> Self {
|
|
let cfg = Config::from_url(redis_url);
|
|
let pool = cfg
|
|
.create_pool(Some(Runtime::Tokio1))
|
|
.expect("Failed to create Redis pool");
|
|
RedisCache { pool }
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Cache for RedisCache {
|
|
async fn set<T: Serialize + Send + Sync>(
|
|
&self,
|
|
key: &String,
|
|
value: &T,
|
|
ttl: u64,
|
|
) -> Result<(), redis::RedisError> {
|
|
let mut conn = self.pool.get().await.map_err(|err| {
|
|
redis::RedisError::from((
|
|
redis::ErrorKind::IoError,
|
|
"Failed to get Redis connection",
|
|
format!("{:?}", err),
|
|
))
|
|
})?;
|
|
let serialized_value = serde_json::to_string(value).map_err(|err| {
|
|
RedisError::from((
|
|
redis::ErrorKind::IoError,
|
|
"Serialization error",
|
|
format!("Serialization error: {}", err),
|
|
))
|
|
})?;
|
|
if ttl > 0 {
|
|
conn.set_ex(key, serialized_value, ttl).await
|
|
} else {
|
|
conn.set(key, serialized_value).await
|
|
}
|
|
}
|
|
|
|
async fn get<T: for<'de> Deserialize<'de> + Send + Sync>(
|
|
&self,
|
|
key: &String,
|
|
) -> Result<Option<T>, redis::RedisError> {
|
|
let mut conn = self.pool.get().await.map_err(|err| {
|
|
redis::RedisError::from((
|
|
redis::ErrorKind::IoError,
|
|
"Failed to get Redis connection",
|
|
format!("{:?}", err),
|
|
))
|
|
})?;
|
|
if let Some(serialized_value) = conn.get::<_, Option<String>>(key).await? {
|
|
let deserialized_value = serde_json::from_str(&serialized_value).map_err(|err| {
|
|
RedisError::from((
|
|
redis::ErrorKind::IoError,
|
|
"Deserialization error",
|
|
format!("Deserialization error: {}", err),
|
|
))
|
|
})?;
|
|
Ok(Some(deserialized_value))
|
|
} else {
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
async fn update<T: Serialize + Send + Sync>(
|
|
&self,
|
|
key: &String,
|
|
value: Option<&T>,
|
|
ttl: Option<u64>,
|
|
) -> Result<(), redis::RedisError> {
|
|
let mut conn = self.pool.get().await.map_err(|err| {
|
|
redis::RedisError::from((
|
|
redis::ErrorKind::IoError,
|
|
"Failed to get Redis connection",
|
|
format!("{:?}", err),
|
|
))
|
|
})?;
|
|
let serialized_value;
|
|
if value.is_some() {
|
|
serialized_value = serde_json::to_string(&value).map_err(|err| {
|
|
RedisError::from((
|
|
redis::ErrorKind::IoError,
|
|
"Serialization error",
|
|
format!("Serialization error: {}", err),
|
|
))
|
|
})?;
|
|
} else {
|
|
serialized_value = conn.get(key).await?;
|
|
}
|
|
if ttl != None {
|
|
conn.set_ex(key, serialized_value, ttl.unwrap()).await
|
|
} else {
|
|
conn.set(key, serialized_value).await
|
|
}
|
|
}
|
|
|
|
async fn delete(&mut self, key: &str) -> redis::RedisResult<()> {
|
|
let mut conn = self.pool.get().await.map_err(|err| {
|
|
redis::RedisError::from((
|
|
redis::ErrorKind::IoError,
|
|
"Failed to get Redis connection",
|
|
format!("{:?}", err),
|
|
))
|
|
})?;
|
|
conn.del(key).await
|
|
}
|
|
|
|
async fn refresh(&self, key: &str, ttl: i64) -> redis::RedisResult<()> {
|
|
let mut conn = self.pool.get().await.map_err(|err| {
|
|
redis::RedisError::from((
|
|
redis::ErrorKind::IoError,
|
|
"Failed to get Redis connection",
|
|
format!("{:?}", err),
|
|
))
|
|
})?;
|
|
conn.expire(key, ttl).await
|
|
}
|
|
}
|