vim_macros/
lib.rs

1//! # vim_macros
2//!
3//! Procedural macros for simplified VMware vSphere property retrieval and monitoring.
4//!
5//! ## Property Retrieval with Macros
6//!
7//! The library provides two powerful macros to simplify property retrieval and monitoring:
8//!
9//! ### One-time Property Retrieval with `vim_retrievable`
10//!
11//! The `vim_retrievable` macro creates structs for efficient, one-time property retrieval:
12//!
13//! ```ignore
14//! use vim_macros::vim_retrievable;
15//! use vim_rs::core::pc_retrieve::ObjectRetriever;
16//!
17//! // Define a struct mapping to HostSystem properties
18//! vim_retrievable!(
19//!     struct Host: HostSystem {
20//!         name = "name",
21//!         power_state = "runtime.power_state",
22//!         connected = "runtime.connection_state",
23//!         cpu_usage = "summary.quick_stats.overall_cpu_usage",
24//!         memory_usage = "summary.quick_stats.overall_memory_usage",
25//!         uptime = "summary.quick_stats.uptime",
26//!         datastores = "datastore.length",
27//!     }
28//! );
29//!
30//! async fn print_hosts(client: &Client) -> Result<()> {
31//!    // Create a retriever using the client
32//!    let retriever = ObjectRetriever::new(client.clone())?;
33//!
34//!    // Retrieve all hosts with their properties in a single API call
35//!    let hosts: Vec<HostInfo> = retriever
36//!            .retrieve_objects_from_container(&client.service_content().root_folder)
37//!            .await?;
38//!
39//!    // Work with strongly-typed host objects
40//!    for host in hosts {
41//!       println!("Host {} is {:?}", host.name, host.power_state);
42//!    }
43//!
44//!    Ok(())
45//! }
46//! ```
47//! 
48//! The `ObjectRetriever` utility class is used to retrieve properties from the vSphere API. It
49//! allows you to retrieve properties from a container (e.g., a datacenter or cluster) or a specific
50//! list of managed objects. The `retrieve_objects_from_container` method retrieves all objects
51//! from the specified container and returns them as a vector of strongly-typed objects. The
52//! `retrieve_objects_from_list` method retrieves a specific list of managed objects.
53//!
54//! ### Continuous Property Monitoring with `vim_updatable`
55//!
56//! The `vim_updatable` macro creates structs for continuous property monitoring:
57//!
58//! ```ignore
59//! vim_updatable!(
60//!     struct VmDetails: VirtualMachine {
61//!         name = "name",
62//!         power_state = "runtime.power_state",
63//!     }
64//! );
65//!
66//! impl Display for VmDetails {
67//!     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
68//!         write!(
69//!             f,
70//!             "VM ({}): {} with power state: {:?}", self.id.value, self.name, self.power_state
71//!         )
72//!     }
73//! }
74//!
75//! struct ChangeListener {}
76//!
77//! impl ObjectCacheListener<VmDetails> for ChangeListener {
78//!     fn on_new(&mut self, obj: &VmDetails) {
79//!         info!("New VM: {}", obj);
80//!     }
81//!
82//!     fn on_update(&mut self, obj: &VmDetails) {
83//!         info!("VM updated: {}", obj);
84//!     }
85//!
86//!     fn on_remove(&mut self, obj: VmDetails) {
87//!         info!("VM removed: {}", obj);
88//!     }
89//! }
90//!
91//! async fn monitor_vms(client: &Arc<Client>) -> Result<(), Error> {
92//!     let cache = Box::new(ObjectCache::new_with_listener(Box::new(ChangeListener {})));
93//!     let mut manager = CacheManager::new(client.clone())?;
94//!     let mut monitor = manager.create_monitor()?;
95//!
96//!     manager.add_container_cache(cache, &client.service_content().root_folder).await?;
97//!
98//!     let start = Instant::now();
99//!     loop {
100//!         let updates = monitor.wait_updates(10).await?;
101//!         if let Some(updates) = updates {
102//!             manager.apply_updates(updates)?;
103//!         }
104//!         if start.elapsed().as_secs() > 60 {
105//!             break;
106//!         }
107//!     }
108//!
109//!     manager.destroy().await?;
110//!     Ok(())
111//! }
112//! ```
113//! Note that `CacheManager` also supports `add_list_cache` method to monitor predefined list of
114//! objects.
115//! 
116//! ### How the Macros Work
117//!
118//! Both macros:
119//!
120//! 1. Generate a struct based on the data structure defined in the macro, corresponding to a vSphere managed object type (VirtualMachine, HostSystem, etc.)
121//! 2. Elicit the types of struct fields from the property paths in the vSphere API
122//! 3. Handle type conversion between vSphere dynamic types and Rust types
123//!
124//! The `vim_rs::core::pc_retrieve` module supports one-time property retrieval,
125//! while `vim_rs::core::pc_cache` provides infrastructure for continuous property monitoring.
126//!
127//! ### Macro Syntax
128//!
129//! ```ignore
130//! vim_retrievable!(
131//!     struct StructName: ManagedObjectType {
132//!         field_name = "property.path",
133//!         another_field = "another.property.path"
134//!     }
135//! );
136//! ```
137//! 
138//! Property paths use dot delimited rust field name starting from the managed object property
139//! accessor and going deeper inside the structure. Property paths only support the properties of
140//! the declared field types i.e. properties from child types are not supported. 
141//! 
142//! Array properties support special `length` property to retrieve their length e.g. 
143//! "datastore.length"to obtain the count of datastores attached to a host.
144//! 
145//! The same syntax applies to the `vim_updatable!` macro.
146mod resolver;
147mod field_data;
148
149use proc_macro::TokenStream;
150use quote::{quote};
151use syn::{parse_macro_input, Token, braced, punctuated::Punctuated, parse::Parse, parse::ParseStream, Result, Ident, LitStr, token};
152use syn::token::Comma;
153use resolver::get_default_field_data;
154
155#[allow(dead_code)]
156struct PropertyField {
157    name: Ident,
158    colon_token: Token![=],
159    path: LitStr,
160}
161
162#[allow(dead_code)]
163struct VimObjectMacro {
164    struct_token: Token![struct],
165    struct_name: Ident,
166    colon_token: Token![:],
167    object_type: Ident,
168    brace_token: token::Brace,
169    fields: Punctuated<PropertyField, Token![,]>,
170}
171
172impl Parse for PropertyField {
173    fn parse(input: ParseStream) -> Result<Self> {
174        Ok(PropertyField {
175            name: input.parse()?,
176            colon_token: input.parse()?,
177            path: input.parse()?,
178        })
179    }
180}
181
182impl Parse for VimObjectMacro {
183    fn parse(input: ParseStream) -> Result<Self> {
184        let content;
185        Ok(VimObjectMacro {
186            struct_token: input.parse()?,
187            struct_name: input.parse()?,
188            colon_token: input.parse()?,
189            object_type: input.parse()?,
190            brace_token: braced!(content in input),
191            fields: Punctuated::parse_terminated(&content)?,
192        })
193    }
194}
195
196struct FieldInfo<'a> {
197    property_field: &'a PropertyField,
198    field_data: resolver::FieldData,
199}
200
201
202
203/// A macro to generate a struct and implementation necessary to work with
204/// PropertyCollector::RetrievePropertiesEx API. Developers need to select the managed object type
205/// of interest and the properties they need retrieved. For example to retrieve properties of a
206/// VirtualMachine object, the macro can be used as follows:
207///
208/// ```ignore
209/// vim_retrievable!(
210///    struct VM: VirtualMachine {
211///       name = "name",
212///       os = "summary.guest.guest_full_name",
213///       storage = "summary.storage",
214///       host_cpu = "summary.quick_stats.overall_cpu_usage",
215///       host_memory = "summary.quick_stats.host_memory_usage",
216///       status = "summary.overall_status",
217///       power_state = "runtime.power_state",
218///       devices = "config.hardware.device",
219///       ft_info = "config.ft_info",
220///   }
221/// );
222/// ```
223/// The macro will generate a struct `VM` with the specified properties and extract their types from
224/// the vSphere API. The generated struct will implement the `TryFrom<vim_rs::types::structs::ObjectContent>`
225/// trait, allowing you to convert the output of the `PropertyCollector::retrieve_properties_ex`
226/// into vector of objects from the generated struct.
227#[proc_macro]
228pub fn vim_retrievable(input: TokenStream) -> TokenStream {
229    let VimObjectMacro { struct_token: _, struct_name, colon_token: _, object_type: managed_object_type, brace_token: _, fields } =
230        parse_macro_input!(input as VimObjectMacro);
231
232    let (field_infos, errors) = resolve_fields(&managed_object_type, &fields);
233
234    let struct_tokens = generate_struct_decl(&struct_name, &field_infos);
235
236    let struct_impl_tokens = generate_retrieve_struct_impl(&struct_name, &managed_object_type, &field_infos);
237
238    let try_from_object_content = generate_try_from_object_content(&struct_name, &field_infos);
239
240    let output = quote! {
241        #( #errors )*
242
243        #struct_tokens
244        #struct_impl_tokens
245
246        #try_from_object_content
247    };
248    output.into()
249}
250
251/// A macro to generate a struct and implementation necessary to work with
252/// PropertyCollector::wait_for_updates_ex API. Developers need to select the managed object type
253/// of interest and the properties they need replicated. For example to replicate properties of a
254/// VirtualMachine object, the macro can be used as follows:
255///
256/// ```ignore
257/// vim_updatable!(
258///    struct VM: VirtualMachine {
259///       name = "name",
260///       os = "summary.guest.guest_full_name",
261///       storage = "summary.storage",
262///       host_cpu = "summary.quick_stats.overall_cpu_usage",
263///       host_memory = "summary.quick_stats.host_memory_usage",
264///       status = "summary.overall_status",
265///       power_state = "runtime.power_state",
266///       devices = "config.hardware.device",
267///       ft_info = "config.ft_info",
268///   }
269/// );
270/// ```
271/// The macro will generate a struct `VM` with the specified properties and extract their types from
272/// the vSphere API. The generated struct will implement the `TryFrom<vim_rs::types::structs::ObjectUpdate>`
273/// trait, allowing you to convert the output of the `PropertyCollector::wait_for_updates_ex`
274/// into vector of objects from the generated struct. Subsequently, the generated struct content can
275/// be updated using the `apply_update` method. The generated struct will also implement the
276/// `Queriable` trait, allowing you to use the `prop_spec` method to generate a `PropertySpec` for
277/// the specified properties.
278///
279/// The generated struct is usable with the `ObjectCache` and `CacheManager` utility objects.
280#[proc_macro]
281pub fn vim_updatable(input: TokenStream) -> TokenStream {
282    let VimObjectMacro { struct_token: _, struct_name, colon_token: _, object_type: managed_object_type, brace_token: _, fields } =
283        parse_macro_input!(input as VimObjectMacro);
284
285    let (field_infos, errors) = resolve_fields(&managed_object_type, &fields);
286
287    let struct_tokens = generate_struct_decl(&struct_name, &field_infos);
288
289    let struct_impl_tokens = generate_updateable_struct_impl(&struct_name, &managed_object_type, &field_infos);
290
291    let try_from_object_content = generate_try_from_object_update(&struct_name, &field_infos);
292
293    let output = quote! {
294        #( #errors )*
295
296        #struct_tokens
297        #struct_impl_tokens
298
299        #try_from_object_content
300    };
301    output.into()
302}
303
304fn resolve_fields<'a>(managed_object_type: &Ident, fields: &'a Punctuated<PropertyField, Comma>) -> (Vec<FieldInfo<'a>>, Vec<proc_macro2::TokenStream>) {
305    let mut field_infos = Vec::new();
306    let mut errors: Vec<proc_macro2::TokenStream> = Vec::new();
307    for property_field in fields {
308        let path_str = property_field.path.value();
309        let res = resolver::resolve_path(&managed_object_type.to_string(), &path_str);
310        let field_data = match res {
311            Ok(field_type) => field_type,
312            Err(e) => {
313                let msg = format!("Error resolving path: {}", e);
314                errors.push(syn::Error::new(property_field.path.span(), msg).to_compile_error());
315                get_default_field_data()
316            }
317        };
318        field_infos.push(FieldInfo { property_field, field_data });
319    };
320    (field_infos, errors)
321}
322
323
324/// Generates a struct declaration based on the provided struct name and fields.
325fn generate_struct_decl(struct_name: &Ident, fields: &Vec<FieldInfo>) -> proc_macro2::TokenStream {
326    let mut field_declarations: Vec<proc_macro2::TokenStream> = Vec::with_capacity(fields.len());
327    let mut docs: Vec<&'static str> = Vec::with_capacity(fields.len());
328
329    for f in fields {
330        let field_name = &f.property_field.name;
331        let parsed_field_type: syn::Type = syn::parse_str(&f.field_data.data_type).unwrap();
332        let decl = quote! {
333            #field_name : #parsed_field_type
334        };
335        field_declarations.push(decl);
336        docs.push(f.field_data.doc.unwrap_or(""))
337    }
338
339    let struct_tokens = quote! {
340        #[derive(Debug)]
341        pub struct #struct_name {
342            #[doc = "Object identifier"]
343            pub id: vim_rs::types::structs::ManagedObjectReference,
344            #(#[doc = #docs]
345            pub #field_declarations,)*
346        }
347    };
348    struct_tokens
349}
350
351fn generate_retrieve_struct_impl(struct_name: &Ident, managed_object_type: &Ident, fields: &Vec<FieldInfo>) -> proc_macro2::TokenStream {
352    let prop_spec = prop_spec(managed_object_type, fields);
353    let id = id();
354    quote! {
355        impl vim_rs::core::pc_helpers::Queriable for #struct_name {
356            #prop_spec
357        }
358
359        impl #struct_name {
360            pub #id
361        }
362    }
363}
364
365fn generate_updateable_struct_impl(struct_name: &Ident, managed_object_type: &Ident, fields: &Vec<FieldInfo>) -> proc_macro2::TokenStream {
366    let prop_spec = prop_spec(managed_object_type, fields);
367    let id = id();
368    let apply_update = generate_apply_update(fields);
369    quote! {
370        impl vim_rs::core::pc_helpers::Queriable for #struct_name {
371            #prop_spec
372        }
373
374        impl vim_rs::core::pc_cache::Cacheable for #struct_name {
375            #id
376            #apply_update
377        }
378    }
379}
380
381fn prop_spec(managed_object_type: &Ident, fields: &Vec<FieldInfo>) -> proc_macro2::TokenStream {
382    let field_paths: Vec<&str> = fields.iter().map(|f| f.field_data.vim_path.as_str()).collect();
383    let prop_paths_quoted: Vec<proc_macro2::TokenStream> = field_paths
384        .iter()
385        .map(|path| quote! { #path.into() })
386        .collect();
387
388    quote! {
389        fn prop_spec() -> vim_rs::types::structs::PropertySpec {
390            vim_rs::types::structs::PropertySpec {
391                all: Some(false),
392                path_set: Some(vec![
393                    #(#prop_paths_quoted),*
394                ]),
395                r#type: Into::<&str>::into(vim_rs::types::enums::MoTypesEnum::#managed_object_type).to_string(),
396            }
397        }
398    }
399}
400
401fn id() -> proc_macro2::TokenStream {
402    quote! {
403        fn id(&self) -> &vim_rs::types::structs::ManagedObjectReference {
404            &self.id
405        }
406    }
407}
408
409fn generate_try_from_object_content(struct_name: &Ident, fields: &Vec<FieldInfo>) -> proc_macro2::TokenStream {
410
411    let mut field_declarations: Vec<proc_macro2::TokenStream> = Vec::with_capacity(fields.len());
412    let mut field_conversions: Vec<proc_macro2::TokenStream> = Vec::with_capacity(fields.len());
413    let mut field_assignments: Vec<proc_macro2::TokenStream> = Vec::with_capacity(fields.len());
414    let mut idx = 1;
415    for field in fields {
416        let field_alias: Ident = syn::parse_str(&format!("field{}", idx)).unwrap();
417        let field_name = &field.property_field.name;
418        field_declarations.push(quote! { let mut #field_alias = None; });
419        match field.field_data.processing_type {
420            resolver::FieldProcessingType::Enum(enum_field_name) => {
421                field_conversions.push(generate_enum_field_from_content(field, &field_alias, &enum_field_name));
422            },
423            resolver::FieldProcessingType::Struct => {
424                field_conversions.push(generate_struct_field_from_content(field, &field_alias, &field.field_data.data_type));
425            },
426            resolver::FieldProcessingType::Trait => {
427                field_conversions.push(generate_trait_field_from_content(field, &field_alias, &field.field_data.data_type));
428            },
429        }
430        if field.field_data.is_optional {
431            field_assignments.push(quote! { #field_name: #field_alias });
432        } else {
433            let field_name_str = field.field_data.vim_path.as_str();
434            field_assignments.push(quote! { #field_name: #field_alias.ok_or_else(|| vim_rs::core::error::Error::missing_required_field(#field_name_str.to_string()))? });
435        }
436        idx += 1;
437    }
438
439    quote! {
440        impl core::convert::TryFrom<vim_rs::types::structs::ObjectContent> for #struct_name {
441            type Error = vim_rs::core::error::Error;
442
443            fn try_from(row: vim_rs::types::structs::ObjectContent) -> vim_rs::core::error::Result<Self> {
444                let id = row.obj;
445                let Some(row) = row.prop_set else {
446                    return Err(vim_rs::core::error::Error::no_data_found());
447                };
448
449                #(#field_declarations)*
450
451                for prop in row {
452                    match prop.name.as_str() {
453                        #(#field_conversions)*
454                        name => {
455                            return Err(vim_rs::core::error::Error::unexpected_property_path(name.to_string()));
456                        }
457                    }
458                }
459
460                Ok(#struct_name {
461                    id,
462                    #(#field_assignments),*
463                })
464            }
465        }
466    }
467}
468
469fn generate_try_from_object_update(struct_name: &Ident, fields: &Vec<FieldInfo>) -> proc_macro2::TokenStream {
470
471    let mut field_declarations: Vec<proc_macro2::TokenStream> = Vec::with_capacity(fields.len());
472    let mut field_conversions: Vec<proc_macro2::TokenStream> = Vec::with_capacity(fields.len());
473    let mut field_assignments: Vec<proc_macro2::TokenStream> = Vec::with_capacity(fields.len());
474    let mut idx = 1;
475    for field in fields {
476        let field_alias: Ident = syn::parse_str(&format!("field{}", idx)).unwrap();
477        let field_name = &field.property_field.name;
478        field_declarations.push(quote! { let mut #field_alias = None; });
479        match field.field_data.processing_type {
480            resolver::FieldProcessingType::Enum(enum_field_name) => {
481                field_conversions.push(generate_enum_field_from_update(field, &field_alias, &enum_field_name));
482            },
483            resolver::FieldProcessingType::Struct => {
484                field_conversions.push(generate_struct_field_from_update(field, &field_alias, &field.field_data.data_type));
485            },
486            resolver::FieldProcessingType::Trait => {
487                field_conversions.push(generate_trait_field_from_update(field, &field_alias, &field.field_data.data_type));
488            },
489        }
490        if field.field_data.is_optional {
491            field_assignments.push(quote! { #field_name: #field_alias });
492        } else {
493            let field_name_str = field.field_data.vim_path.as_str();
494            field_assignments.push(quote! { #field_name: #field_alias.ok_or_else(|| vim_rs::core::error::Error::missing_required_field(#field_name_str.to_string()))? });
495        }
496        idx += 1;
497    }
498
499    quote! {
500        impl core::convert::TryFrom<vim_rs::types::structs::ObjectUpdate> for #struct_name {
501            type Error = vim_rs::core::error::Error;
502
503            fn try_from(row: vim_rs::types::structs::ObjectUpdate) -> vim_rs::core::error::Result<Self> {
504                let id = row.obj;
505                let Some(row) = row.change_set else {
506                    return Err(vim_rs::core::error::Error::no_data_found());
507                };
508
509                #(#field_declarations)*
510
511                for prop in row {
512                    if matches!(prop.op, vim_rs::types::enums::PropertyChangeOpEnum::Add | vim_rs::types::enums::PropertyChangeOpEnum::Remove | vim_rs::types::enums::PropertyChangeOpEnum::Other_(_)) {
513                        // It is assumption of the code here that create_filter was called with `partial_updates = false`.
514                        // This flag value implies only `assign` and `indirectRemove` operations are returned. `add` and
515                        // `remove` operations are used when `partial_updates = true`. The big problem is that with
516                        // `partial_updates` server will return paths to sub-properties that we cannot easily resolve in
517                        // Rust hence the assumption that `partial_updates = false` is made.`Add` and `Remove` operations
518                        // are not supported in this code.
519                        // error!("Unsupported PropertyChangeOp: {:?} for property {} in  object {:?}", prop.op, prop.name, id);
520                        continue;
521                    }
522                    match prop.name.as_str() {
523                        #(#field_conversions)*
524                        name => {
525                            return Err(vim_rs::core::error::Error::unexpected_property_path(name.to_string()));
526                        }
527                    }
528                }
529
530                Ok(#struct_name {
531                    id,
532                    #(#field_assignments),*
533                })
534            }
535        }
536    }
537}
538
539
540fn generate_apply_update(fields: &Vec<FieldInfo>) -> proc_macro2::TokenStream {
541
542    let mut field_conversions: Vec<proc_macro2::TokenStream> = Vec::with_capacity(fields.len());
543    for field in fields {
544
545        match field.field_data.processing_type {
546            resolver::FieldProcessingType::Enum(enum_field_name) => {
547                field_conversions.push(generate_enum_field_apply(field, &enum_field_name));
548            },
549            resolver::FieldProcessingType::Struct => {
550                field_conversions.push(generate_struct_field_apply(field, &field.field_data.data_type));
551            },
552            resolver::FieldProcessingType::Trait => {
553                field_conversions.push(generate_trait_field_apply(field, &field.field_data.data_type));
554            },
555        }
556    }
557
558    quote! {
559        fn apply_update(&mut self, update: vim_rs::types::structs::ObjectUpdate) -> vim_rs::core::pc_helpers::Result<()> {
560            let Some(row) = update.change_set else {
561                return Ok(());
562            };
563
564            for prop in row {
565                if matches!(prop.op, vim_rs::types::enums::PropertyChangeOpEnum::Add | vim_rs::types::enums::PropertyChangeOpEnum::Remove | vim_rs::types::enums::PropertyChangeOpEnum::Other_(_)) {
566                    // It is assumption of the code here that create_filter was called with `partial_updates = false`.
567                    // This flag value implies only `assign` and `indirectRemove` operations are returned. `add` and
568                    // `remove` operations are used when `partial_updates = true`. The big problem is that with
569                    // `partial_updates` server will return paths to sub-properties that we cannot easily resolve in
570                    // Rust hence the assumption that `partial_updates = false` is made.`Add` and `Remove` operations
571                    // are not supported in this code.
572                    // error!("Unsupported PropertyChangeOp: {:?} for property {} in  object {:?}", prop.op, prop.name, id);
573                    continue;
574                }
575                match prop.name.as_str() {
576                    #(#field_conversions)*
577                    name => {
578                        return Err(vim_rs::core::error::Error::unexpected_property_path(name.to_string()));
579                    }
580                }
581            }
582            Ok(())
583        }
584    }
585}
586
587// Templates for TryFrom<ObjectContent> generated code
588
589// 1. Generate ValueElements enum members deserialize code
590//                 "<property path>" => {
591//                     <field name with ordinal> = match prop.val {
592//                         VimAny::Value(ValueElements::<enum field name>(vd)) => Some(vd),
593//                         ref val => return Err(pc_helpers::Error::InvalidPropertyType { property: "<property path>".to_string(), "<enum field name>".to_string(), got: pc_helpers::type_name(val)}),
594//                     };
595//                 }
596fn generate_enum_field_from_content(field: &FieldInfo, field_alias: &Ident, enum_field_name: &str) -> proc_macro2::TokenStream {
597    let path = &field.field_data.vim_path;
598    let enum_field = Ident::new(enum_field_name, field.property_field.path.span());
599    quote! {
600        #path => {
601            #field_alias = match prop.val {
602                vim_rs::types::vim_any::VimAny::Value(vim_rs::types::boxed_types::ValueElements::#enum_field(vd)) => Some(vd),
603                ref val => return Err(vim_rs::core::error::Error::invalid_property_type(#path.to_string(), #enum_field_name.to_string(), vim_rs::core::pc_helpers::type_name(val))),
604            };
605        }
606    }
607}
608
609fn generate_enum_field_from_update(field: &FieldInfo, field_alias: &Ident, enum_field_name: &str) -> proc_macro2::TokenStream {
610    let path = &field.field_data.vim_path;
611    let enum_field = Ident::new(enum_field_name, field.property_field.path.span());
612    quote! {
613        #path => {
614            #field_alias = match prop.val {
615                Some(vim_rs::types::vim_any::VimAny::Value(vim_rs::types::boxed_types::ValueElements::#enum_field(vd))) => Some(vd),
616                None => continue,
617                Some(ref val) => return Err(vim_rs::core::error::Error::invalid_property_type(#path.to_string(), #enum_field_name.to_string(), vim_rs::core::pc_helpers::type_name(val))),
618            };
619        }
620    }
621}
622
623fn generate_enum_field_apply(field: &FieldInfo, enum_field_name: &str) -> proc_macro2::TokenStream {
624    let path = &field.field_data.vim_path;
625    let enum_field = Ident::new(enum_field_name, field.property_field.path.span());
626    let field_name = &field.property_field.name;
627    let none_code;
628    let value_code;
629    if field.field_data.is_optional {
630        none_code = quote! { None };
631        value_code = quote! { Some(vd) };
632    } else {
633        none_code = quote! { return Err(vim_rs::core::error::Error::missing_required_field(#path.to_string())) };
634        value_code = quote! { vd };
635    };
636
637    quote! {
638        #path => {
639            self.#field_name = match prop.val {
640                Some(vim_rs::types::vim_any::VimAny::Value(vim_rs::types::boxed_types::ValueElements::#enum_field(vd))) => #value_code,
641                None => #none_code,
642                Some(ref val) => return Err(vim_rs::core::error::Error::invalid_property_type(#path.to_string(), #enum_field_name.to_string(), vim_rs::core::pc_helpers::type_name(val))),
643            };
644        }
645    }
646}
647
648
649// 2. Generate struct type deserialize code ofr structs without children
650//                "<property path>" => {
651//                     <field name with ordinal> = match prop.val {
652//                         VimAny::Object(obj) => {
653//                             let name: &'static str = obj.data_type().into();
654//                             match obj.as_any_box().downcast() {
655//                                 Ok(val) => Some(*val),
656//                                 Err(_) => return Err(pc_helpers::Error::InvalidPropertyType {property: "<property path>".to_string(), "<struct type name>".to_string(), name.to_string())),
657//                             }
658//                         },
659//                         ref val => return Err(pc_helpers::Error::InvalidPropertyType {property: "<property path>".to_string(), "<struct type name>".to_string(), got: pc_helpers::type_name(val)}),
660//                     };
661//                 }
662fn generate_struct_field_from_content(field: &FieldInfo, field_alias: &Ident, struct_type: &str) -> proc_macro2::TokenStream {
663    let path = &field.field_data.vim_path;
664
665    quote! {
666        #path => {
667            #field_alias = match prop.val {
668                vim_rs::types::vim_any::VimAny::Object(obj) => {
669                    let name: &'static str = obj.data_type().into();
670                    match obj.as_any_box().downcast() {
671                        Ok(val) => Some(*val),
672                        Err(_) => return Err(vim_rs::core::error::Error::invalid_property_type( #path.to_string(), #struct_type.to_string(), name.to_string())),
673                    }
674                },
675                ref val => return Err(vim_rs::core::error::Error::invalid_property_type( #path.to_string(), #struct_type.to_string(), vim_rs::core::pc_helpers::type_name(val))),
676            };
677        }
678    }
679}
680
681fn generate_struct_field_from_update(field: &FieldInfo, field_alias: &Ident, struct_type: &str) -> proc_macro2::TokenStream {
682    let path = &field.field_data.vim_path;
683    quote! {
684        #path => {
685            #field_alias = match prop.val {
686                Some(vim_rs::types::vim_any::VimAny::Object(obj)) => {
687                    let name: &'static str = obj.data_type().into();
688                    match obj.as_any_box().downcast() {
689                        Ok(val) => Some(*val),
690                        Err(_) => return Err(vim_rs::core::error::Error::invalid_property_type( #path.to_string(), #struct_type.to_string(), name.to_string())),
691                    }
692                },
693                None => continue,
694                Some(ref val) => return Err(vim_rs::core::error::Error::invalid_property_type( #path.to_string(), #struct_type.to_string(), vim_rs::core::pc_helpers::type_name(val))),
695            };
696        }
697    }
698}
699
700fn generate_struct_field_apply(field: &FieldInfo, struct_type: &str) -> proc_macro2::TokenStream {
701    let path = &field.field_data.vim_path;
702    let field_name = &field.property_field.name;
703    let none_code;
704    let value_code;
705    if field.field_data.is_optional {
706        none_code = quote! { None };
707        value_code = quote! { Some(*val) };
708    } else {
709        none_code = quote! { return Err(vim_rs::core::error::Error::missing_required_field(#path.to_string())) };
710        value_code = quote! { *val };
711    };
712    quote! {
713        #path => {
714            self.#field_name = match prop.val {
715                Some(vim_rs::types::vim_any::VimAny::Object(obj)) => {
716                    let name: &'static str = obj.data_type().into();
717                    match obj.as_any_box().downcast() {
718                        Ok(val) => #value_code,
719                        Err(_) => return Err(vim_rs::core::error::Error::invalid_property_type( #path.to_string(), #struct_type.to_string(), name.to_string())),
720                    }
721                },
722                None => #none_code,
723                Some(ref val) => return Err(vim_rs::core::error::Error::invalid_property_type( #path.to_string(), #struct_type.to_string(), vim_rs::core::pc_helpers::type_name(val))),
724            };
725        }
726    }
727}
728
729// 3. Generate trait type deserialize code for structs with children
730//                "<property path>" => {
731//                     <field name with ordinal> = match prop.val {
732//                         vim_rs::types::vim_any::VimAny::Object(obj) => {
733//                             let name: &'static str = obj.data_type().into();
734//                             match obj.into_box() {
735//                                 Ok(val) => Some(val),
736//                                 Err(_) => return Err(pc_helpers::Error::InvalidPropertyType {property: "<property path>".to_string(), "<trait type name>".to_string(), name.to_string())),
737//                             }
738//                         },
739//                         ref val => return Err(pc_helpers::Error::InvalidPropertyType {property: "<property path>".to_string(), "<trait type name>".to_string(), got: pc_helpers::type_name(val)}),
740//                     };
741//                 }
742fn generate_trait_field_from_content(field: &FieldInfo, field_alias: &Ident, trait_type: &str) -> proc_macro2::TokenStream {
743    let path = &field.field_data.vim_path;
744    quote! {
745        #path => {
746            #field_alias = match prop.val {
747                vim_rs::types::vim_any::VimAny::Object(obj) => {
748                    let name: &'static str = obj.data_type().into();
749                    match vim_rs::types::convert::CastInto::into_box(obj) {
750                        Ok(val) => Some(val),
751                        Err(_) => return Err(vim_rs::core::error::Error::invalid_property_type( #path.to_string(), #trait_type.to_string(), name.to_string())),
752                    }
753                },
754                ref val => return Err(vim_rs::core::error::Error::invalid_property_type( #path.to_string(), #trait_type.to_string(), vim_rs::core::pc_helpers::type_name(val))),
755            };
756        }
757    }
758}
759
760fn generate_trait_field_from_update(field: &FieldInfo, field_alias: &Ident, trait_type: &str) -> proc_macro2::TokenStream {
761    let path = &field.field_data.vim_path;
762    quote! {
763        #path => {
764            #field_alias = match prop.val {
765                Some(vim_rs::types::vim_any::VimAny::Object(obj)) => {
766                    let name: &'static str = obj.data_type().into();
767                    match vim_rs::types::convert::CastInto::into_box(obj) {
768                        Ok(val) => Some(val),
769                        Err(_) => return Err(vim_rs::core::error::Error::invalid_property_type( #path.to_string(), #trait_type.to_string(), name.to_string())),
770                    }
771                },
772                None => continue,
773                Some(ref val) => return Err(vim_rs::core::error::Error::invalid_property_type( #path.to_string(), #trait_type.to_string(), vim_rs::core::pc_helpers::type_name(val))),
774            };
775        }
776    }
777}
778
779fn generate_trait_field_apply(field: &FieldInfo, trait_type: &str) -> proc_macro2::TokenStream {
780    let path = &field.field_data.vim_path;
781    let field_name = &field.property_field.name;
782    let none_code;
783    let value_code;
784    if field.field_data.is_optional {
785        none_code = quote! { None };
786        value_code = quote! { Some(val) };
787    } else {
788        none_code = quote! { return Err(vim_rs::core::error::Error::missing_required_field(#path.to_string())) };
789        value_code = quote! { val };
790    };
791
792    quote! {
793        #path => {
794            self.#field_name = match prop.val {
795                Some(vim_rs::types::vim_any::VimAny::Object(obj)) => {
796                    let name: &'static str = obj.data_type().into();
797                    match vim_rs::types::convert::CastInto::into_box(obj) {
798                        Ok(val) => #value_code,
799                        Err(_) => return Err(vim_rs::core::error::Error::invalid_property_type( #path.to_string(), #trait_type.to_string(), name.to_string())),
800                    }
801                },
802                None => #none_code,
803                Some(ref val) => return Err(vim_rs::core::error::Error::invalid_property_type( #path.to_string(), #trait_type.to_string(), vim_rs::core::pc_helpers::type_name(val))),
804            };
805        }
806    }
807}
808