envir_derive/
lib.rs

1#![warn(warnings)]
2#![doc = include_str!("../README.md")]
3
4mod attr;
5mod deserialize;
6mod serialize;
7
8#[proc_macro_derive(Deserialize, attributes(envir))]
9pub fn deserialize_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
10    let ast = syn::parse_macro_input!(input);
11
12    deserialize::impl_macro(&ast)
13        .unwrap_or_else(syn::Error::into_compile_error)
14        .into()
15}
16
17#[proc_macro_derive(Serialize, attributes(envir))]
18pub fn serialize_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
19    let ast = syn::parse_macro_input!(input);
20
21    serialize::impl_macro(&ast)
22        .unwrap_or_else(syn::Error::into_compile_error)
23        .into()
24}
25
26pub(crate) fn error<R>(ast: &dyn quote::ToTokens, message: &str) -> syn::Result<R> {
27    Err(syn::Error::new_spanned(ast, message))
28}
29
30pub(crate) fn is_option(ty: &syn::Type) -> bool {
31    is_ty(ty, "Option")
32}
33
34pub(crate) fn is_vec(ty: &syn::Type) -> bool {
35    is_ty(ty, "Vec")
36}
37
38pub(crate) fn is_option_vec(ty: &syn::Type) -> bool {
39    crate::extract_type_from_option(ty)
40        .map(crate::is_vec)
41        .unwrap_or_default()
42}
43
44pub(crate) fn is_ty(ty: &syn::Type, expected: &str) -> bool {
45    let syn::Type::Path(typepath) = ty else {
46        return false;
47    };
48
49    typepath.path.leading_colon.is_none()
50        && typepath.path.segments.len() == 1
51        && typepath
52            .path
53            .segments
54            .iter()
55            .next()
56            .map(|x| x.ident.to_string())
57            == Some(expected.to_string())
58}
59
60// https://stackoverflow.com/questions/55271857/how-can-i-get-the-t-from-an-optiont-when-using-syn
61fn extract_type_from_option(ty: &syn::Type) -> Option<&syn::Type> {
62    use syn::{GenericArgument, Path, PathArguments, PathSegment};
63
64    fn extract_type_path(ty: &syn::Type) -> Option<&Path> {
65        match *ty {
66            syn::Type::Path(ref typepath) if typepath.qself.is_none() => Some(&typepath.path),
67            _ => None,
68        }
69    }
70
71    // TODO store (with lazy static) the vec of string
72    // TODO maybe optimization, reverse the order of segments
73    fn extract_option_segment(path: &Path) -> Option<&PathSegment> {
74        let idents_of_path = path
75            .segments
76            .iter()
77            .fold(String::new(), |mut acc, v| {
78                acc.push_str(&v.ident.to_string());
79                acc.push('|');
80                acc
81            });
82        vec!["Option|", "std|option|Option|", "core|option|Option|"]
83            .into_iter()
84            .find(|s| idents_of_path == *s)
85            .and_then(|_| path.segments.last())
86    }
87
88    extract_type_path(ty)
89        .and_then(|path| extract_option_segment(path))
90        .and_then(|path_seg| {
91            let type_params = &path_seg.arguments;
92            // It should have only on angle-bracketed param ("<String>"):
93            match *type_params {
94                PathArguments::AngleBracketed(ref params) => params.args.first(),
95                _ => None,
96            }
97        })
98        .and_then(|generic_arg| match *generic_arg {
99            GenericArgument::Type(ref ty) => Some(ty),
100            _ => None,
101        })
102}