oxinat_derive/
lib.rs

1extern crate proc_macro;
2mod uri;
3mod model;
4mod version;
5
6use proc_macro::TokenStream;
7
8use proc_macro2::{Ident, Span};
9use quote::quote;
10use syn::{parse_macro_input, Attribute, Data, DeriveInput, Fields};
11
12use uri::uribuilder;
13use crate::version::{
14    derive_version_parse_legacy,
15    derive_version_parse_root_uri,
16    derive_version_parse_data_uri
17};
18
19/// Shortcut to avoid repetive usage of the same
20/// derive parsing boilerplate. Exposes the
21/// declared fields from an input parsed as
22/// `syn::DeriveInput`
23macro_rules! derive_input_boilerplate {
24    ($($field:ident),+ $(,)?; from $input:ident) => {
25        let temp_input = $input.clone();
26        let DeriveInput {
27            $(
28                $field,
29            )+
30            ..
31        } = parse_macro_input!(temp_input as DeriveInput);
32    };
33}
34
35/// Shortcut to avoid repetive usage of the same
36/// implementation pattern. Specifically in cases
37/// where a trait is entirely comprised of inline
38/// definitions.
39macro_rules! empty_impl {
40    ($derive_name:path; from $input:ident) => {
41        {
42            derive_input_boilerplate!(ident, generics; from $input);
43            let where_clause = &generics.where_clause;
44            let crate_ident  = get_crate_ident();
45            quote! {
46                impl #generics #crate_ident::$derive_name for #ident #generics #where_clause {}
47            }
48        }
49    };
50}
51
52/// Shortcut to create an ambiguous ident token.
53macro_rules! new_ambiguous_ident {
54    ($e:expr, $($vars:expr),+ $(,)?) => {
55        Ident::new(
56            &format!($e, $($vars,)+),
57            Span::call_site()
58        )
59    };
60    ($e:expr) => {
61        Ident::new($e, Span::call_site())
62    }
63}
64
65/// Alias for `Vec<syn::Attribute>`.
66type Attributes = Vec<Attribute>;
67
68fn get_crate_ident() -> Ident {
69    let pkg_name = std::env::var("CARGO_PKG_NAME").unwrap_or("crate".into());
70    new_ambiguous_ident!(if pkg_name == "oxinat_core" {
71        "crate"
72    } else {
73        "oxinat_core"
74    })
75}
76
77#[proc_macro_derive(FullUri)]
78pub fn derive_alluri(input: TokenStream) -> TokenStream {
79    let mut gen = TokenStream::new();
80    [
81        derive_adminuri,
82        derive_archiveuri,
83        derive_authuri,
84        derive_dicomuri,
85        derive_eventuri,
86        derive_experimenturi,
87        derive_pluginuri,
88        derive_projectsuri,
89        derive_serviceuri,
90        derive_subjecturi,
91        derive_sysuri,
92        derive_usersuri
93    ].iter().for_each(|deriver| gen.extend(deriver(input.clone())));
94    gen
95}
96
97/// Generates the methods required to implement a
98/// `AdminUri` or `AdminUriLegacy` trait, allowing
99/// for a type to represent the administrative
100/// endpoints available.
101#[proc_macro_derive(AdminUri)]
102pub fn derive_adminuri(input: TokenStream) -> TokenStream {
103    derive_input_boilerplate!(attrs; from input);
104
105    // Conditionally implement legacy endpoints.
106    let mut gen = quote! {};
107    if !derive_version_parse_legacy(&attrs) {
108        gen.extend(empty_impl!(AdminUri; from input));
109    }
110    gen.extend(empty_impl!(AdminUriLegacy; from input));
111    gen.into()
112}
113
114/// Generates the methods required to implement a
115/// `ArchiveUri` trait, allowing for a type to
116/// represent the XNAT archive access endpoints.
117#[proc_macro_derive(ArchiveUri)]
118pub fn derive_archiveuri(input: TokenStream) -> TokenStream {
119    empty_impl!(ArchiveUri; from input).into()
120}
121
122/// Generates the methods required to implement a
123/// `AuthUri` trait, allowing for a type to
124/// represent the user authentication endpoints.
125#[proc_macro_derive(AuthUri)]
126pub fn derive_authuri(input: TokenStream) -> TokenStream {
127    empty_impl!(AuthUriLegacy; from input).into()
128}
129
130/// Generates the methods required to implement a
131/// `DicomUri` trait, allowing for a type to
132/// represent the DICOM management endpoints.
133#[proc_macro_derive(DicomUri)]
134pub fn derive_dicomuri(input: TokenStream) -> TokenStream {
135    empty_impl!(DicomUri; from input).into()
136}
137
138/// Generates the methods required to implement a
139/// `EventsUri` trait, allowing for a type to
140/// represent the XNAT event system.
141#[proc_macro_derive(EventUri)]
142pub fn derive_eventuri(input: TokenStream) -> TokenStream {
143    empty_impl!(EventsUri; from input).into()
144}
145
146/// Generates the methods required to implement a
147/// `ExperimentsUri` trait, allowing for atype to
148/// represent the XNAT experiments system.
149#[proc_macro_derive(ExperimentUri)]
150pub fn derive_experimenturi(input: TokenStream) -> TokenStream {
151    let mut gen = quote! {};
152    gen.extend(empty_impl!(ExperimentUri; from input));
153    gen.extend(empty_impl!(ExperimentUriArchive; from input));
154    gen.into()
155}
156
157/// Generates the methods required to implement a
158/// `PluginUri` trait, allowing for a type to
159/// represent the plugin management endpoints.
160#[proc_macro_derive(PluginUri)]
161pub fn derive_pluginuri(input: TokenStream) -> TokenStream {
162    empty_impl!(PluginUri; from input).into()
163}
164
165/// Generates the methods required to implement a
166/// `ProjectUri` trait, allowing for a type to
167/// represent the endpoints available for project
168/// management.
169#[proc_macro_derive(ProjectUri)]
170pub fn derive_projectsuri(input: TokenStream) -> TokenStream {
171    derive_input_boilerplate!(attrs; from input);
172    let mut gen = quote! {};
173    if !derive_version_parse_legacy(&attrs) {
174        gen.extend(empty_impl!(ProjectUri; from input))
175    }
176    gen.extend(empty_impl!(ProjectUriArchive; from input));
177    gen.extend(empty_impl!(ProjectUriLegacy; from input));
178    gen.into()
179}
180
181/// Generates the methods required to implement a
182/// `ServicesUri` trait. allowing for a type to
183/// represent certain service endpoints
184/// available.
185#[proc_macro_derive(ServicesUri)]
186pub fn derive_serviceuri(input: TokenStream) -> TokenStream {
187    empty_impl!(ServicesUriLegacy; from input).into()
188}
189
190/// Generates the methods required to implement a
191/// `SubjectUri` trait, allowing for a type to
192/// represent the endpoints available for subject
193/// management.
194#[proc_macro_derive(SubjectUri)]
195pub fn derive_subjecturi(input: TokenStream) -> TokenStream {
196    let mut gen = quote! {};
197    gen.extend(empty_impl!(SubjectUriLegacy; from input));
198    gen.extend(empty_impl!(SubjectUriArchive; from input));
199    gen.into()
200}
201
202/// Generates the methods required to implement a
203/// `SystemUri` trait, allowing for a type to
204/// represent the administrative endpoints
205/// available.
206#[proc_macro_derive(SystemUri)]
207pub fn derive_sysuri(input: TokenStream) -> TokenStream {
208    empty_impl!(SystemUri; from input).into()
209}
210
211/// Generates the methods required to implement a
212/// `UsersUri` trait, allowing for a type to
213/// represent the user administrative endpoints
214/// available.
215#[proc_macro_derive(UsersUri)]
216pub fn derive_usersuri(input: TokenStream) -> TokenStream {
217    derive_input_boilerplate!(attrs; from input);
218
219    let mut gen = quote! {};
220    if !derive_version_parse_legacy(&attrs) {
221        gen.extend(empty_impl!(UsersUri; from input));
222    }
223    gen.extend(empty_impl!(UsersUriLegacy; from input));
224    gen.into()
225}
226
227/// Implements `serde::Deserialize` with a custom
228/// implementation for model properties.
229/// 
230/// ## Panics ##
231/// This macro will panic if the deriving struct
232/// is not a tuple struct.
233#[proc_macro_derive(ModelField)]
234pub fn derive_model_field(input: TokenStream) -> TokenStream {
235    model::build_property(input)
236}
237
238/// Generates an alias for `UriBuilder` and other
239/// common traits required by subsequent
240/// implementations.
241///
242/// Specifically, generates the alias from the
243/// given `name` and then produces a declarative
244/// macro, derived from the `name`.
245/// 
246/// 
247/// e.g.
248/// ```no_compile
249/// use oxinat_derive::uri_builder_alias;
250/// 
251/// uri_builder_alias!(AliasedUriBuilder);
252/// // Supports non-generics as a single pattern.
253/// ImplAliasedUriBuilder! {
254///     (String),
255///     .. // variadic declarations.
256/// }
257/// // patterns that require generics need to
258/// // currently be declared separately...
259/// ImplAliasedUriBuilder! {
260///     (TypeToImpAliasedUriBuilder<Parent>, Parent),
261///     .. // variadic declarations.
262/// }
263/// ```
264#[proc_macro]
265pub fn uri_builder_alias(input: TokenStream) -> TokenStream {
266    let ident = parse_macro_input!(input as Ident);
267    let trait_doc = "This is an alias trait for traits common in subsequent implmentations.";
268    let impl_doc  = &format!("Generate implementations of `{ident}`.");
269    let impl_name = Ident::new(&format!("Impl{ident}"), Span::call_site());
270    quote! {
271        #[doc=#trait_doc]
272        pub trait #ident: UriBuilder + Clone + Debug {}
273        #[doc=#impl_doc]
274        macro_rules! #impl_name {
275            ($(($kind:ty)),+ $(,)?) => {
276                $(impl #ident for $kind {})+
277            };
278            ($(($kind:ty, $parent:ident)),+ $(,)?) => {
279                $( 
280                    impl<$parent> #ident for $kind
281                    where
282                        $parent: #ident,
283                    {}
284                )+
285            };
286        }
287    }.into()
288}
289
290/// Generates the methods required to implement a
291/// `UriBuilder` trait, allowing the type to
292/// construct URI paths.
293/// 
294/// Currently we do not support the implementation
295/// against unions.
296#[proc_macro_derive(UriBuilder, attributes(parent, match_path, param, validator))]
297pub fn derive_uribuilder(input: TokenStream) -> TokenStream {
298    uribuilder::build(input)
299}
300
301/// Generates the methods required to implement a
302/// `Version` trait, allowing the type to
303/// represent some API version.
304#[proc_macro_derive(Version, attributes(version))]
305pub fn derive_version(input: TokenStream) -> TokenStream {
306    // Get general information related to the
307    // derive input, including the raw details,
308    // generic declarations and attributes passed
309    // through version() calls.
310    derive_input_boilerplate!(attrs, data, generics, ident; from input);
311    let where_clause = &generics.where_clause;
312    let crate_ident  = get_crate_ident();
313
314    // Determine the `root_uri` attribute to be
315    // passed to the actual derived
316    // implementation.
317    let root_uri = derive_version_parse_root_uri(&attrs)
318        .unwrap_or_else(|_| ident.to_string().to_lowercase());
319    let data_uri = derive_version_parse_data_uri(&attrs).unwrap();
320
321    let mut gen = quote! {};
322    gen.extend(quote! {
323        impl #generics #crate_ident::Version for #ident #generics #where_clause {
324            fn root_uri(&self) -> String {
325                String::from(#root_uri)
326            }
327
328            fn data_uri(&self) -> String {
329                String::from(#data_uri)
330            }
331        }
332    });
333    gen.extend(quote! {
334        impl #generics #crate_ident::UriBuilder for #ident #generics #where_clause {
335            fn build(&self) -> oxinat_core::BuildResult {
336                Ok(self.root_uri())
337            }
338        }
339    });
340    gen.extend(quote! {
341        impl #generics std::fmt::Display for #ident #generics #where_clause {
342            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
343                write!(f, "{}", self.root_uri())
344            }
345        }
346    });
347
348    let mut default_calls = quote! {};
349    match data {
350        Data::Struct(d) => d.fields.to_owned(),
351        _ => Fields::Unit,
352    }
353        .iter()
354        .for_each(|f| {
355            let field_ident = &f.ident;
356            let field_type  = &f.ty;
357            default_calls.extend(quote! {
358                #field_ident: #field_type::default(),
359            })
360        });
361    gen.extend(quote! {
362        impl #generics Default for #ident #generics #where_clause {
363            fn default() -> Self {
364                Self { #default_calls }
365            }
366        }
367    });
368    gen.into()
369}