Skip to main content

vld_redis/
lib.rs

1//! # vld-redis
2//!
3//! Redis integration for `vld`.
4//!
5//! ## Overview
6//!
7//! `vld-redis` keeps one entrypoint macro:
8//!
9//! - `impl_to_redis!(conn)`
10//!
11//! After rebinding, `conn` becomes a validating wrapper with auto conversion for:
12//!
13//! - `set/get`
14//! - `mset/mget`
15//! - `hset/hget`
16//! - `lpush/rpush/lpop/rpop`
17//! - `sadd/smembers`
18//! - `zadd/zrange`
19//! - `publish`
20//!
21//! All other native Redis methods are still available through deref to the inner connection.
22
23use std::fmt;
24use std::ops::{Deref, DerefMut};
25
26pub use vld;
27
28/// Redis connection wrapper with validate+JSON behavior.
29pub struct RedisConn<C> {
30    inner: C,
31}
32
33impl<C> RedisConn<C> {
34    pub fn new(inner: C) -> Self {
35        Self { inner }
36    }
37
38    pub fn into_inner(self) -> C {
39        self.inner
40    }
41}
42
43impl<C> Deref for RedisConn<C> {
44    type Target = C;
45
46    fn deref(&self) -> &Self::Target {
47        &self.inner
48    }
49}
50
51impl<C> DerefMut for RedisConn<C> {
52    fn deref_mut(&mut self) -> &mut Self::Target {
53        &mut self.inner
54    }
55}
56
57impl<C> RedisConn<C>
58where
59    C: redis::ConnectionLike,
60{
61    fn encode_json_value<V>(value: &V) -> Result<serde_json::Value, VldRedisError>
62    where
63        V: serde::Serialize + vld::schema::VldParse,
64    {
65        let json =
66            serde_json::to_value(value).map_err(|e| VldRedisError::Serialization(e.to_string()))?;
67        <V as vld::schema::VldParse>::vld_parse_value(&json).map_err(VldRedisError::Validation)?;
68        Ok(json)
69    }
70
71    fn encode_json_string<V>(value: &V) -> Result<String, VldRedisError>
72    where
73        V: serde::Serialize + vld::schema::VldParse,
74    {
75        let json = Self::encode_json_value(value)?;
76        serde_json::to_string(&json).map_err(|e| VldRedisError::Serialization(e.to_string()))
77    }
78
79    fn encode_json_bytes<V>(value: &V) -> Result<Vec<u8>, VldRedisError>
80    where
81        V: serde::Serialize + vld::schema::VldParse,
82    {
83        let json = Self::encode_json_value(value)?;
84        serde_json::to_vec(&json).map_err(|e| VldRedisError::Serialization(e.to_string()))
85    }
86
87    fn decode_json_bytes<T>(bytes: &[u8]) -> Result<T, VldRedisError>
88    where
89        T: vld::schema::VldParse,
90    {
91        let value: serde_json::Value = serde_json::from_slice(bytes)
92            .map_err(|e| VldRedisError::Deserialization(e.to_string()))?;
93        <T as vld::schema::VldParse>::vld_parse_value(&value).map_err(VldRedisError::Validation)
94    }
95
96    pub fn set<K, V>(&mut self, key: K, value: &V) -> Result<(), VldRedisError>
97    where
98        K: redis::ToRedisArgs,
99        V: serde::Serialize + vld::schema::VldParse,
100    {
101        let s = Self::encode_json_string(value)?;
102        redis::cmd("SET")
103            .arg(key)
104            .arg(s)
105            .query::<()>(&mut self.inner)?;
106        Ok(())
107    }
108
109    pub fn get<K, T>(&mut self, key: K) -> Result<Option<T>, VldRedisError>
110    where
111        K: redis::ToRedisArgs,
112        T: vld::schema::VldParse,
113    {
114        let raw: Option<Vec<u8>> = redis::cmd("GET").arg(key).query(&mut self.inner)?;
115        raw.map(|bytes| Self::decode_json_bytes(&bytes)).transpose()
116    }
117
118    pub fn mset<'a, K, V, I>(&mut self, items: I) -> Result<(), VldRedisError>
119    where
120        I: IntoIterator<Item = (K, &'a V)>,
121        K: redis::ToRedisArgs,
122        V: serde::Serialize + vld::schema::VldParse + 'a,
123    {
124        let mut cmd = redis::cmd("MSET");
125        for (key, value) in items {
126            let encoded = Self::encode_json_string(value)?;
127            cmd.arg(key).arg(encoded);
128        }
129        cmd.query::<()>(&mut self.inner)?;
130        Ok(())
131    }
132
133    pub fn mget<K, T, I>(&mut self, keys: I) -> Result<Vec<Option<T>>, VldRedisError>
134    where
135        I: IntoIterator<Item = K>,
136        K: redis::ToRedisArgs,
137        T: vld::schema::VldParse,
138    {
139        let mut cmd = redis::cmd("MGET");
140        for key in keys {
141            cmd.arg(key);
142        }
143        let raw: Vec<Option<Vec<u8>>> = cmd.query(&mut self.inner)?;
144        raw.into_iter()
145            .map(|opt| opt.map(|bytes| Self::decode_json_bytes(&bytes)).transpose())
146            .collect()
147    }
148
149    pub fn hset<K, F, V>(&mut self, key: K, field: F, value: &V) -> Result<(), VldRedisError>
150    where
151        K: redis::ToRedisArgs,
152        F: redis::ToRedisArgs,
153        V: serde::Serialize + vld::schema::VldParse,
154    {
155        let s = Self::encode_json_string(value)?;
156        redis::cmd("HSET")
157            .arg(key)
158            .arg(field)
159            .arg(s)
160            .query::<()>(&mut self.inner)?;
161        Ok(())
162    }
163
164    pub fn hget<K, F, T>(&mut self, key: K, field: F) -> Result<Option<T>, VldRedisError>
165    where
166        K: redis::ToRedisArgs,
167        F: redis::ToRedisArgs,
168        T: vld::schema::VldParse,
169    {
170        let raw: Option<Vec<u8>> = redis::cmd("HGET")
171            .arg(key)
172            .arg(field)
173            .query(&mut self.inner)?;
174        raw.map(|bytes| Self::decode_json_bytes(&bytes)).transpose()
175    }
176
177    pub fn lpush<K, V>(&mut self, key: K, value: &V) -> Result<usize, VldRedisError>
178    where
179        K: redis::ToRedisArgs,
180        V: serde::Serialize + vld::schema::VldParse,
181    {
182        let payload = Self::encode_json_string(value)?;
183        let len: usize = redis::cmd("LPUSH")
184            .arg(key)
185            .arg(payload)
186            .query(&mut self.inner)?;
187        Ok(len)
188    }
189
190    pub fn rpush<K, V>(&mut self, key: K, value: &V) -> Result<usize, VldRedisError>
191    where
192        K: redis::ToRedisArgs,
193        V: serde::Serialize + vld::schema::VldParse,
194    {
195        let payload = Self::encode_json_string(value)?;
196        let len: usize = redis::cmd("RPUSH")
197            .arg(key)
198            .arg(payload)
199            .query(&mut self.inner)?;
200        Ok(len)
201    }
202
203    pub fn lpop<K, T>(&mut self, key: K) -> Result<Option<T>, VldRedisError>
204    where
205        K: redis::ToRedisArgs,
206        T: vld::schema::VldParse,
207    {
208        let raw: Option<Vec<u8>> = redis::cmd("LPOP").arg(key).query(&mut self.inner)?;
209        raw.map(|bytes| Self::decode_json_bytes(&bytes)).transpose()
210    }
211
212    pub fn rpop<K, T>(&mut self, key: K) -> Result<Option<T>, VldRedisError>
213    where
214        K: redis::ToRedisArgs,
215        T: vld::schema::VldParse,
216    {
217        let raw: Option<Vec<u8>> = redis::cmd("RPOP").arg(key).query(&mut self.inner)?;
218        raw.map(|bytes| Self::decode_json_bytes(&bytes)).transpose()
219    }
220
221    pub fn sadd<K, V>(&mut self, key: K, value: &V) -> Result<usize, VldRedisError>
222    where
223        K: redis::ToRedisArgs,
224        V: serde::Serialize + vld::schema::VldParse,
225    {
226        let payload = Self::encode_json_string(value)?;
227        let added: usize = redis::cmd("SADD")
228            .arg(key)
229            .arg(payload)
230            .query(&mut self.inner)?;
231        Ok(added)
232    }
233
234    pub fn smembers<K, T>(&mut self, key: K) -> Result<Vec<T>, VldRedisError>
235    where
236        K: redis::ToRedisArgs,
237        T: vld::schema::VldParse,
238    {
239        let raw: Vec<Vec<u8>> = redis::cmd("SMEMBERS").arg(key).query(&mut self.inner)?;
240        raw.into_iter()
241            .map(|bytes| Self::decode_json_bytes(&bytes))
242            .collect()
243    }
244
245    pub fn zadd<K, V>(&mut self, key: K, score: f64, value: &V) -> Result<usize, VldRedisError>
246    where
247        K: redis::ToRedisArgs,
248        V: serde::Serialize + vld::schema::VldParse,
249    {
250        let payload = Self::encode_json_string(value)?;
251        let added: usize = redis::cmd("ZADD")
252            .arg(key)
253            .arg(score)
254            .arg(payload)
255            .query(&mut self.inner)?;
256        Ok(added)
257    }
258
259    pub fn zrange<K, T>(
260        &mut self,
261        key: K,
262        start: isize,
263        stop: isize,
264    ) -> Result<Vec<T>, VldRedisError>
265    where
266        K: redis::ToRedisArgs,
267        T: vld::schema::VldParse,
268    {
269        let raw: Vec<Vec<u8>> = redis::cmd("ZRANGE")
270            .arg(key)
271            .arg(start)
272            .arg(stop)
273            .query(&mut self.inner)?;
274        raw.into_iter()
275            .map(|bytes| Self::decode_json_bytes(&bytes))
276            .collect()
277    }
278
279    pub fn publish<Cn, V>(&mut self, channel: Cn, value: &V) -> Result<i64, VldRedisError>
280    where
281        Cn: redis::ToRedisArgs,
282        V: serde::Serialize + vld::schema::VldParse,
283    {
284        let payload = Self::encode_json_bytes(value)?;
285        let delivered: i64 = redis::cmd("PUBLISH")
286            .arg(channel)
287            .arg(payload)
288            .query(&mut self.inner)?;
289        Ok(delivered)
290    }
291}
292
293#[derive(Debug)]
294pub enum VldRedisError {
295    Validation(vld::error::VldError),
296    Serialization(String),
297    Deserialization(String),
298    Redis(redis::RedisError),
299}
300
301impl fmt::Display for VldRedisError {
302    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
303        match self {
304            VldRedisError::Validation(e) => write!(f, "Validation error: {e}"),
305            VldRedisError::Serialization(e) => write!(f, "Serialization error: {e}"),
306            VldRedisError::Deserialization(e) => write!(f, "Deserialization error: {e}"),
307            VldRedisError::Redis(e) => write!(f, "Redis error: {e}"),
308        }
309    }
310}
311
312impl std::error::Error for VldRedisError {
313    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
314        match self {
315            VldRedisError::Validation(e) => Some(e),
316            VldRedisError::Redis(e) => Some(e),
317            VldRedisError::Serialization(_) | VldRedisError::Deserialization(_) => None,
318        }
319    }
320}
321
322impl From<vld::error::VldError> for VldRedisError {
323    fn from(value: vld::error::VldError) -> Self {
324        Self::Validation(value)
325    }
326}
327
328impl From<redis::RedisError> for VldRedisError {
329    fn from(value: redis::RedisError) -> Self {
330        Self::Redis(value)
331    }
332}
333
334/// Rebind Redis connection into `vld`-aware connection with native-like calls.
335#[macro_export]
336macro_rules! impl_to_redis {
337    ($conn:ident) => {
338        let mut $conn = $crate::RedisConn::new($conn);
339    };
340}
341
342pub mod prelude {
343    pub use crate::VldRedisError;
344    pub use crate::{impl_to_redis, RedisConn};
345    pub use vld::prelude::*;
346}