redis_macros_derive/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::TokenStream as TokenStream2;
3use quote::{quote, ToTokens};
4use syn::{parse_macro_input, Attribute, DeriveInput, Expr, GenericParam};
5
6fn get_serializer(attrs: Vec<Attribute>, default: &str) -> TokenStream2 {
7    let default_token = default.parse::<TokenStream2>().unwrap();
8
9    attrs
10        .into_iter()
11        .find(|attr| attr.path().is_ident("redis_serializer"))
12        .and_then(|attr| {
13            let Ok(Expr::Path(path)) = attr.parse_args::<Expr>() else {
14                return None;
15            };
16
17            Some(path.to_token_stream())
18        })
19        .unwrap_or(default_token)
20}
21
22/// Derive macro for the redis crate's [`FromRedisValue`](../redis/trait.FromRedisValue.html) trait to allow parsing Redis responses to this type.
23///
24/// *NOTE: This trait requires serde's [`Deserialize`](../serde/trait.Deserialize.html) to also be derived (or implemented).*
25///
26/// Simply use the `#[derive(FromRedisValue, Deserialize)]` before any structs (or serializable elements).
27/// This allows, when using Redis commands, to set this as the return type and deserialize from JSON automatically, while reading from Redis.
28///
29/// ```rust,no_run
30/// # use redis::{Client, Commands, RedisResult};
31/// use redis_macros::{FromRedisValue};
32/// use serde::{Deserialize};
33///
34/// #[derive(FromRedisValue, Deserialize)]
35/// struct User { id: u32 }
36///  
37/// # fn main () -> redis::RedisResult<()> {
38/// # let client = redis::Client::open("redis://localhost:6379/")?;
39/// # let mut con = client.get_connection()?;
40/// con.set("user", &r#"{ "id": 1 }"#)?;
41/// let user: User = con.get("user")?;  // => User { id: 1 }
42/// # Ok(())
43/// # }
44/// ```
45///
46/// If you want to use a different serde format, for example `serde_yaml`, you can set this with the `redis_serializer` attribute.
47/// The only restriction is to have the deserializer implement the `from_str` function.
48///
49/// ```rust,no_run
50/// use redis_macros::{FromRedisValue};
51/// use serde::{Deserialize};
52///
53/// #[derive(FromRedisValue, Deserialize)]
54/// #[redis_serializer(serde_yaml)]
55/// struct User { id: u32 }
56/// ```
57///
58/// For more information see the isomorphic pair of this trait: [ToRedisArgs].
59#[proc_macro_derive(FromRedisValue, attributes(redis_serializer))]
60pub fn from_redis_value_macro(input: TokenStream) -> TokenStream {
61    let DeriveInput {
62        ident,
63        attrs,
64        generics,
65        ..
66    } = parse_macro_input!(input as DeriveInput);
67    let serializer = get_serializer(attrs, "serde_json");
68    let ident_str = format!("{}", ident);
69    let serializer_str = format!("{}", serializer);
70
71    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
72
73    let mut where_clause_extended = where_clause.cloned();
74
75    // Add serde constraints for each type parameter
76    for param in &generics.params {
77        if let GenericParam::Type(type_param) = param {
78            let ident = &type_param.ident;
79            let constraint = syn::parse_quote! { #ident : serde::de::DeserializeOwned };
80
81            if let Some(ref mut w) = where_clause_extended {
82                w.predicates.push(constraint);
83            } else {
84                where_clause_extended = Some(syn::parse_quote! { where #constraint });
85            }
86        }
87    }
88
89    let where_with_serialize = where_clause_extended.as_ref().map(|w| quote! { #w }).unwrap_or(quote! {});
90
91    let failed_parse_error = quote! {
92        Err(redis::RedisError::from((
93            redis::ErrorKind::TypeError,
94            "Response was of incompatible type",
95            format!("Response type not deserializable to {} with {}. (response was {:?})", #ident_str, #serializer_str, v)
96        )))
97    };
98
99    // If the parsing failed, the issue might simply be that the user is using a RedisJSON command
100    // RedisJSON commands wrap the response into square brackets for some godforesaken reason
101    // We can try removing the brackets and try the parse again
102    let redis_json_hack = quote! {
103        let mut ch = s.chars();
104        if ch.next() == Some('[') && ch.next_back() == Some(']') {
105            if let Ok(s) = #serializer::from_str(ch.as_str()) {
106                Ok(s)
107            } else {
108                Err(redis::RedisError::from((
109                redis::ErrorKind::TypeError,
110                "Response was of incompatible type",
111                format!("Response type not RedisJSON deserializable to {}. (response was {:?})", #ident_str, v)
112            )))
113            }
114        } else {
115            #failed_parse_error
116        }
117    };
118
119    // The Redis JSON hack only relevant if we are using serde_json
120    let failed_parse = if serializer_str == "serde_json" {
121        redis_json_hack
122    } else {
123        failed_parse_error
124    };
125
126    quote! {
127        impl #impl_generics redis::FromRedisValue for #ident #ty_generics #where_with_serialize {
128            fn from_redis_value(v: &redis::Value) -> redis::RedisResult<Self> {
129                match *v {
130                    redis::Value::BulkString(ref bytes) => {
131                        if let Ok(s) = std::str::from_utf8(bytes) {
132                            if let Ok(s) = #serializer::from_str(s) {
133                                Ok(s)
134                            } else {
135                                #failed_parse
136                            }
137                        } else {
138                            Err(redis::RedisError::from((
139                                redis::ErrorKind::TypeError,
140                                "Response was of incompatible type",
141                                format!("Response was not valid UTF-8 string. (response was {:?})", v)
142                            )))
143                        }
144                    },
145                    _ => Err(redis::RedisError::from((
146                        redis::ErrorKind::TypeError,
147                        "Response was of incompatible type",
148                        format!("Response type was not deserializable to {}. (response was {:?})", #ident_str, v)
149                    ))),
150                }
151            }
152        }
153    }
154    .into()
155}
156
157/// Derive macro for the redis crate's [`ToRedisArgs`](../redis/trait.ToRedisArgs.html) trait to allow passing the type to Redis commands.
158///
159/// *NOTE: This trait requires serde's [`Serialize`](../serde/trait.Serialize.html) to also be derived (or implemented).*
160///
161/// ***WARNING: This trait panics if the underlying serialization fails.***
162///
163/// Simply use the `#[derive(ToRedisArgs, Serialize)]` before any structs (or serializable elements).
164/// This allows to pass this type to Redis commands like SET. The type will be serialized into JSON automatically while saving to Redis.
165///
166/// ```rust,no_run
167/// # use redis::{Client, Commands, RedisResult};
168/// use redis_macros::{ToRedisArgs};
169/// use serde::{Serialize};
170///
171/// #[derive(ToRedisArgs, Serialize)]
172/// struct User { id: u32 }
173///  
174/// # fn main () -> redis::RedisResult<()> {
175/// # let client = redis::Client::open("redis://localhost:6379/")?;
176/// # let mut con = client.get_connection()?;
177/// con.set("user", User { id: 1 })?;
178/// let user: String = con.get("user")?;  // => "{ \"id\": 1 }"
179/// # Ok(())
180/// # }
181/// ```
182///
183/// If you want to use a different serde format, for example `serde_yaml`, you can set this with the `redis_serializer` attribute.
184/// The only restriciton is to have the serializer implement the `to_string` function.
185///
186/// ```rust,no_run
187/// # use redis::{Client, Commands, RedisResult};
188/// use redis_macros::{ToRedisArgs};
189/// use serde::{Serialize};
190///
191/// #[derive(ToRedisArgs, Serialize)]
192/// #[redis_serializer(serde_yaml)]
193/// struct User { id: u32 }
194/// ```
195///
196/// For more information see the isomorphic pair of this trait: [FromRedisValue].
197#[proc_macro_derive(ToRedisArgs, attributes(redis_serializer))]
198pub fn to_redis_args_macro(input: TokenStream) -> TokenStream {
199    let DeriveInput {
200        ident,
201        attrs,
202        generics,
203        ..
204    } = parse_macro_input!(input as DeriveInput);
205    let serializer = get_serializer(attrs, "serde_json");
206
207    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
208
209    let mut where_clause_extended = where_clause.cloned();
210
211    // Add serde constraints for each type parameter
212    for param in &generics.params {
213        if let GenericParam::Type(type_param) = param {
214            let ident = &type_param.ident;
215            let constraint = syn::parse_quote! { #ident : serde::Serialize };
216
217            if let Some(ref mut w) = where_clause_extended {
218                w.predicates.push(constraint);
219            } else {
220                where_clause_extended = Some(syn::parse_quote! { where #constraint });
221            }
222        }
223    }
224
225    let where_with_serialize = where_clause_extended.as_ref().map(|w| quote! { #w }).unwrap_or(quote! {});
226
227    quote! {
228        impl #impl_generics redis::ToRedisArgs for #ident #ty_generics #where_with_serialize {
229            fn write_redis_args<W>(&self, out: &mut W)
230            where
231                W: ?Sized + redis::RedisWrite,
232            {
233                let buf = #serializer::to_string(&self).unwrap();
234                out.write_arg(&buf.as_bytes())
235            }
236        }
237    }
238    .into()
239}