tmflib_derive/
lib.rs

1// Copyright [2025] [Ryan Ruckley]
2
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6
7//     http://www.apache.org/licenses/LICENSE-2.0
8
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Derive Crate for TMFLib traits.
16
17#![warn(missing_docs)]
18
19use proc_macro::TokenStream;
20use quote::quote;
21use syn::{parse_macro_input, Data, DeriveInput};
22
23/// Generate code for struct when HasId trait is required.
24/// NB: This trait requires both id and href fields to be present.
25#[proc_macro_derive(HasId)]
26pub fn hasid_derive(input: TokenStream) -> TokenStream {
27    let input = parse_macro_input!(input as DeriveInput);
28
29    let fields = match input.data {
30        Data::Struct(s) => s
31            .fields
32            .into_iter()
33            .map(|f| f.ident.unwrap().to_string())
34            .collect::<Vec<_>>(),
35        _ => panic!("HasId only supports Struct"),
36    };
37    let name = input.ident;
38    // Ensure id field is present
39    let _id = fields
40        .iter()
41        .find(|s| *s == "id")
42        .expect("No id field present");
43    let _href = fields
44        .iter()
45        .find(|s| *s == "href")
46        .expect("No href field present");
47    // Generate HasId impl block based on this name.
48
49    let out = quote! {
50
51        impl HasId for #name {
52            fn generate_id(&mut self) {
53                let id = #name::get_uuid();
54                self.id = id.into();
55                self.generate_href();
56            }
57            fn generate_href(&mut self) {
58                let href = format!("{}/{}",#name::get_class_href(),self.get_id());
59                self.href = href.into();
60            }
61            fn get_id(&self) -> String {
62                match self.id.as_ref() {
63                    Some(i) => i.clone(),
64                    None => String::default(),
65                }
66            }
67            fn get_href(&self) -> String {
68                match self.href.as_ref() {
69                    Some(h) => h.clone(),
70                    None => String::default(),
71                }
72            }
73            fn get_class() -> String {
74                CLASS_PATH.to_string()
75            }
76            fn get_class_href() -> String {
77                format!("/{}/{}/{}",crate::get_lib_path(),MOD_PATH,#name::get_class())
78            }
79            fn get_mod_path() -> String {
80                format!("/{}/{}",crate::get_lib_path(),MOD_PATH)
81            }
82            fn set_id(&mut self, id : impl Into<String>) {
83                self.id = Some(id.into());
84                // Since we have changed the Id, the href will be invalid.
85                self.generate_href();
86            }
87
88            fn id(mut self, id : impl Into<String>) -> Self {
89                self.set_id(id);
90                self
91            }
92        }
93    };
94    out.into()
95}
96
97/// Generate functions for HasAttachment Trait
98#[proc_macro_derive(HasAttachment)]
99pub fn hasattachment_derive(input: TokenStream) -> TokenStream {
100    let input = parse_macro_input!(input as DeriveInput);
101    let fields = match input.data {
102        Data::Struct(s) => s
103            .fields
104            .into_iter()
105            .map(|f| f.ident.unwrap().to_string())
106            .collect::<Vec<_>>(),
107        _ => panic!("HasAttachments only supports Struct"),
108    };
109    let _last_update = fields
110        .iter()
111        .find(|s| *s == "attachment")
112        .expect("No attachment field present");
113    let name = input.ident;
114    let out = quote! {
115        impl HasAttachment for #name {
116            fn add(&mut self, attachment : &AttachmentRefOrValue) {
117                match self.attachment.as_mut() {
118                    Some(v) => {
119                        v.push(attachment.clone());
120                    }
121                    None => {
122                        self.attachment = Some(vec![attachment.clone()]);
123                    }
124                }
125            }
126            fn position(&self, name : impl Into<String>) -> Option<usize> {
127                match self.attachment.as_ref() {
128                    Some(v) => {
129                        let pattern : String = name.into();
130                        v.iter().position(|a| a.name == Some(pattern.clone()))
131                    }
132                    None => None,
133                }
134            }
135            fn find(&self, name : impl Into<String>) -> Option<&AttachmentRefOrValue> {
136                match self.attachment.as_ref() {
137                    Some(v) => {
138                        let pattern : String = name.into();
139                        v.iter().find(|a| a.name == Some(pattern.clone()))
140                    },
141                    None => None,
142                }
143            }
144            fn get(&self, position: usize) -> Option<AttachmentRefOrValue> {
145                match self.attachment.as_ref() {
146                    Some(v) => {
147                        v.get(position).cloned()
148                    },
149                    None => None,
150                }
151            }
152            fn remove(&mut self, position : usize) -> Option<AttachmentRefOrValue> {
153                match self.attachment.as_mut() {
154                    Some(v) => {
155                        Some(v.remove(position))
156                    },
157                    None => None,
158                }
159            }
160
161            fn attachment(mut self, attachment : AttachmentRefOrValue) -> Self {
162                self.add(&attachment);
163                self
164            }
165        }
166    };
167    out.into()
168}
169
170/// Generate code for [tmflib::HasLastUpdate] trait.
171#[proc_macro_derive(HasLastUpdate)]
172pub fn haslastupdate_derive(input: TokenStream) -> TokenStream {
173    let input = parse_macro_input!(input as DeriveInput);
174    let fields = match input.data {
175        Data::Struct(s) => s
176            .fields
177            .into_iter()
178            .map(|f| f.ident.unwrap().to_string())
179            .collect::<Vec<_>>(),
180        _ => panic!("HasId only supports Struct"),
181    };
182    let _last_update = fields
183        .iter()
184        .find(|s| *s == "last_update")
185        .expect("No last_update field present");
186    let name = input.ident;
187    let out = quote! {
188        impl HasLastUpdate for #name {
189            fn set_last_update(&mut self, time : impl Into<String>) {
190                self.last_update = Some(time.into());
191            }
192
193            fn get_last_update(&self) -> Option<String> {
194                self.last_update.clone()
195            }
196
197            fn last_update(mut self, time : Option<String>) -> Self {
198                match time {
199                    Some(t) => self.set_last_update(t),
200                    None => self.set_last_update(Self::get_timestamp()),
201                };
202                self
203            }
204        }
205    };
206    out.into()
207}
208
209/// Generate code for [tmflib::HasName] trait.
210#[proc_macro_derive(HasName)]
211pub fn hasname_derive(input: TokenStream) -> TokenStream {
212    let input = parse_macro_input!(input as DeriveInput);
213    let fields = match input.data {
214        Data::Struct(s) => s
215            .fields
216            .into_iter()
217            .map(|f| f.ident.unwrap().to_string())
218            .collect::<Vec<_>>(),
219        _ => panic!("HasId only supports Struct"),
220    };
221    let name = input.ident;
222    // Ensure id field is present
223    let _name = fields
224        .iter()
225        .find(|s| *s == "name")
226        .expect("No name field present");
227    let out = quote! {
228        impl HasName for #name {
229            fn get_name(&self) -> String {
230                self.name.clone().unwrap_or("NoName".to_string())
231            }
232            fn set_name(&mut self, name : impl Into<String>) {
233                self.name = Some(name.into().trim().to_string());
234            }
235            fn name(mut self, name :impl Into<String>) -> Self {
236                self.set_name(name);
237                self
238            }
239        }
240    };
241    out.into()
242}
243
244/// Generate code for [tmflib::HasNote] trait
245#[proc_macro_derive(HasNote)]
246pub fn hasnote_derive(input: TokenStream) -> TokenStream {
247    let input = parse_macro_input!(input as DeriveInput);
248    let fields = match input.data {
249        Data::Struct(s) => s
250            .fields
251            .into_iter()
252            .map(|f| f.ident.unwrap().to_string())
253            .collect::<Vec<_>>(),
254        _ => panic!("HasId only supports Struct"),
255    };
256    let name = input.ident;
257    // Ensure id field is present
258    let _note = fields
259        .iter()
260        .find(|s| *s == "note")
261        .expect("No note field present");
262    let out = quote! {
263        impl HasNote for #name {
264            fn add_note(&mut self, note : Note) {
265                match self.note.as_mut() {
266                    Some(v) => v.push(note),
267                    None => self.note = Some(vec![note]),
268                }
269            }
270            fn get_note(&self, idx : usize) -> Option<&Note> {
271                match self.note.as_ref() {
272                    Some(n) => n.get(idx),
273                    None => None,
274                }
275            }
276            fn remove_note(&mut self, idx: usize) -> Result<Note,TMFError> {
277                match self.note.as_mut() {
278                    Some(n) => Ok(n.remove(idx)),
279                    None => Err(TMFError::NoDataError("No notes present".to_string())),
280                }
281            }
282            fn note(mut self, note : Note) -> Self {
283                self.add_note(note);
284                self
285            }
286        }
287    };
288    out.into()
289}
290
291/// Generate code for [tmflib::HasRelatedParty] trait.
292#[proc_macro_derive(HasRelatedParty)]
293pub fn hasrelatedparty_derive(input: TokenStream) -> TokenStream {
294    let input = parse_macro_input!(input as DeriveInput);
295    let fields = match input.data {
296        Data::Struct(s) => s
297            .fields
298            .into_iter()
299            .map(|f| f.ident.unwrap().to_string())
300            .collect::<Vec<_>>(),
301        _ => panic!("HasRelatedParty only supports Struct"),
302    };
303    let name = input.ident;
304    // Ensure id field is present
305    let _related_party: &String = fields
306        .iter()
307        .find(|s| *s == "related_party")
308        .expect("No related_party field present");
309    let out = quote! {
310        impl HasRelatedParty for #name {
311            fn add_party(&mut self, party : RelatedParty) {
312                match self.related_party.as_mut() {
313                    Some(v) => v.push(party),
314                    None => self.related_party = Some(vec![party]),
315                }
316            }
317            fn get_party(&self, idx : usize ) -> Option<&RelatedParty> {
318                match self.related_party.as_ref() {
319                    Some(rp) => {
320                        // Simple return results of get()
321                        rp.get(idx)
322                    },
323                    None => None,
324                }
325
326            }
327            fn remove_party(&mut self, idx : usize) -> Result<RelatedParty,TMFError> {
328                match self.related_party.as_mut() {
329                    None => Err(TMFError::NoDataError("No related parties present".to_string())),
330                    Some(rp) => {
331                        Ok(rp.remove(idx))
332                    }
333                }
334            }
335            fn get_by_role(&self, role : String) -> Option<Vec<&RelatedParty>> {
336                match &self.related_party {
337                    Some(rp) => {
338                        let out = rp.iter()
339                            .filter(|p| p.role.is_some())
340                            .filter(|p| p.role.clone().unwrap() == role)
341                            .collect();
342                        Some(out)
343                    },
344                    None => None,
345                }
346            }
347
348            fn party(mut self, party : RelatedParty) -> Self {
349                self.add_party(party);
350                self
351            }
352        }
353    };
354    out.into()
355}
356
357/// Implement the HasDescription Trait
358#[proc_macro_derive(HasDescription)]
359pub fn hasdescription_derive(input: TokenStream) -> TokenStream {
360    let input = parse_macro_input!(input as DeriveInput);
361    let fields = match input.data {
362        Data::Struct(s) => s
363            .fields
364            .into_iter()
365            .map(|f| f.ident.unwrap().to_string())
366            .collect::<Vec<_>>(),
367        _ => panic!("HasValidity only supports Struct"),
368    };
369    let name = input.ident;
370    // Ensure id field is present
371    let _name = fields
372        .iter()
373        .find(|s| *s == "description")
374        .expect("No description field found");
375
376    let out = quote! {
377        impl HasDescription for #name {
378            fn description(mut self, description : impl Into<String>) -> Self {
379                self.description = Some(description.into());
380                self
381            }
382            fn get_description(&self) -> String {
383                match self.description.as_ref() {
384                    Some(d) => d.clone(),
385                    None => String::default(),
386                }
387            }
388            fn set_description(&mut self, description : impl Into<String>) -> Option<String> {
389                self.description.replace(description.into())
390            }
391        }
392    };
393    out.into()
394}
395
396/// Generate code for HasValidity trait.
397#[proc_macro_derive(HasValidity)]
398pub fn hasvalidity_derive(input: TokenStream) -> TokenStream {
399    let input = parse_macro_input!(input as DeriveInput);
400    let fields = match input.data {
401        Data::Struct(s) => s
402            .fields
403            .into_iter()
404            .map(|f| f.ident.unwrap().to_string())
405            .collect::<Vec<_>>(),
406        _ => panic!("HasValidity only supports Struct"),
407    };
408    let name = input.ident;
409    // Ensure id field is present
410    let _name = fields
411        .iter()
412        .find(|s| *s == "valid_for")
413        .expect("No valid_for object present");
414    let out = quote! {
415        impl HasValidity for #name {
416            fn get_validity(&self) -> Option<TimePeriod> {
417                self.valid_for.clone()
418            }
419            fn get_validity_end(&self) -> Option<crate::TimeStamp> {
420                match self.valid_for.as_ref() {
421                    Some(v) => v.end_date_time.clone(),
422                    None => None,
423                }
424            }
425            fn get_validity_start(&self) -> Option<crate::TimeStamp> {
426                match self.valid_for.as_ref() {
427                    Some(v) => Some(v.start_date_time.clone()),
428                    None => None,
429                }
430            }
431            fn set_validity(&mut self, validity : TimePeriod) {
432                self.valid_for = Some(validity);
433            }
434            fn set_validity_end(&mut self, end : crate::TimeStamp) -> TimePeriod {
435                let mut validity = match self.get_validity() {
436                    Some(v) => v,
437                    None => TimePeriod::default(),
438                };
439                validity.end_date_time = Some(end);
440                self.set_validity(validity.clone());
441                validity
442            }
443            fn set_validity_start(&mut self, start : crate::TimeStamp) -> TimePeriod {
444                let mut validity = match self.get_validity() {
445                    Some(v) => v,
446                    None => TimePeriod::default(),
447                };
448                validity.start_date_time = start;
449                self.set_validity(validity.clone());
450                validity
451            }
452            fn is_valid(&self) -> bool {
453                let validity = self.get_validity();
454                match validity {
455                    Some(v) => {
456                        if v.started() && !v.finished()  {
457                            return true
458                        }
459                        false
460                    },
461                    None => false
462                }
463            }
464
465            fn validity(mut self, validity : TimePeriod) -> Self {
466                self.set_validity(validity);
467                self
468            }
469        }
470    };
471    out.into()
472}