use super::SerializerError;
use async_trait::async_trait;
use reinhardt_db::orm::{Model, QuerySet};
use serde::{Serialize, de::DeserializeOwned};
use serde_json::Value;
use std::collections::HashMap;
#[async_trait]
pub trait SerializerSaveMixin
where
Self: Sized,
{
type Model: Model + Serialize + DeserializeOwned + Clone + Send + Sync;
async fn create(data: Value) -> Result<Self::Model, SerializerError> {
Self::validate_for_create(&data).await?;
let model: Self::Model =
serde_json::from_value(data).map_err(|e| SerializerError::Serde {
message: format!("Failed to deserialize: {}", e),
})?;
let queryset = QuerySet::<Self::Model>::new();
let created = queryset
.create(model)
.await
.map_err(|e| SerializerError::Other {
message: format!("Failed to create: {}", e),
})?;
Ok(created)
}
async fn update(
mut instance: Self::Model,
data: Value,
) -> Result<Self::Model, SerializerError> {
Self::validate_for_update(&data, Some(&instance)).await?;
if let Value::Object(map) = data {
let instance_value =
serde_json::to_value(&instance).map_err(|e| SerializerError::Serde {
message: format!("Failed to serialize instance: {}", e),
})?;
if let Value::Object(mut instance_map) = instance_value {
for (key, value) in map {
instance_map.insert(key, value);
}
instance = serde_json::from_value(Value::Object(instance_map)).map_err(|e| {
SerializerError::Serde {
message: format!("Failed to deserialize updated instance: {}", e),
}
})?;
}
}
use reinhardt_db::orm::manager::Manager;
let manager = Manager::<Self::Model>::new();
let updated = manager
.update(&instance)
.await
.map_err(|e| SerializerError::Other {
message: format!("Failed to update instance: {}", e),
})?;
Ok(updated)
}
async fn save(
data: Value,
instance: Option<Self::Model>,
) -> Result<Self::Model, SerializerError> {
match instance {
Some(inst) => Self::update(inst, data).await,
None => Self::create(data).await,
}
}
async fn validate_for_create(_data: &Value) -> Result<(), SerializerError> {
Ok(())
}
async fn validate_for_update(
_data: &Value,
_instance: Option<&Self::Model>,
) -> Result<(), SerializerError> {
Ok(())
}
}
#[derive(Debug, Clone, Default)]
pub struct SaveContext {
pub extra: HashMap<String, Value>,
}
impl SaveContext {
pub fn new() -> Self {
Self {
extra: HashMap::new(),
}
}
pub fn with_extra(mut self, key: String, value: Value) -> Self {
self.extra.insert(key, value);
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use reinhardt_db::orm::{FieldSelector, Model};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
struct TestUser {
id: Option<i64>,
username: String,
email: String,
}
#[derive(Debug, Clone)]
struct TestUserFields;
impl FieldSelector for TestUserFields {
fn with_alias(self, _alias: &str) -> Self {
self
}
}
impl Model for TestUser {
type PrimaryKey = i64;
type Fields = TestUserFields;
fn table_name() -> &'static str {
"test_users"
}
fn new_fields() -> Self::Fields {
TestUserFields
}
fn primary_key(&self) -> Option<Self::PrimaryKey> {
self.id
}
fn set_primary_key(&mut self, value: Self::PrimaryKey) {
self.id = Some(value);
}
}
struct TestUserSerializer;
impl SerializerSaveMixin for TestUserSerializer {
type Model = TestUser;
}
#[test]
fn test_save_context_creation() {
let context = SaveContext::new();
assert!(context.extra.is_empty());
}
#[test]
fn test_save_context_with_extra() {
let context = SaveContext::new()
.with_extra("key1".to_string(), serde_json::json!("value1"))
.with_extra("key2".to_string(), serde_json::json!(42));
assert_eq!(context.extra.len(), 2);
assert_eq!(
context.extra.get("key1").unwrap(),
&serde_json::json!("value1")
);
assert_eq!(context.extra.get("key2").unwrap(), &serde_json::json!(42));
}
#[tokio::test]
async fn test_validate_for_create_default() {
let data = serde_json::json!({
"username": "testuser",
"email": "test@example.com"
});
let result = TestUserSerializer::validate_for_create(&data).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_validate_for_update_default() {
let data = serde_json::json!({"email": "newemail@example.com"});
let instance = TestUser {
id: Some(1),
username: "testuser".to_string(),
email: "old@example.com".to_string(),
};
let result = TestUserSerializer::validate_for_update(&data, Some(&instance)).await;
assert!(result.is_ok());
}
}
#[derive(Debug, Clone)]
pub struct CacheAwareSaveContext {
pub context: SaveContext,
pub invalidator: Option<crate::serializers::CacheInvalidator>,
}
impl CacheAwareSaveContext {
pub fn new() -> Self {
Self {
context: SaveContext::new(),
invalidator: None,
}
}
pub fn with_invalidator(invalidator: crate::serializers::CacheInvalidator) -> Self {
Self {
context: SaveContext::new(),
invalidator: Some(invalidator),
}
}
pub fn invalidate_cache(&self, model_name: &str, pk: &str) -> Vec<String> {
if let Some(ref invalidator) = self.invalidator {
invalidator.invalidate(model_name, pk)
} else {
Vec::new()
}
}
pub fn add_cache_dependency(&self, cache_key: &str, model_name: &str, pk: &str) {
if let Some(ref invalidator) = self.invalidator {
invalidator.add_dependency(cache_key, model_name, pk);
}
}
pub fn inner(&self) -> &SaveContext {
&self.context
}
pub fn into_inner(self) -> SaveContext {
self.context
}
}
impl Default for CacheAwareSaveContext {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod cache_aware_tests {
use super::*;
use crate::serializers::InvalidationStrategy;
#[test]
fn test_cache_aware_save_context_without_invalidator() {
let context = CacheAwareSaveContext::new();
assert!(context.invalidator.is_none());
let keys = context.invalidate_cache("User", "123");
assert_eq!(keys.len(), 0);
}
#[test]
fn test_cache_aware_save_context_with_invalidator() {
let invalidator =
crate::serializers::CacheInvalidator::new(InvalidationStrategy::Immediate);
let context = CacheAwareSaveContext::with_invalidator(invalidator);
assert!(context.invalidator.is_some());
context.add_cache_dependency("user:123:profile", "User", "123");
let keys = context.invalidate_cache("User", "123");
assert_eq!(keys.len(), 1);
assert_eq!(keys[0], "user:123:profile");
}
#[test]
fn test_cache_aware_save_context_inner() {
let context = CacheAwareSaveContext::new();
let inner = context.inner();
assert!(inner.extra.is_empty());
}
#[test]
fn test_cache_aware_save_context_into_inner() {
let context = CacheAwareSaveContext::new();
let inner = context.into_inner();
assert!(inner.extra.is_empty());
}
}