Skip to main content

ppoppo_infra/
ext.rs

1//! Extension traits with typed convenience methods.
2//!
3//! These traits provide `Serialize`/`DeserializeOwned` wrappers around
4//! the `serde_json::Value`-based core traits. They are automatically
5//! available for any type implementing the core trait via blanket impls.
6
7use async_trait::async_trait;
8use serde::{Serialize, de::DeserializeOwned};
9
10use crate::Result;
11use crate::cache::Cache;
12use crate::job_queue::JobQueue;
13use crate::publisher::Publisher;
14use crate::types::Priority;
15
16/// Typed convenience methods for [`Cache`].
17#[async_trait]
18pub trait CacheExt: Cache {
19    /// Set a typed value with optional TTL in seconds.
20    async fn set_typed<T: Serialize + Send + Sync>(
21        &self,
22        key: &str,
23        value: &T,
24        ttl_seconds: Option<i32>,
25    ) -> Result<()> {
26        let json = serde_json::to_value(value)?;
27        self.set(key, &json, ttl_seconds).await
28    }
29
30    /// Get a typed value if it exists and is not expired.
31    async fn get_typed<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>> {
32        match self.get(key).await? {
33            Some(value) => Ok(Some(serde_json::from_value(value)?)),
34            None => Ok(None),
35        }
36    }
37
38    /// Get multiple typed values atomically.
39    async fn mget_typed<T: DeserializeOwned>(
40        &self,
41        keys: &[&str],
42    ) -> Result<Vec<(String, Option<T>)>> {
43        let raw = self.mget(keys).await?;
44        let mut results = Vec::with_capacity(raw.len());
45        for (key, value) in raw {
46            let parsed = match value {
47                Some(v) => Some(serde_json::from_value(v)?),
48                None => None,
49            };
50            results.push((key, parsed));
51        }
52        Ok(results)
53    }
54}
55
56/// Blanket impl: any Cache automatically gets CacheExt.
57impl<T: Cache + ?Sized> CacheExt for T {}
58
59/// Typed convenience methods for [`JobQueue`].
60#[async_trait]
61pub trait JobQueueExt: JobQueue {
62    /// Push a typed payload to the queue. Returns the generated job ID.
63    async fn push_typed<T: Serialize + Send + Sync>(
64        &self,
65        queue_name: &str,
66        payload: &T,
67        priority: Priority,
68    ) -> Result<String> {
69        let json = serde_json::to_value(payload)?;
70        self.push(queue_name, &json, priority).await
71    }
72
73    /// Push a typed payload with full options.
74    async fn push_typed_with_options<T: Serialize + Send + Sync>(
75        &self,
76        queue_name: &str,
77        payload: &T,
78        priority: Priority,
79        delay_seconds: i32,
80        max_attempts: i32,
81    ) -> Result<String> {
82        let json = serde_json::to_value(payload)?;
83        self.push_with_options(queue_name, &json, priority, delay_seconds, max_attempts)
84            .await
85    }
86
87    /// Pop and deserialize a typed job from the queue.
88    async fn pop_typed<T: DeserializeOwned>(
89        &self,
90        queue_name: &str,
91        lock_duration_seconds: i32,
92    ) -> Result<Option<crate::types::TypedJob<T>>> {
93        match self.pop(queue_name, lock_duration_seconds).await? {
94            Some(job) => {
95                let payload: T = serde_json::from_value(job.payload)?;
96                Ok(Some(crate::types::TypedJob {
97                    id: job.id,
98                    queue_name: job.queue_name,
99                    payload,
100                    attempts: job.attempts,
101                    max_attempts: job.max_attempts,
102                    created_at: job.created_at,
103                }))
104            }
105            None => Ok(None),
106        }
107    }
108}
109
110/// Blanket impl: any JobQueue automatically gets JobQueueExt.
111impl<T: JobQueue + ?Sized> JobQueueExt for T {}
112
113/// Typed convenience methods for [`Publisher`].
114#[async_trait]
115pub trait PublisherExt: Publisher {
116    /// Publish a typed payload to a channel.
117    async fn publish_typed<T: Serialize + Send + Sync>(
118        &self,
119        channel: &str,
120        payload: &T,
121    ) -> Result<()> {
122        let json = serde_json::to_value(payload)?;
123        self.publish(channel, &json).await
124    }
125
126    /// Publish a typed payload to multiple channels atomically.
127    async fn publish_multi_typed<T: Serialize + Send + Sync>(
128        &self,
129        channels: &[&str],
130        payload: &T,
131    ) -> Result<()> {
132        let json = serde_json::to_value(payload)?;
133        self.publish_multi(channels, &json).await
134    }
135}
136
137/// Blanket impl: any Publisher automatically gets PublisherExt.
138impl<T: Publisher + ?Sized> PublisherExt for T {}