Skip to main content

xoev_xwasser_derive/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3
4mod xwasser_validate;
5
6#[proc_macro_derive(XWasserValidate)]
7pub fn xwasser_validate_derive(input: TokenStream) -> TokenStream {
8    xwasser_validate::derive(input.into())
9        .unwrap_or_else(syn::Error::into_compile_error)
10        .into()
11}
12
13#[proc_macro_attribute]
14pub fn xoev_xwasser_code(attr: TokenStream, item: TokenStream) -> TokenStream {
15    xoev_xwasser_code2(attr.into(), item.into())
16        .unwrap_or_else(syn::Error::into_compile_error)
17        .into()
18}
19
20fn xoev_xwasser_code2(
21    attr: proc_macro2::TokenStream,
22    item: proc_macro2::TokenStream,
23) -> syn::Result<proc_macro2::TokenStream> {
24    let XoevXWasserCodeArgs {
25        uri,
26        version,
27        validate,
28    }: XoevXWasserCodeArgs = xoev_xwasser_code_args(attr)?;
29    let ast: syn::DeriveInput = syn::parse2(item)?;
30    let name = ast.ident;
31    let version = version.unwrap_or_default();
32    let validation = if validate {
33        None
34    } else {
35        Some(quote! {
36            fn validate(&self, _: &impl crate::CodeListsProvider) -> bool {
37                true
38            }
39        })
40    };
41    Ok(quote! {
42        #[derive(Clone, Default, Debug, raxb::XmlSerialize, raxb::XmlDeserialize, serde::Serialize, serde::Deserialize)]
43        #[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
44        #[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
45        #[cfg_attr(feature = "builder", derive(TypedBuilder))]
46        #[xml(tns(b"xwas", crate::TNS))]
47        pub struct #name {
48            #[xml(name = b"code", ty = "child")]
49            #[serde(default)]
50            pub code: String,
51            #[xml(name = b"name", ty = "child")]
52            #[serde(default)]
53    #[cfg_attr(feature = "wasm", tsify(optional))]
54            pub name: Option<String>,
55            #[serde(skip)]
56            #[xml(
57                default,
58                name = b"listURI",
59                ty = "attr",
60                value = #uri
61            )]
62            #[cfg_attr(feature = "builder", builder(default))]
63            _list_uri: ConstStr,
64            #[serde(skip)]
65            #[xml(
66                default,
67                name = b"listVersionID",
68                ty = "attr",
69                value = #version
70            )]
71            #[cfg_attr(feature = "builder", builder(default))]
72            _list_version_id: ConstStr,
73        }
74
75
76        impl <S> From<S> for #name where S: Into<String> {
77            fn from(val: S) -> Self {
78                Self {
79                    code: val.into(),
80                    ..Default::default()
81                }
82            }
83        }
84
85        impl GenericXwasserCode for #name {
86            fn code(&self) -> &str {
87                &self.code
88            }
89        }
90
91        #[cfg(feature = "validate")]
92        impl crate::CodeListValue for #name {
93            const CODELIST: &str = #uri;
94            #validation
95            fn as_value(&self) -> &str {
96                &self.code
97            }
98        }
99
100        #[cfg(feature = "validate")]
101        impl crate::XWasserValidateMarker for #name {}
102
103    })
104}
105
106fn xoev_xwasser_code_args(attr: proc_macro2::TokenStream) -> syn::Result<XoevXWasserCodeArgs> {
107    syn::parse2(attr)
108}
109
110#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
111struct XoevXWasserCodeArgs {
112    uri: String,
113    version: Option<String>,
114    validate: bool,
115}
116
117impl syn::parse::Parse for XoevXWasserCodeArgs {
118    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
119        let uri = input.parse().map(|litstr: syn::LitStr| litstr.value())?;
120        if !input.is_empty() {
121            let _: syn::token::Comma = input.parse()?;
122        }
123        let mut version = None;
124        let mut validate = false;
125        if !input.is_empty() {
126            let head = input.fork();
127            let lit: syn::Lit = input.parse()?;
128            match lit {
129                syn::Lit::Str(lit_str) => version = Some(lit_str.value()),
130                syn::Lit::Bool(lit_bool) => validate = lit_bool.value(),
131                _ => return Err(head.error("expected either string or bool")),
132            }
133            if !input.is_empty() {
134                let _: syn::token::Comma = input.parse()?;
135            }
136        }
137        if !input.is_empty() {
138            let lit_bool: syn::LitBool = input.parse()?;
139            validate = lit_bool.value();
140            if !input.is_empty() {
141                let _: syn::token::Comma = input.parse()?;
142            }
143        }
144
145        Ok(Self {
146            uri,
147            version,
148            validate,
149        })
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    fn xoev_xwasser_validate_case(attr: proc_macro2::TokenStream) -> XoevXWasserCodeArgs {
158        xoev_xwasser_code_args(attr).expect("XoevXWasserValidateArgs")
159    }
160
161    #[test]
162    fn xoev_xwasser_validate_should_parse_valid_attr() {
163        for (case, expected) in [
164            (
165                quote! { "abc" },
166                XoevXWasserCodeArgs {
167                    uri: "abc".into(),
168                    version: None,
169                    validate: false,
170                },
171            ),
172            (
173                quote! { "abc", },
174                XoevXWasserCodeArgs {
175                    uri: "abc".into(),
176                    version: None,
177                    validate: false,
178                },
179            ),
180            (
181                quote! { "abc", "def" },
182                XoevXWasserCodeArgs {
183                    uri: "abc".into(),
184                    version: Some("def".into()),
185                    validate: false,
186                },
187            ),
188            (
189                quote! { "abc", "def", },
190                XoevXWasserCodeArgs {
191                    uri: "abc".into(),
192                    version: Some("def".into()),
193                    validate: false,
194                },
195            ),
196            (
197                quote! { "abc", true },
198                XoevXWasserCodeArgs {
199                    uri: "abc".into(),
200                    version: None,
201                    validate: true,
202                },
203            ),
204            (
205                quote! { "abc", false, },
206                XoevXWasserCodeArgs {
207                    uri: "abc".into(),
208                    version: None,
209                    validate: false,
210                },
211            ),
212            (
213                quote! { "abc", "def", true },
214                XoevXWasserCodeArgs {
215                    uri: "abc".into(),
216                    version: Some("def".into()),
217                    validate: true,
218                },
219            ),
220            (
221                quote! { "abc", "def", false, },
222                XoevXWasserCodeArgs {
223                    uri: "abc".into(),
224                    version: Some("def".into()),
225                    validate: false,
226                },
227            ),
228        ] {
229            assert_eq!(xoev_xwasser_validate_case(case), expected);
230        }
231    }
232}