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
90        .as_ref()
91        .map(|w| quote! { #w })
92        .unwrap_or(quote! {});
93
94    let failed_parse_error = quote! {
95        Err(format!("Response type not deserializable to {} with {}. (response was {:?})", #ident_str, #serializer_str, v).into())
96    };
97
98    // If the parsing failed, the issue might simply be that the user is using a RedisJSON command
99    // RedisJSON commands wrap the response into square brackets for some godforsaken reason
100    // We can try removing the brackets and try the parse again
101    let redis_json_hack = quote! {
102        let mut ch = s.chars();
103        if ch.next() == Some('[') && ch.next_back() == Some(']') {
104            if let Ok(s) = #serializer::from_str(ch.as_str()) {
105                Ok(s)
106            } else {
107                Err(format!("Response type not RedisJSON deserializable to {}. (response was {:?})", #ident_str, v).into())
108            }
109        } else {
110            #failed_parse_error
111        }
112    };
113
114    // The Redis JSON hack only relevant if we are using serde_json
115    let failed_parse = if serializer_str == "serde_json" {
116        redis_json_hack
117    } else {
118        failed_parse_error
119    };
120
121    quote! {
122        impl #impl_generics redis::FromRedisValue for #ident #ty_generics #where_with_serialize {
123            fn from_redis_value(v: redis::Value) -> Result<Self, redis::ParsingError> {
124                match v {
125                    redis::Value::BulkString(ref bytes) => {
126                        if let Ok(s) = std::str::from_utf8(bytes) {
127                            if let Ok(s) = #serializer::from_str(s) {
128                                Ok(s)
129                            } else {
130                                #failed_parse
131                            }
132                        } else {
133                            Err(format!("Response was not valid UTF-8 string. (response was {:?})", v).into())
134                        }
135                    },
136                    _ => Err(format!("Response type was not deserializable to {}. (response was {:?})", #ident_str, v).into()),
137                }
138            }
139        }
140    }
141    .into()
142}
143
144/// Derive macro for the redis crate's [`ToRedisArgs`](../redis/trait.ToRedisArgs.html) trait to allow passing the type to Redis commands.
145///
146/// *NOTE: This trait requires serde's [`Serialize`](../serde/trait.Serialize.html) to also be derived (or implemented).*
147///
148/// ***WARNING: This trait panics if the underlying serialization fails.***
149///
150/// Simply use the `#[derive(ToRedisArgs, Serialize)]` before any structs (or serializable elements).
151/// This allows to pass this type to Redis commands like SET. The type will be serialized into JSON automatically while saving to Redis.
152///
153/// ```rust,no_run
154/// # use redis::{Client, Commands, RedisResult};
155/// use redis_macros::{ToRedisArgs};
156/// use serde::{Serialize};
157///
158/// #[derive(ToRedisArgs, Serialize)]
159/// struct User { id: u32 }
160///  
161/// # fn main () -> redis::RedisResult<()> {
162/// # let client = redis::Client::open("redis://localhost:6379/")?;
163/// # let mut con = client.get_connection()?;
164/// con.set("user", User { id: 1 })?;
165/// let user: String = con.get("user")?;  // => "{ \"id\": 1 }"
166/// # Ok(())
167/// # }
168/// ```
169///
170/// If you want to use a different serde format, for example `serde_yaml`, you can set this with the `redis_serializer` attribute.
171/// The only restriciton is to have the serializer implement the `to_string` function.
172///
173/// ```rust,no_run
174/// # use redis::{Client, Commands, RedisResult};
175/// use redis_macros::{ToRedisArgs};
176/// use serde::{Serialize};
177///
178/// #[derive(ToRedisArgs, Serialize)]
179/// #[redis_serializer(serde_yaml)]
180/// struct User { id: u32 }
181/// ```
182///
183/// For more information see the isomorphic pair of this trait: [FromRedisValue].
184#[proc_macro_derive(ToRedisArgs, attributes(redis_serializer))]
185pub fn to_redis_args_macro(input: TokenStream) -> TokenStream {
186    let DeriveInput {
187        ident,
188        attrs,
189        generics,
190        ..
191    } = parse_macro_input!(input as DeriveInput);
192    let serializer = get_serializer(attrs, "serde_json");
193
194    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
195
196    let mut where_clause_extended = where_clause.cloned();
197
198    // Add serde constraints for each type parameter
199    for param in &generics.params {
200        if let GenericParam::Type(type_param) = param {
201            let ident = &type_param.ident;
202            let constraint = syn::parse_quote! { #ident : serde::Serialize };
203
204            if let Some(ref mut w) = where_clause_extended {
205                w.predicates.push(constraint);
206            } else {
207                where_clause_extended = Some(syn::parse_quote! { where #constraint });
208            }
209        }
210    }
211
212    let where_with_serialize = where_clause_extended
213        .as_ref()
214        .map(|w| quote! { #w })
215        .unwrap_or(quote! {});
216
217    quote! {
218        impl #impl_generics redis::ToRedisArgs for #ident #ty_generics #where_with_serialize {
219            fn write_redis_args<W>(&self, out: &mut W)
220            where
221                W: ?Sized + redis::RedisWrite,
222            {
223                let buf = #serializer::to_string(&self).unwrap();
224                out.write_arg(&buf.as_bytes())
225            }
226        }
227
228        impl #impl_generics redis::ToSingleRedisArg for #ident #ty_generics #where_with_serialize {}
229    }
230    .into()
231}