fsds_rs_derive/
lib.rs

1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4use quote::quote;
5use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Fields, FieldsNamed};
6
7/// Implements `TryFrom<Value>` for a #struct and `From<#struct>` for `Value`.
8///
9/// Note that [`rmpv::Value`] must be in scope for the derive to work.
10///
11/// ## From<#struct> for Value implementation
12///
13/// The implementation of `From<#struct>` for `Value` will create a `Value::Map`
14/// with the field names as keys and the field values as values.
15///
16/// Every field of the struct must implement `Into<Value>`.
17///
18/// ## TryFrom<Value> for #struct implementation
19///
20/// The implementation of `TryFrom<Value>` for `#struct` will try to convert a
21/// `Value::Map` to a struct.
22///
23/// Every field of the struct must implement `TryFrom<Value>`. The struct must
24/// have the same fields as the `Value::Map` keys.
25#[proc_macro_derive(FromIntoValue)]
26pub fn from_and_into_for_value_derive(input: TokenStream) -> TokenStream {
27    // Parsing TokenStream into DeriveInput.
28    let input = parse_macro_input!(input as DeriveInput);
29
30    // Extracting the struct name.
31    let name = input.ident;
32
33    // Extracting the fields of the struct.
34    let fields = if let Data::Struct(DataStruct {
35        fields: Fields::Named(FieldsNamed { named, .. }),
36        ..
37    }) = &input.data
38    {
39        named
40    } else {
41        // Works only for structs with named fields.
42        unimplemented!();
43    };
44
45    // ----------------------- //
46    // FROM<#struct> FOR VALUE //
47    // ----------------------- //
48
49    // Converting the struct fields into `Value`s.
50    let field_from_impl = fields.iter().map(|field| {
51        let field_name = &field.ident;
52        quote! {
53            vec.push((stringify!(#field_name).into(), value.#field_name.into()));
54        }
55    });
56
57    // From<#struct> for Value implementation.
58    let from_impl = quote! {
59        impl From<#name> for Value {
60            fn from(value: #name) -> Self {
61                let mut vec = Vec::new();
62
63                #(#field_from_impl)*
64
65                Value::Map(vec)
66            }
67        }
68    };
69
70    // -------------------------- //
71    // TRYFROM<VALUE> FOR #struct //
72    // -------------------------- //
73
74    let try_from_impl = {
75        // Converting the `Value::Map` fields into the struct fields.
76        let fields_def = fields.iter().map(|field| {
77            let field_name = &field.ident;
78            quote! {
79                let pos = map.iter().position(|(k, _)| k
80                    .as_str()
81                    .unwrap_or("Value::Map should contain only String keys to be converted to a struct.")
82                    == stringify!(#field_name)
83                ).ok_or(anyhow::anyhow!("Field {} not found in Value::Map.", stringify!(#field_name)))?;
84                let #field_name = map
85                    .remove(pos)
86                    .1
87                    .try_into()
88                    .map_err(|_| anyhow::anyhow!("Every field of {} should be convertible to Value.", stringify!(#name)))?;
89            }
90        });
91
92        // Populating the struct fields.
93        let fields = fields.iter().map(|field| {
94            let field_name = &field.ident;
95            quote! {
96                #field_name
97            }
98        });
99
100        // TryFrom<Value> for #struct implementation.
101        quote! {
102            impl TryFrom<Value> for #name {
103                type Error = anyhow::Error;
104
105                fn try_from(value: Value) -> Result<Self, Self::Error> {
106                    match value {
107                        Value::Map(mut map) => {
108                            #(#fields_def)*
109                            if map.is_empty() {
110                                Ok(#name {
111                                    #(#fields),*
112                                })
113                            } else {
114                                Err(anyhow::anyhow!("Value::Map contains extra fields: {:?}", map))
115                            }
116                        }
117                        _ => Err(anyhow::anyhow!("Value should be a Map to be converted to {}", stringify!(#name))),
118                    }
119                }
120            }
121        }
122    };
123
124    // Expanding the macro.
125    let expanded = quote! {
126        #from_impl
127        #try_from_impl
128    };
129
130    // Returning the generated impl.
131    TokenStream::from(expanded)
132}