mwapi_responses_derive/
builder.rs

1/*
2Copyright (C) 2021 Kunal Mehta <legoktm@debian.org>
3
4This program is free software: you can redistribute it and/or modify
5it under the terms of the GNU General Public License as published by
6the Free Software Foundation, either version 3 of the License, or
7(at your option) any later version.
8
9This program is distributed in the hope that it will be useful,
10but WITHOUT ANY WARRANTY; without even the implied warranty of
11MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12GNU General Public License for more details.
13
14You should have received a copy of the GNU General Public License
15along with this program.  If not, see <https://www.gnu.org/licenses/>.
16 */
17
18use proc_macro2::TokenStream as TokenStream2;
19use quote::{format_ident, quote, ToTokens};
20use syn::{Ident, Visibility};
21
22pub(crate) struct StructBuilder {
23    pub(crate) ident: Ident,
24    pub(crate) fields: Vec<StructField>,
25    pub(crate) visibility: Visibility,
26    pub(crate) extra_derive: Option<TokenStream2>,
27}
28
29impl ToTokens for StructBuilder {
30    fn to_tokens(&self, tokens: &mut TokenStream2) {
31        let ident = &self.ident;
32        let visiblity = &self.visibility;
33        let extra_derive = &self.extra_derive;
34        let fields = &self.fields;
35        let stream = quote! {
36            #[doc(hidden)]
37            #extra_derive
38            #[derive(Debug, Clone, ::mwapi_responses::serde::Deserialize)]
39            #[serde(crate = "::mwapi_responses::serde")]
40            #visiblity struct #ident {
41                #(#fields)*
42            }
43        };
44
45        stream.to_tokens(tokens);
46    }
47}
48
49#[derive(Debug)]
50pub(crate) struct StructField {
51    pub(crate) name: String,
52    pub(crate) type_: TokenStream2,
53    pub(crate) default: bool,
54    pub(crate) rename: Option<String>,
55    pub(crate) deserialize_with: Option<String>,
56}
57
58impl ToTokens for StructField {
59    /// A field line looks like:
60    /// ```ignore
61    /// #[serde(default)] #[serde(rename = "foo")] foo: String;
62    /// ```
63    /// The serde attributes are optional.
64    ///
65    fn to_tokens(&self, tokens: &mut TokenStream2) {
66        let (name, rename) = match &self.rename {
67            Some(rename) => {
68                let name = &self.name;
69                (
70                    rename.to_string(),
71                    Some(quote! {
72                        #[serde(rename = #name)]
73                    }),
74                )
75            }
76            None => (self.name.to_string(), None),
77        };
78        let name = format_ident!("{}", name);
79        let type_ = &self.type_;
80        let default = if self.default {
81            Some(quote! {
82                #[serde(default)]
83            })
84        } else {
85            None
86        };
87        let deser_with = self.deserialize_with.as_ref().map(|function| {
88            quote! {
89                #[serde(deserialize_with = #function)]
90            }
91        });
92        let stream = quote! {
93            #default #rename #deser_with pub #name: #type_,
94        };
95
96        stream.to_tokens(tokens);
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103    fn stringify<P: ToTokens>(thing: P) -> String {
104        let mut stream = quote! {};
105        thing.to_tokens(&mut stream);
106        stream.to_string()
107    }
108
109    #[test]
110    fn test_builder() {
111        let builder = StructBuilder {
112            ident: format_ident!("{}", "Name"),
113            fields: vec![StructField {
114                name: "abc".to_string(),
115                type_: quote! { u32 },
116                default: false,
117                rename: None,
118                deserialize_with: None,
119            }],
120            visibility: Visibility::Inherited,
121            extra_derive: None,
122        };
123        assert_eq!(
124            &stringify(builder),
125            "# [doc (hidden)] # [derive (Debug , Clone , :: mwapi_responses :: serde :: Deserialize)] # [serde (crate = \"::mwapi_responses::serde\")] struct Name { pub abc : u32 , }"
126        );
127    }
128
129    #[test]
130    fn test_field() {
131        let field = StructField {
132            name: "abc".to_string(),
133            type_: quote! { u32 },
134            default: false,
135            rename: None,
136            deserialize_with: None,
137        };
138        assert_eq!(&stringify(field), "pub abc : u32 ,");
139        let field = StructField {
140            name: "abc".to_string(),
141            type_: quote! { u32 },
142            default: true,
143            rename: None,
144            deserialize_with: None,
145        };
146        assert_eq!(&stringify(field), "# [serde (default)] pub abc : u32 ,");
147        let field = StructField {
148            name: "abc".to_string(),
149            type_: quote! { u32 },
150            default: true,
151            rename: Some("def".to_string()),
152            deserialize_with: None,
153        };
154        assert_eq!(
155            &stringify(field),
156            "# [serde (default)] # [serde (rename = \"abc\")] pub def : u32 ,"
157        );
158        let field = StructField {
159            name: "abc".to_string(),
160            type_: quote! { u32 },
161            default: false,
162            rename: None,
163            deserialize_with: Some("::foo::bar".to_string()),
164        };
165        assert_eq!(
166            &stringify(field),
167            "# [serde (deserialize_with = \"::foo::bar\")] pub abc : u32 ,"
168        );
169    }
170}