Skip to main content

wwsvc_rs_derive/
lib.rs

1#![warn(missing_docs)]
2#![crate_name = "wwsvc_rs_derive"]
3//! # wwsvc-rs-derive
4//!
5//! This is a set of macros to derive the traits from wwsvc-rs.
6
7extern crate proc_macro;
8
9use darling::{FromDeriveInput, FromField, FromMeta};
10use proc_macro::TokenStream;
11use quote::quote;
12use syn::{parse_macro_input, DeriveInput};
13
14#[derive(FromDeriveInput)]
15#[darling(attributes(wwsvc))]
16struct WWSVCGetAttributes {
17    function: String,
18    #[darling(default)]
19    version: Option<u32>,
20    #[darling(default)]
21    list_name: Option<String>,
22    #[darling(default)]
23    container_name: Option<String>,
24}
25
26struct RenameField(String);
27
28impl FromMeta for RenameField {
29    fn from_string(value: &str) -> darling::Result<Self> {
30        Ok(RenameField(value.to_string()))
31    }
32
33    fn from_list(items: &[darling::ast::NestedMeta]) -> darling::Result<Self> {
34        let mut rename = None;
35        for item in items {
36            if let darling::ast::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue {
37                path,
38                value:
39                    syn::Expr::Lit(syn::ExprLit {
40                        lit: syn::Lit::Str(lit_str),
41                        ..
42                    }),
43                ..
44            })) = item
45            {
46                if path.is_ident("deserialize") {
47                    rename = Some(lit_str.value());
48                }
49            }
50        }
51        if let Some(rename) = rename {
52            Ok(RenameField(rename))
53        } else {
54            Err(darling::Error::custom(
55                "serde(rename) requires a deserialize rename",
56            ))
57        }
58    }
59}
60
61#[derive(FromField)]
62#[darling(attributes(serde), allow_unknown_fields)]
63struct WWSVCGetFieldAttributes {
64    rename: RenameField,
65}
66
67/// Generates a response and a container struct based on the name of the struct and the function name.
68///
69/// ## Example
70/// ```
71/// use wwsvc_rs_proc::WWSVCGetData;
72///
73/// #[derive(WWSVCGetData, serde::Deserialize, Clone)]
74/// #[wwsvc(function = "IDBID0026")]
75/// pub struct TrackingData {
76///     #[serde(rename = "IDB_0_20")]
77///     pub index: String
78/// }
79/// ```
80#[proc_macro_derive(WWSVCGetData, attributes(wwsvc))]
81pub fn wwsvc_wrapper_derive(input: TokenStream) -> TokenStream {
82    let ast = parse_macro_input!(input as DeriveInput);
83
84    let name = &ast.ident;
85    let WWSVCGetAttributes { function, version, list_name, container_name } =
86        WWSVCGetAttributes::from_derive_input(&ast).unwrap();
87
88    // parse fields and add #[serde(rename = "#name")] to each field
89    let fields = if let syn::Data::Struct(syn::DataStruct {
90        fields: syn::Fields::Named(syn::FieldsNamed { named: fields, .. }),
91        ..
92    }) = &ast.data
93    {
94        fields
95            .iter()
96            .map(|field| {
97                let WWSVCGetFieldAttributes { rename } = WWSVCGetFieldAttributes::from_field(field)
98                    .expect("WWSVCGetData requires serde renames!");
99                rename
100            })
101            .collect::<Vec<_>>()
102    } else {
103        panic!("WWSVCGetData can only be derived for structs with named fields.");
104    };
105
106    let response_type = format!("{}Response", name);
107    let container_type = format!("{}Container", name);
108    let function_list = match list_name {
109        Some(name) => name,
110        None => format!("{}LISTE", function),
111    };
112    let container = match container_name {
113        Some(name) => name,
114        None => function.clone(),
115    };
116    let full_function_name = format!("{function}.GET");
117    let response_ident = syn::Ident::new(&response_type, name.span());
118    let container_ident = syn::Ident::new(&container_type, name.span());
119    // collect fields to comma separated string
120    let available_fields = fields
121        .into_iter()
122        .map(|field| field.0)
123        .collect::<Vec<_>>()
124        .join(",");
125
126    let function_version = if let Some(version) = version {
127        quote! {
128            const VERSION: u32 = #version;
129        }
130    } else {
131        quote! {}
132    };
133
134    let gen = quote! {
135        /// A response struct for a WWSVC GET request.
136        #[derive(serde::Deserialize, Debug, Clone)]
137        pub struct #response_ident {
138            /// The COMRESULT of the request. Contains information about the status of the request.
139            #[serde(rename = "COMRESULT")]
140            pub com_result: wwsvc_rs::responses::ComResult,
141            /// The container struct for the list of items.
142            #[serde(rename = #function_list)]
143            pub container: Option<#container_ident>,
144        }
145
146        /// Container struct for the list of items.
147        #[derive(serde::Deserialize, Debug, Clone)]
148        pub struct #container_ident {
149            /// The list of items.
150            #[serde(rename = #container)]
151            pub list: Option<Vec<#name>>,
152        }
153
154        #[wwsvc_rs::async_trait]
155        impl wwsvc_rs::traits::WWSVCGetData for #name {
156            const FUNCTION: &'static str = #full_function_name;
157            #function_version
158            const FIELDS: &'static str = #available_fields;
159
160            type Response = #response_ident;
161            type Container = #container_ident;
162        }
163
164        impl wwsvc_rs::cursor_response::HasList<#name> for #response_ident {
165            fn into_items(self) -> Option<Vec<#name>> {
166                if let Some(container) = self.container {
167                    container.list
168                } else {
169                    None
170                }
171            }
172        }
173
174        impl wwsvc_rs::cursor_response::HasComResult for #response_ident {
175            fn comresult(&self) -> &wwsvc_rs::responses::ComResult {
176                &self.com_result
177            }
178        }
179    };
180
181    gen.into()
182}