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}