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 + ?Sized,
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 + ?Sized,
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 + ?Sized,
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 + ?Sized,
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))
116        .transpose()
117    }
118
119    pub fn mset<'a, K, V, I>(&mut self, items: I) -> Result<(), VldRedisError>
120    where
121        I: IntoIterator<Item = (K, &'a V)>,
122        K: redis::ToRedisArgs,
123        V: serde::Serialize + vld::schema::VldParse + ?Sized + 'a,
124    {
125        let mut cmd = redis::cmd("MSET");
126        for (key, value) in items {
127            let encoded = Self::encode_json_string(value)?;
128            cmd.arg(key).arg(encoded);
129        }
130        cmd.query::<()>(&mut self.inner)?;
131        Ok(())
132    }
133
134    pub fn mget<K, T, I>(&mut self, keys: I) -> Result<Vec<Option<T>>, VldRedisError>
135    where
136        I: IntoIterator<Item = K>,
137        K: redis::ToRedisArgs,
138        T: vld::schema::VldParse,
139    {
140        let mut cmd = redis::cmd("MGET");
141        for key in keys {
142            cmd.arg(key);
143        }
144        let raw: Vec<Option<Vec<u8>>> = cmd.query(&mut self.inner)?;
145        raw.into_iter()
146            .map(|opt| opt.map(|bytes| Self::decode_json_bytes(&bytes)).transpose())
147            .collect()
148    }
149
150    pub fn hset<K, F, V>(&mut self, key: K, field: F, value: &V) -> Result<(), VldRedisError>
151    where
152        K: redis::ToRedisArgs,
153        F: redis::ToRedisArgs,
154        V: serde::Serialize + vld::schema::VldParse + ?Sized,
155    {
156        let s = Self::encode_json_string(value)?;
157        redis::cmd("HSET")
158            .arg(key)
159            .arg(field)
160            .arg(s)
161            .query::<()>(&mut self.inner)?;
162        Ok(())
163    }
164
165    pub fn hget<K, F, T>(&mut self, key: K, field: F) -> Result<Option<T>, VldRedisError>
166    where
167        K: redis::ToRedisArgs,
168        F: redis::ToRedisArgs,
169        T: vld::schema::VldParse,
170    {
171        let raw: Option<Vec<u8>> = redis::cmd("HGET")
172            .arg(key)
173            .arg(field)
174            .query(&mut self.inner)?;
175        raw.map(|bytes| Self::decode_json_bytes(&bytes))
176        .transpose()
177    }
178
179    pub fn lpush<K, V>(&mut self, key: K, value: &V) -> Result<usize, VldRedisError>
180    where
181        K: redis::ToRedisArgs,
182        V: serde::Serialize + vld::schema::VldParse + ?Sized,
183    {
184        let payload = Self::encode_json_string(value)?;
185        let len: usize = redis::cmd("LPUSH").arg(key).arg(payload).query(&mut self.inner)?;
186        Ok(len)
187    }
188
189    pub fn rpush<K, V>(&mut self, key: K, value: &V) -> Result<usize, VldRedisError>
190    where
191        K: redis::ToRedisArgs,
192        V: serde::Serialize + vld::schema::VldParse + ?Sized,
193    {
194        let payload = Self::encode_json_string(value)?;
195        let len: usize = redis::cmd("RPUSH").arg(key).arg(payload).query(&mut self.inner)?;
196        Ok(len)
197    }
198
199    pub fn lpop<K, T>(&mut self, key: K) -> Result<Option<T>, VldRedisError>
200    where
201        K: redis::ToRedisArgs,
202        T: vld::schema::VldParse,
203    {
204        let raw: Option<Vec<u8>> = redis::cmd("LPOP").arg(key).query(&mut self.inner)?;
205        raw.map(|bytes| Self::decode_json_bytes(&bytes)).transpose()
206    }
207
208    pub fn rpop<K, T>(&mut self, key: K) -> Result<Option<T>, VldRedisError>
209    where
210        K: redis::ToRedisArgs,
211        T: vld::schema::VldParse,
212    {
213        let raw: Option<Vec<u8>> = redis::cmd("RPOP").arg(key).query(&mut self.inner)?;
214        raw.map(|bytes| Self::decode_json_bytes(&bytes)).transpose()
215    }
216
217    pub fn sadd<K, V>(&mut self, key: K, value: &V) -> Result<usize, VldRedisError>
218    where
219        K: redis::ToRedisArgs,
220        V: serde::Serialize + vld::schema::VldParse + ?Sized,
221    {
222        let payload = Self::encode_json_string(value)?;
223        let added: usize = redis::cmd("SADD").arg(key).arg(payload).query(&mut self.inner)?;
224        Ok(added)
225    }
226
227    pub fn smembers<K, T>(&mut self, key: K) -> Result<Vec<T>, VldRedisError>
228    where
229        K: redis::ToRedisArgs,
230        T: vld::schema::VldParse,
231    {
232        let raw: Vec<Vec<u8>> = redis::cmd("SMEMBERS").arg(key).query(&mut self.inner)?;
233        raw.into_iter()
234            .map(|bytes| Self::decode_json_bytes(&bytes))
235            .collect()
236    }
237
238    pub fn zadd<K, V>(&mut self, key: K, score: f64, value: &V) -> Result<usize, VldRedisError>
239    where
240        K: redis::ToRedisArgs,
241        V: serde::Serialize + vld::schema::VldParse + ?Sized,
242    {
243        let payload = Self::encode_json_string(value)?;
244        let added: usize = redis::cmd("ZADD")
245            .arg(key)
246            .arg(score)
247            .arg(payload)
248            .query(&mut self.inner)?;
249        Ok(added)
250    }
251
252    pub fn zrange<K, T>(&mut self, key: K, start: isize, stop: isize) -> Result<Vec<T>, VldRedisError>
253    where
254        K: redis::ToRedisArgs,
255        T: vld::schema::VldParse,
256    {
257        let raw: Vec<Vec<u8>> = redis::cmd("ZRANGE")
258            .arg(key)
259            .arg(start)
260            .arg(stop)
261            .query(&mut self.inner)?;
262        raw.into_iter()
263            .map(|bytes| Self::decode_json_bytes(&bytes))
264            .collect()
265    }
266
267    pub fn publish<Cn, V>(&mut self, channel: Cn, value: &V) -> Result<i64, VldRedisError>
268    where
269        Cn: redis::ToRedisArgs,
270        V: serde::Serialize + vld::schema::VldParse + ?Sized,
271    {
272        let payload = Self::encode_json_bytes(value)?;
273        let delivered: i64 = redis::cmd("PUBLISH")
274            .arg(channel)
275            .arg(payload)
276            .query(&mut self.inner)?;
277        Ok(delivered)
278    }
279}
280
281#[derive(Debug)]
282pub enum VldRedisError {
283    Validation(vld::error::VldError),
284    Serialization(String),
285    Deserialization(String),
286    Redis(redis::RedisError),
287}
288
289impl fmt::Display for VldRedisError {
290    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
291        match self {
292            VldRedisError::Validation(e) => write!(f, "Validation error: {e}"),
293            VldRedisError::Serialization(e) => write!(f, "Serialization error: {e}"),
294            VldRedisError::Deserialization(e) => write!(f, "Deserialization error: {e}"),
295            VldRedisError::Redis(e) => write!(f, "Redis error: {e}"),
296        }
297    }
298}
299
300impl std::error::Error for VldRedisError {
301    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
302        match self {
303            VldRedisError::Validation(e) => Some(e),
304            VldRedisError::Redis(e) => Some(e),
305            VldRedisError::Serialization(_) | VldRedisError::Deserialization(_) => None,
306        }
307    }
308}
309
310impl From<vld::error::VldError> for VldRedisError {
311    fn from(value: vld::error::VldError) -> Self {
312        Self::Validation(value)
313    }
314}
315
316impl From<redis::RedisError> for VldRedisError {
317    fn from(value: redis::RedisError) -> Self {
318        Self::Redis(value)
319    }
320}
321
322/// Rebind Redis connection into `vld`-aware connection with native-like calls.
323#[macro_export]
324macro_rules! impl_to_redis {
325    ($conn:ident) => {
326        let mut $conn = $crate::RedisConn::new($conn);
327    };
328}
329
330pub mod prelude {
331    pub use crate::VldRedisError;
332    pub use crate::{impl_to_redis, RedisConn};
333    pub use vld::prelude::*;
334}