1#![warn(missing_docs)]
2#![crate_name = "wwsvc_rs_derive"]
3extern 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#[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 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 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 #[derive(serde::Deserialize, Debug, Clone)]
137 pub struct #response_ident {
138 #[serde(rename = "COMRESULT")]
140 pub com_result: wwsvc_rs::responses::ComResult,
141 #[serde(rename = #function_list)]
143 pub container: Option<#container_ident>,
144 }
145
146 #[derive(serde::Deserialize, Debug, Clone)]
148 pub struct #container_ident {
149 #[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}