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}