Skip to main content

from_pg_derive/
lib.rs

1use proc_macro::TokenStream;
2use quote::{format_ident, quote};
3use syn::{
4    Data, DeriveInput, ExprPath, Field, Fields, Ident, Token, Type, parse::ParseStream,
5    parse_macro_input, spanned::Spanned,
6};
7
8const MACRO_NAME: &'static str = "frompg";
9
10/// For a type using the frompg macro, you can write `#[frompg(from = T, func = F)] above a field`.
11/// This retrieves any valid attributes from a field, and can return an error.
12#[proc_macro_derive(FromPg, attributes(frompg))]
13pub fn frompg(_item: TokenStream) -> TokenStream {
14    let input = parse_macro_input!(_item as DeriveInput);
15
16    let item_name = input.ident.clone();
17
18    let new_struct = match input.data {
19        Data::Struct(s) => from_pg_helper(&item_name, &s.fields),
20        _ => Err(syn::Error::new(
21            item_name.span(),
22            "frompg only supports structs",
23        )),
24    }
25    .unwrap_or_else(|e| e.into_compile_error());
26
27    new_struct.into()
28}
29
30fn from_pg_helper(
31    item_name: &Ident,
32    item_fields: &Fields,
33) -> syn::Result<proc_macro2::TokenStream> {
34    let conf_name = format_ident!("{}Config", item_name.clone());
35    let err_name = format_ident!("{}Error", item_name.clone());
36
37    if item_fields.is_empty() {
38        return Ok(quote! {
39            impl FromPg for #item_name {
40                type Config = ();
41                type Error  = ::std::convert::Infallible;
42                fn from_pg(
43                    _: &::tokio_postgres::row::Row,
44                    _: &()
45                ) -> Result<Self, Self::Error> {
46                    Ok(Self {})
47                }
48            }
49        });
50    }
51
52    let fields = map_fields(item_fields, |s| s.to_string())?;
53
54    let field_infos: Vec<_> = item_fields
55        .iter()
56        .map(|field| {
57            let fd = FieldDeserializer::from(field)?;
58            let field_name = field
59                .ident
60                .as_ref()
61                .ok_or(syn::Error::new(field.span(), "fields must be named"))?;
62
63            Ok(match fd {
64                Some(FieldDeserializer::Custom { ty, func }) => FieldInfo::Custom {
65                    name: field_name.clone(),
66                    ty,
67                    func,
68                },
69                Some(FieldDeserializer::Derive) => {
70                    let (derived_ty, is_option) = extract_option_inner_type(&field.ty);
71                    FieldInfo::Derive {
72                        name: field_name.clone(),
73                        ty: derived_ty,
74                        is_option,
75                    }
76                }
77                _ => FieldInfo::Default {
78                    name: field_name.clone(),
79                },
80            })
81        })
82        .collect::<syn::Result<Vec<_>>>()?;
83
84    let field_deserializations = field_infos
85        .iter()
86        .map(|field_info| {
87            match field_info {
88                FieldInfo::Custom { name, ty, func } => quote! {
89                    row
90                        .try_get::<_,#ty>(conf.#name.as_str())
91                        .map_err(|e| Box::new(e) as Box<(dyn std::error::Error + Send + Sync)>)
92                        .and_then(|val| #func(val).map_err(|e| e.into()))
93                },
94                FieldInfo::Derive { name, ty, is_option } => {
95                    if *is_option {
96                        quote! {
97                            {
98                                // Helper type to detect null values without casting to a specific type
99                                // It always succeeds for non-null values, and always fails for null/missing
100                                struct AnySql;
101                                impl tokio_postgres::types::FromSql<'_> for AnySql {
102                                    fn from_sql(
103                                        _type_: &tokio_postgres::types::Type,
104                                        _raw: &[u8]
105                                    ) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
106                                        Ok(AnySql)
107                                    }
108                                    
109                                    fn accepts(_type_: &tokio_postgres::types::Type) -> bool {
110                                        true
111                                    }
112                                }
113                                
114                                let has_non_null_fields = conf.#name.fields().iter().any(|&field_name| {
115                                    row.try_get::<_, AnySql>(field_name).is_ok()
116                                });
117                                
118                                if has_non_null_fields {
119                                    match <#ty as FromPg>::from_pg(row, &conf.#name) {
120                                        Ok(val) => Ok(Some(val)),
121                                        Err(e) => Err(Box::new(e) as Box<(dyn std::error::Error + Send + Sync)>)
122                                    }
123                                } else {
124                                    Ok(None)
125                                }
126                            }
127                        }
128                    } else {
129                        quote! {
130                            <#ty as FromPg>::from_pg(row, &conf.#name)
131                                .map_err(|e| Box::new(e) as Box<(dyn std::error::Error + Send + Sync)>)
132                        }
133                    }
134                },
135                FieldInfo::Default { name, .. } => quote! {
136                    row
137                        .try_get(conf.#name.as_str())
138                        .map_err(|e| Box::new(e) as Box<(dyn std::error::Error + Send + Sync)>)
139                },
140            }
141        })
142        .collect::<Vec<_>>();
143
144    let config_fields = field_infos
145        .iter()
146        .map(|field_info| {
147            match field_info {
148                FieldInfo::Custom { name, .. } => quote! {
149                    #name: String
150                },
151                FieldInfo::Derive { name, ty, .. } => quote! {
152                    #name: <#ty as FromPg>::Config
153                },
154                FieldInfo::Default { name, .. } => quote! {
155                    #name: String
156                },
157            }
158        })
159        .collect::<Vec<_>>();
160
161    let default_fields = field_infos
162        .iter()
163        .map(|field_info| {
164            match field_info {
165                FieldInfo::Custom { name, .. } => quote! {
166                    #name: String::from(stringify!(#name))
167                },
168                FieldInfo::Derive { name, ty, .. } => quote! {
169                    #name: <#ty as FromPg>::Config::default()
170                },
171                FieldInfo::Default { name, .. } => quote! {
172                    #name: String::from(stringify!(#name))
173                },
174            }
175        })
176        .collect::<Vec<_>>();
177
178    let getter_methods = field_infos
179        .iter()
180        .map(|field_info| {
181            match field_info {
182                FieldInfo::Custom { name, .. } => quote! {
183                    pub fn #name(&self) -> &str {
184                        self.#name.as_ref()
185                    }
186                },
187                FieldInfo::Derive { name, ty, .. } => quote! {
188                    pub fn #name(&self) -> &<#ty as FromPg>::Config {
189                        &self.#name
190                    }
191                },
192                FieldInfo::Default { name, .. } => quote! {
193                    pub fn #name(&self) -> &str {
194                        self.#name.as_ref()
195                    }
196                },
197            }
198        })
199        .collect::<Vec<_>>();
200
201    // only include non-derived fields
202    let fields_method = {
203        let field_getters: Vec<_> = field_infos
204            .iter()
205            .filter_map(|field_info| match field_info {
206                FieldInfo::Custom { name, .. } => Some(quote! { self.#name() }),
207                FieldInfo::Default { name, .. } => Some(quote! { self.#name() }),
208                FieldInfo::Derive { .. } => None, // Exclude derived fields
209            })
210            .collect();
211
212        quote! {
213            pub fn fields(&self) -> Vec<&str> {
214                vec![#( #field_getters ),*]
215            }
216        }
217    };
218
219    let setter_methods = field_infos
220        .iter()
221        .map(|field_info| {
222            match field_info {
223                FieldInfo::Custom { name, .. } => {
224                    let set_method = format_ident!("set_{}", name);
225                    quote! {
226                        pub fn #set_method(self, name: String) -> Self {
227                            Self {
228                                #name: name,
229                                ..self
230                            }
231                        }
232                    }
233                }
234                FieldInfo::Derive { name, ty, .. } => {
235                    let set_method = format_ident!("set_{}", name);
236                    quote! {
237                        pub fn #set_method(self, config: <#ty as FromPg>::Config) -> Self {
238                            Self {
239                                #name: config,
240                                ..self
241                            }
242                        }
243                    }
244                }
245                FieldInfo::Default { name, .. } => {
246                    let set_method = format_ident!("set_{}", name);
247                    quote! {
248                        pub fn #set_method(self, name: String) -> Self {
249                            Self {
250                                #name: name,
251                                ..self
252                            }
253                        }
254                    }
255                }
256            }
257        })
258        .collect::<Vec<_>>();
259
260    let prefix_methods = field_infos
261        .iter()
262        .filter_map(|field_info| {
263            match field_info {
264                FieldInfo::Custom { name, .. } => {
265                    let prefix_method = format_ident!("prefix_{}", name);
266                    Some(quote! {
267                        pub fn #prefix_method(self, prefix: String) -> Self {
268                            let mut name = prefix.clone();
269                            name.push_str(stringify!(#name));
270                            Self {
271                                #name: name,
272                                ..self
273                            }
274                        }
275                    })
276                }
277                FieldInfo::Derive { .. } => {
278                    None
279                }
280                FieldInfo::Default { name, .. } => {
281                    let prefix_method = format_ident!("prefix_{}", name);
282                    Some(quote! {
283                        pub fn #prefix_method(self, prefix: String) -> Self {
284                            let mut name = prefix.clone();
285                            name.push_str(stringify!(#name));
286                            Self {
287                                #name: name,
288                                ..self
289                            }
290                        }
291                    })
292                }
293            }
294        }).collect::<Vec<_>>();
295
296    Ok(quote! {
297        #[derive(Clone, Debug, Eq, PartialEq)]
298        pub struct #conf_name {
299            #( #config_fields ),*
300        }
301
302        impl ::std::default::Default for #conf_name {
303            fn default() -> Self {
304                Self {
305                    #( #default_fields ),*
306                }
307            }
308        }
309
310        impl #conf_name {
311            pub fn new() -> Self {
312                Self::default()
313            }
314
315            #( #getter_methods )*
316
317            #( #setter_methods )*
318
319            #( #prefix_methods )*
320
321            #fields_method
322        }
323
324        #[derive(Debug, Default)]
325        pub struct #err_name {
326            #(
327                #fields: Option<Box<dyn ::std::error::Error + ::core::marker::Sync + ::core::marker::Send>>
328            ),*
329        }
330
331        impl ::std::fmt::Display for #err_name {
332            fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
333                let mut messages = Vec::new();
334                #(
335                    if let Some(ref err) = self.#fields {
336                        messages.push(format!("{}: {}", stringify!(#fields), err));
337                    }
338                )*
339                write!(f, "{}", messages.join("; "))
340            }
341        }
342
343        impl ::std::error::Error for #err_name {}
344
345        impl FromPg for #item_name {
346            type Config = #conf_name;
347            type Error = #err_name;
348
349            fn from_pg(row: &::tokio_postgres::row::Row, conf: &Self::Config) -> Result<Self, Self::Error> {
350                match ( #( #field_deserializations ),* ) {
351                    ( #( Ok(#fields) ),* ) => Ok(Self {
352                        #( #fields ),*
353                    }),
354                    ( #( #fields ),* ) => Err(Self::Error {
355                        #( #fields: #fields.err() ),*
356                    })
357                }
358            }
359        }
360    })
361}
362
363enum FieldInfo {
364    Custom {
365        name: Ident,
366        ty: Type,
367        func: ExprPath,
368    },
369    Derive {
370        name: Ident,
371        ty: Type,
372        is_option: bool,
373    },
374    Default {
375        name: Ident,
376    },
377}
378
379/// Extracts the inner type of an Option<T> and returns (Type, bool) where bool indicates if it was an Option
380fn extract_option_inner_type(ty: &Type) -> (Type, bool) {
381    if let Type::Path(type_path) = ty {
382        if let Some(segment) = type_path.path.segments.last() {
383            if segment.ident == "Option" {
384                if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
385                    if let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first() {
386                        return (inner_ty.clone(), true);
387                    }
388                }
389            }
390        }
391    }
392    (ty.clone(), false)
393}
394
395enum FieldDeserializer {
396    Custom { ty: Type, func: ExprPath },
397    Derive,
398}
399
400impl FieldDeserializer {
401    fn from(field: &Field) -> syn::Result<Option<Self>> {
402        field
403            .attrs
404            .iter()
405            .find(|attr| attr.path().is_ident(MACRO_NAME))
406            .map(|attr| {
407                Ok(attr
408                    .meta
409                    .require_list()?
410                    .parse_args::<FieldDeserializer>()?)
411            })
412            .transpose()
413    }
414}
415
416/// For a type using the frompg macro, you can write `#[frompg(from = T, func = F)] above a field`.
417/// This retrieves any valid attributes from a field, and can return an error.
418/// Alternatively, you can write `#[frompg(derive = T)]` to use FromPg instead of FromSql.
419impl syn::parse::Parse for FieldDeserializer {
420    fn parse(input: ParseStream) -> syn::Result<Self> {
421        let first_token: Ident = input.parse()?;
422
423        match first_token.to_string().as_str() {
424            "from" => {
425                input.parse::<Token![=]>()?;
426                let ty: Type = input.parse()?;
427                input.parse::<Token![,]>()?;
428                let func_token: Ident = input.parse()?;
429                if func_token != "func" {
430                    return Err(syn::Error::new(func_token.span(), "expected `func`"));
431                }
432                input.parse::<Token![=]>()?;
433                let func: ExprPath = input.parse()?;
434                Ok(Self::Custom { ty, func })
435            }
436            "derive" => {
437                input.parse::<Token![=]>()?;
438                let _ty: Type = input.parse()?;
439                Ok(Self::Derive)
440            }
441            _ => Err(syn::Error::new(
442                first_token.span(),
443                "expected `from` or `derive`",
444            )),
445        }
446    }
447}
448
449/// Maps the fields of a DataStruct using the given function.
450/// Returns `None` if any fields are `None`.
451fn map_fields(fields: &Fields, f: impl Fn(&str) -> String) -> syn::Result<Vec<Ident>> {
452    fields
453        .iter()
454        .map(|field| {
455            Ok(Ident::new(
456                &f(&field
457                    .ident
458                    .as_ref()
459                    .ok_or(syn::Error::new(fields.span(), "fields must be named"))?
460                    .to_string()),
461                field.ident.span(),
462            ))
463        })
464        .collect()
465}