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}