Skip to main content

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//!         tolerant = "some.path"?, // optional: force `Option<T>` if the server may omit it
135//!     }
136//! );
137//! ```
138//!
139//! Property paths use dot delimited rust field name starting from the managed object property
140//! accessor and going deeper inside the structure. Property paths only support the properties of
141//! the declared field types i.e. properties from child types are not supported.
142//!
143//! Array properties support special `length` property to retrieve their length e.g.
144//! "datastore.length"to obtain the count of datastores attached to a host.
145//!
146//! The same syntax applies to the `vim_updatable!` macro.
147//!
148//! ### Forcing a field to be optional
149//!
150//! A trailing `?` after the property-path string forces the generated field to be
151//! `Option<T>` even when the XSD marks the property as required. The same syntax
152//! works in **`vim_retrievable!`** and **`vim_updatable!`**: both share resolution
153//! and generated `TryFrom` logic, so missing nominally-required properties surface
154//! as `None` instead of failing `TryFrom<vim_rs::types::structs::ObjectContent>`
155//! (retrieval) or `TryFrom<vim_rs::types::structs::ObjectUpdate>` (cache). For
156//! updatable structs, `apply_update` treats forced-optional fields like any other
157//! `Option<T>` (assign `Some(...)`, clear with `None`).
158//!
159//! ```ignore
160//! vim_updatable!(
161//!     struct ClusterDetails: ClusterComputeResource {
162//!         name = "name",
163//!         // XSD-required fields that some servers (notably vcsim) never
164//!         // populate. The `?` tells the macro to type them as `Option<T>`
165//!         // instead of `T` so that missing values do not cause the whole
166//!         // object to be dropped by `TryFrom<ObjectUpdate>`.
167//!         available_cpu = "summary_ex.effective_cpu"?,
168//!         available_memory = "summary_ex.effective_memory"?,
169//!     }
170//! );
171//! ```
172//!
173//! Fields already resolved as `Option<T>` (because the XSD or some ancestor in
174//! the path is optional) are unaffected; `?` is idempotent.
175mod field_data;
176mod resolver;
177
178use proc_macro::TokenStream;
179use quote::quote;
180use resolver::get_default_field_data;
181use syn::token::Comma;
182use syn::{
183    braced, parse::Parse, parse::ParseStream, parse_macro_input, punctuated::Punctuated, token,
184    Ident, LitStr, Result, Token,
185};
186
187#[allow(dead_code)]
188struct PropertyField {
189    name: Ident,
190    colon_token: Token![=],
191    path: LitStr,
192    /// Trailing `?` after the path forces the generated field to be
193    /// `Option<T>` even when the XSD marks it as required. Useful for
194    /// servers that deviate from the spec (vcsim, older vCenters) where
195    /// a nominally-required property may simply be absent.
196    force_optional: Option<Token![?]>,
197}
198
199#[allow(dead_code)]
200struct VimObjectMacro {
201    struct_token: Token![struct],
202    struct_name: Ident,
203    colon_token: Token![:],
204    object_type: Ident,
205    brace_token: token::Brace,
206    fields: Punctuated<PropertyField, Token![,]>,
207}
208
209impl Parse for PropertyField {
210    fn parse(input: ParseStream) -> Result<Self> {
211        let name: Ident = input.parse()?;
212        let colon_token: Token![=] = input.parse()?;
213        let path: LitStr = input.parse()?;
214        let force_optional: Option<Token![?]> = input.parse()?;
215        Ok(PropertyField {
216            name,
217            colon_token,
218            path,
219            force_optional,
220        })
221    }
222}
223
224impl Parse for VimObjectMacro {
225    fn parse(input: ParseStream) -> Result<Self> {
226        let content;
227        Ok(VimObjectMacro {
228            struct_token: input.parse()?,
229            struct_name: input.parse()?,
230            colon_token: input.parse()?,
231            object_type: input.parse()?,
232            brace_token: braced!(content in input),
233            fields: Punctuated::parse_terminated(&content)?,
234        })
235    }
236}
237
238struct FieldInfo<'a> {
239    property_field: &'a PropertyField,
240    field_data: resolver::FieldData,
241}
242
243/// A macro to generate a struct and implementation necessary to work with
244/// PropertyCollector::RetrievePropertiesEx API. Developers need to select the managed object type
245/// of interest and the properties they need retrieved. For example to retrieve properties of a
246/// VirtualMachine object, the macro can be used as follows:
247///
248/// ```ignore
249/// vim_retrievable!(
250///    struct VM: VirtualMachine {
251///       name = "name",
252///       os = "summary.guest.guest_full_name",
253///       storage = "summary.storage",
254///       host_cpu = "summary.quick_stats.overall_cpu_usage",
255///       host_memory = "summary.quick_stats.host_memory_usage",
256///       status = "summary.overall_status",
257///       power_state = "runtime.power_state",
258///       devices = "config.hardware.device",
259///       ft_info = "config.ft_info",
260///   }
261/// );
262/// ```
263/// The macro will generate a struct `VM` with the specified properties and extract their types from
264/// the vSphere API. The generated struct will implement the `TryFrom<vim_rs::types::structs::ObjectContent>`
265/// trait, allowing you to convert the output of the `PropertyCollector::retrieve_properties_ex`
266/// into vector of objects from the generated struct.
267///
268/// Optional trailing `?` after a path string forces `Option<T>` for that field even
269/// when the spec marks it required (see crate-level "Forcing a field to be optional").
270#[proc_macro]
271pub fn vim_retrievable(input: TokenStream) -> TokenStream {
272    let VimObjectMacro {
273        struct_token: _,
274        struct_name,
275        colon_token: _,
276        object_type: managed_object_type,
277        brace_token: _,
278        fields,
279    } = parse_macro_input!(input as VimObjectMacro);
280
281    let (field_infos, errors) = resolve_fields(&managed_object_type, &fields);
282
283    let struct_tokens = generate_struct_decl(&struct_name, &field_infos);
284
285    let struct_impl_tokens =
286        generate_retrieve_struct_impl(&struct_name, &managed_object_type, &field_infos);
287
288    let try_from_object_content = generate_try_from_object_content(&struct_name, &field_infos);
289
290    let output = quote! {
291        #( #errors )*
292
293        #struct_tokens
294        #struct_impl_tokens
295
296        #try_from_object_content
297    };
298    output.into()
299}
300
301/// A macro to generate a struct and implementation necessary to work with
302/// PropertyCollector::wait_for_updates_ex API. Developers need to select the managed object type
303/// of interest and the properties they need replicated. For example to replicate properties of a
304/// VirtualMachine object, the macro can be used as follows:
305///
306/// ```ignore
307/// vim_updatable!(
308///    struct VM: VirtualMachine {
309///       name = "name",
310///       os = "summary.guest.guest_full_name",
311///       storage = "summary.storage",
312///       host_cpu = "summary.quick_stats.overall_cpu_usage",
313///       host_memory = "summary.quick_stats.host_memory_usage",
314///       status = "summary.overall_status",
315///       power_state = "runtime.power_state",
316///       devices = "config.hardware.device",
317///       ft_info = "config.ft_info",
318///   }
319/// );
320/// ```
321/// The macro will generate a struct `VM` with the specified properties and extract their types from
322/// the vSphere API. The generated struct will implement the `TryFrom<vim_rs::types::structs::ObjectUpdate>`
323/// trait, allowing you to convert the output of the `PropertyCollector::wait_for_updates_ex`
324/// into vector of objects from the generated struct. Subsequently, the generated struct content can
325/// be updated using the `apply_update` method. The generated struct will also implement the
326/// `Queriable` trait, allowing you to use the `prop_spec` method to generate a `PropertySpec` for
327/// the specified properties.
328///
329/// The generated struct is usable with the `ObjectCache` and `CacheManager` utility objects.
330///
331/// Optional trailing `?` after a path string forces `Option<T>` for that field even
332/// when the spec marks it required (see crate-level "Forcing a field to be optional").
333#[proc_macro]
334pub fn vim_updatable(input: TokenStream) -> TokenStream {
335    let VimObjectMacro {
336        struct_token: _,
337        struct_name,
338        colon_token: _,
339        object_type: managed_object_type,
340        brace_token: _,
341        fields,
342    } = parse_macro_input!(input as VimObjectMacro);
343
344    let (field_infos, errors) = resolve_fields(&managed_object_type, &fields);
345
346    let struct_tokens = generate_struct_decl(&struct_name, &field_infos);
347
348    let struct_impl_tokens =
349        generate_updateable_struct_impl(&struct_name, &managed_object_type, &field_infos);
350
351    let try_from_object_content = generate_try_from_object_update(&struct_name, &field_infos);
352
353    let output = quote! {
354        #( #errors )*
355
356        #struct_tokens
357        #struct_impl_tokens
358
359        #try_from_object_content
360    };
361    output.into()
362}
363
364fn resolve_fields<'a>(
365    managed_object_type: &Ident,
366    fields: &'a Punctuated<PropertyField, Comma>,
367) -> (Vec<FieldInfo<'a>>, Vec<proc_macro2::TokenStream>) {
368    let mut field_infos = Vec::new();
369    let mut errors: Vec<proc_macro2::TokenStream> = Vec::new();
370    for property_field in fields {
371        let path_str = property_field.path.value();
372        let res = resolver::resolve_path(&managed_object_type.to_string(), &path_str);
373        let mut field_data = match res {
374            Ok(field_type) => field_type,
375            Err(e) => {
376                let msg = format!("Error resolving path: {}", e);
377                errors.push(syn::Error::new(property_field.path.span(), msg).to_compile_error());
378                get_default_field_data()
379            }
380        };
381        // Caller-side opt-out from XSD required-ness: `field = "path"?` forces
382        // the generated field to be `Option<T>`. Servers that omit the
383        // property (vcsim is the prime offender) then leave the value as
384        // `None` instead of making `TryFrom<ObjectUpdate>` bail with
385        // `missing_required_field`.
386        if property_field.force_optional.is_some() && !field_data.is_optional {
387            field_data.is_optional = true;
388            field_data.data_type = format!("Option<{}>", field_data.data_type);
389        }
390        field_infos.push(FieldInfo {
391            property_field,
392            field_data,
393        });
394    }
395    (field_infos, errors)
396}
397
398/// Generates a struct declaration based on the provided struct name and fields.
399fn generate_struct_decl(struct_name: &Ident, fields: &Vec<FieldInfo>) -> proc_macro2::TokenStream {
400    let mut field_declarations: Vec<proc_macro2::TokenStream> = Vec::with_capacity(fields.len());
401    let mut docs: Vec<&'static str> = Vec::with_capacity(fields.len());
402
403    for f in fields {
404        let field_name = &f.property_field.name;
405        let parsed_field_type: syn::Type = syn::parse_str(&f.field_data.data_type).unwrap();
406        let decl = quote! {
407            #field_name : #parsed_field_type
408        };
409        field_declarations.push(decl);
410        docs.push(f.field_data.doc.unwrap_or(""))
411    }
412
413    let struct_tokens = quote! {
414        #[derive(Debug)]
415        pub struct #struct_name {
416            #[doc = "Object identifier"]
417            pub id: vim_rs::types::structs::ManagedObjectReference,
418            #(#[doc = #docs]
419            pub #field_declarations,)*
420        }
421    };
422    struct_tokens
423}
424
425fn generate_retrieve_struct_impl(
426    struct_name: &Ident,
427    managed_object_type: &Ident,
428    fields: &Vec<FieldInfo>,
429) -> proc_macro2::TokenStream {
430    let prop_spec = prop_spec(managed_object_type, fields);
431    let id = id();
432    quote! {
433        impl vim_rs::core::pc_helpers::Queriable for #struct_name {
434            #prop_spec
435        }
436
437        impl #struct_name {
438            pub #id
439        }
440    }
441}
442
443fn generate_updateable_struct_impl(
444    struct_name: &Ident,
445    managed_object_type: &Ident,
446    fields: &Vec<FieldInfo>,
447) -> proc_macro2::TokenStream {
448    let prop_spec = prop_spec(managed_object_type, fields);
449    let id = id();
450    let apply_update = generate_apply_update(fields);
451    quote! {
452        impl vim_rs::core::pc_helpers::Queriable for #struct_name {
453            #prop_spec
454        }
455
456        impl vim_rs::core::pc_cache::Cacheable for #struct_name {
457            #id
458            #apply_update
459        }
460    }
461}
462
463fn prop_spec(managed_object_type: &Ident, fields: &Vec<FieldInfo>) -> proc_macro2::TokenStream {
464    let field_paths: Vec<&str> = fields
465        .iter()
466        .map(|f| f.field_data.vim_path.as_str())
467        .collect();
468    let prop_paths_quoted: Vec<proc_macro2::TokenStream> = field_paths
469        .iter()
470        .map(|path| quote! { #path.into() })
471        .collect();
472
473    quote! {
474        fn prop_spec() -> vim_rs::types::structs::PropertySpec {
475            vim_rs::types::structs::PropertySpec {
476                all: Some(false),
477                path_set: Some(vec![
478                    #(#prop_paths_quoted),*
479                ]),
480                r#type: vim_rs::types::enums::MoTypesEnum::#managed_object_type.as_str().to_string(),
481            }
482        }
483    }
484}
485
486fn id() -> proc_macro2::TokenStream {
487    quote! {
488        fn id(&self) -> &vim_rs::types::structs::ManagedObjectReference {
489            &self.id
490        }
491    }
492}
493
494fn generate_try_from_object_content(
495    struct_name: &Ident,
496    fields: &Vec<FieldInfo>,
497) -> proc_macro2::TokenStream {
498    let mut field_declarations: Vec<proc_macro2::TokenStream> = Vec::with_capacity(fields.len());
499    let mut field_conversions: Vec<proc_macro2::TokenStream> = Vec::with_capacity(fields.len());
500    let mut field_assignments: Vec<proc_macro2::TokenStream> = Vec::with_capacity(fields.len());
501    let mut idx = 1;
502    for field in fields {
503        let field_alias: Ident = syn::parse_str(&format!("field{}", idx)).unwrap();
504        let field_name = &field.property_field.name;
505        field_declarations.push(quote! { let mut #field_alias = None; });
506        match field.field_data.processing_type {
507            resolver::FieldProcessingType::Enum(enum_field_name) => {
508                field_conversions.push(generate_enum_field_from_content(
509                    field,
510                    &field_alias,
511                    &enum_field_name,
512                ));
513            }
514            resolver::FieldProcessingType::Struct => {
515                field_conversions.push(generate_struct_field_from_content(
516                    field,
517                    &field_alias,
518                    &field.field_data.data_type,
519                ));
520            }
521            resolver::FieldProcessingType::Trait => {
522                field_conversions.push(generate_trait_field_from_content(
523                    field,
524                    &field_alias,
525                    &field.field_data.data_type,
526                ));
527            }
528        }
529        if field.field_data.is_optional {
530            field_assignments.push(quote! { #field_name: #field_alias });
531        } else {
532            let field_name_str = field.field_data.vim_path.as_str();
533            field_assignments.push(quote! { #field_name: #field_alias.ok_or_else(|| vim_rs::core::error::Error::missing_required_field(#field_name_str.to_string()))? });
534        }
535        idx += 1;
536    }
537
538    quote! {
539        impl core::convert::TryFrom<vim_rs::types::structs::ObjectContent> for #struct_name {
540            type Error = vim_rs::core::error::Error;
541
542            fn try_from(row: vim_rs::types::structs::ObjectContent) -> vim_rs::core::error::Result<Self> {
543                let id = row.obj;
544                let Some(row) = row.prop_set else {
545                    return Err(vim_rs::core::error::Error::no_data_found());
546                };
547
548                #(#field_declarations)*
549
550                for prop in row {
551                    match prop.name.as_str() {
552                        #(#field_conversions)*
553                        name => {
554                            return Err(vim_rs::core::error::Error::unexpected_property_path(name.to_string()));
555                        }
556                    }
557                }
558
559                Ok(#struct_name {
560                    id,
561                    #(#field_assignments),*
562                })
563            }
564        }
565    }
566}
567
568fn generate_try_from_object_update(
569    struct_name: &Ident,
570    fields: &Vec<FieldInfo>,
571) -> proc_macro2::TokenStream {
572    let mut field_declarations: Vec<proc_macro2::TokenStream> = Vec::with_capacity(fields.len());
573    let mut field_conversions: Vec<proc_macro2::TokenStream> = Vec::with_capacity(fields.len());
574    let mut field_assignments: Vec<proc_macro2::TokenStream> = Vec::with_capacity(fields.len());
575    let mut idx = 1;
576    for field in fields {
577        let field_alias: Ident = syn::parse_str(&format!("field{}", idx)).unwrap();
578        let field_name = &field.property_field.name;
579        field_declarations.push(quote! { let mut #field_alias = None; });
580        match field.field_data.processing_type {
581            resolver::FieldProcessingType::Enum(enum_field_name) => {
582                field_conversions.push(generate_enum_field_from_update(
583                    field,
584                    &field_alias,
585                    &enum_field_name,
586                ));
587            }
588            resolver::FieldProcessingType::Struct => {
589                field_conversions.push(generate_struct_field_from_update(
590                    field,
591                    &field_alias,
592                    &field.field_data.data_type,
593                ));
594            }
595            resolver::FieldProcessingType::Trait => {
596                field_conversions.push(generate_trait_field_from_update(
597                    field,
598                    &field_alias,
599                    &field.field_data.data_type,
600                ));
601            }
602        }
603        if field.field_data.is_optional {
604            field_assignments.push(quote! { #field_name: #field_alias });
605        } else {
606            let field_name_str = field.field_data.vim_path.as_str();
607            field_assignments.push(quote! { #field_name: #field_alias.ok_or_else(|| vim_rs::core::error::Error::missing_required_field(#field_name_str.to_string()))? });
608        }
609        idx += 1;
610    }
611
612    quote! {
613        impl core::convert::TryFrom<vim_rs::types::structs::ObjectUpdate> for #struct_name {
614            type Error = vim_rs::core::error::Error;
615
616            fn try_from(row: vim_rs::types::structs::ObjectUpdate) -> vim_rs::core::error::Result<Self> {
617                let id = row.obj;
618                let Some(row) = row.change_set else {
619                    return Err(vim_rs::core::error::Error::no_data_found());
620                };
621
622                #(#field_declarations)*
623
624                for prop in row {
625                    if matches!(prop.op, vim_rs::types::enums::PropertyChangeOpEnum::Add | vim_rs::types::enums::PropertyChangeOpEnum::Remove | vim_rs::types::enums::PropertyChangeOpEnum::Other_(_)) {
626                        // It is assumption of the code here that create_filter was called with `partial_updates = false`.
627                        // This flag value implies only `assign` and `indirectRemove` operations are returned. `add` and
628                        // `remove` operations are used when `partial_updates = true`. The big problem is that with
629                        // `partial_updates` server will return paths to sub-properties that we cannot easily resolve in
630                        // Rust hence the assumption that `partial_updates = false` is made.`Add` and `Remove` operations
631                        // are not supported in this code.
632                        // error!("Unsupported PropertyChangeOp: {:?} for property {} in  object {:?}", prop.op, prop.name, id);
633                        continue;
634                    }
635                    match prop.name.as_str() {
636                        #(#field_conversions)*
637                        name => {
638                            return Err(vim_rs::core::error::Error::unexpected_property_path(name.to_string()));
639                        }
640                    }
641                }
642
643                Ok(#struct_name {
644                    id,
645                    #(#field_assignments),*
646                })
647            }
648        }
649    }
650}
651
652fn generate_apply_update(fields: &Vec<FieldInfo>) -> proc_macro2::TokenStream {
653    let mut field_conversions: Vec<proc_macro2::TokenStream> = Vec::with_capacity(fields.len());
654    for field in fields {
655        match field.field_data.processing_type {
656            resolver::FieldProcessingType::Enum(enum_field_name) => {
657                field_conversions.push(generate_enum_field_apply(field, &enum_field_name));
658            }
659            resolver::FieldProcessingType::Struct => {
660                field_conversions.push(generate_struct_field_apply(
661                    field,
662                    &field.field_data.data_type,
663                ));
664            }
665            resolver::FieldProcessingType::Trait => {
666                field_conversions.push(generate_trait_field_apply(
667                    field,
668                    &field.field_data.data_type,
669                ));
670            }
671        }
672    }
673
674    quote! {
675        fn apply_update(&mut self, update: vim_rs::types::structs::ObjectUpdate) -> vim_rs::core::pc_helpers::Result<()> {
676            let Some(row) = update.change_set else {
677                return Ok(());
678            };
679
680            for prop in row {
681                if matches!(prop.op, vim_rs::types::enums::PropertyChangeOpEnum::Add | vim_rs::types::enums::PropertyChangeOpEnum::Remove | vim_rs::types::enums::PropertyChangeOpEnum::Other_(_)) {
682                    // It is assumption of the code here that create_filter was called with `partial_updates = false`.
683                    // This flag value implies only `assign` and `indirectRemove` operations are returned. `add` and
684                    // `remove` operations are used when `partial_updates = true`. The big problem is that with
685                    // `partial_updates` server will return paths to sub-properties that we cannot easily resolve in
686                    // Rust hence the assumption that `partial_updates = false` is made.`Add` and `Remove` operations
687                    // are not supported in this code.
688                    // error!("Unsupported PropertyChangeOp: {:?} for property {} in  object {:?}", prop.op, prop.name, id);
689                    continue;
690                }
691                match prop.name.as_str() {
692                    #(#field_conversions)*
693                    name => {
694                        return Err(vim_rs::core::error::Error::unexpected_property_path(name.to_string()));
695                    }
696                }
697            }
698            Ok(())
699        }
700    }
701}
702
703// Templates for TryFrom<ObjectContent> generated code
704
705// 1. Generate ValueElements enum members deserialize code
706//                 "<property path>" => {
707//                     <field name with ordinal> = match prop.val {
708//                         VimAny::Value(ValueElements::<enum field name>(vd)) => Some(vd),
709//                         ref val => return Err(pc_helpers::Error::InvalidPropertyType { property: "<property path>".to_string(), "<enum field name>".to_string(), got: pc_helpers::type_name(val)}),
710//                     };
711//                 }
712fn generate_enum_field_from_content(
713    field: &FieldInfo,
714    field_alias: &Ident,
715    enum_field_name: &str,
716) -> proc_macro2::TokenStream {
717    let path = &field.field_data.vim_path;
718    let enum_field = Ident::new(enum_field_name, field.property_field.path.span());
719    quote! {
720        #path => {
721            #field_alias = match prop.val {
722                vim_rs::types::vim_any::VimAny::Value(vim_rs::types::boxed_types::ValueElements::#enum_field(vd)) => Some(vd),
723                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))),
724            };
725        }
726    }
727}
728
729fn generate_enum_field_from_update(
730    field: &FieldInfo,
731    field_alias: &Ident,
732    enum_field_name: &str,
733) -> proc_macro2::TokenStream {
734    let path = &field.field_data.vim_path;
735    let enum_field = Ident::new(enum_field_name, field.property_field.path.span());
736    quote! {
737        #path => {
738            #field_alias = match prop.val {
739                Some(vim_rs::types::vim_any::VimAny::Value(vim_rs::types::boxed_types::ValueElements::#enum_field(vd))) => Some(vd),
740                None => continue,
741                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))),
742            };
743        }
744    }
745}
746
747fn generate_enum_field_apply(field: &FieldInfo, enum_field_name: &str) -> proc_macro2::TokenStream {
748    let path = &field.field_data.vim_path;
749    let enum_field = Ident::new(enum_field_name, field.property_field.path.span());
750    let field_name = &field.property_field.name;
751    let none_code;
752    let value_code;
753    if field.field_data.is_optional {
754        none_code = quote! { None };
755        value_code = quote! { Some(vd) };
756    } else {
757        none_code = quote! { return Err(vim_rs::core::error::Error::missing_required_field(#path.to_string())) };
758        value_code = quote! { vd };
759    };
760
761    quote! {
762        #path => {
763            self.#field_name = match prop.val {
764                Some(vim_rs::types::vim_any::VimAny::Value(vim_rs::types::boxed_types::ValueElements::#enum_field(vd))) => #value_code,
765                None => #none_code,
766                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))),
767            };
768        }
769    }
770}
771
772// 2. Generate struct type deserialize code ofr structs without children
773//                "<property path>" => {
774//                     <field name with ordinal> = match prop.val {
775//                         VimAny::Object(obj) => {
776//                             let name = obj.data_type().as_str();
777//                             match obj.as_any_box().downcast() {
778//                                 Ok(val) => Some(*val),
779//                                 Err(_) => return Err(pc_helpers::Error::InvalidPropertyType {property: "<property path>".to_string(), "<struct type name>".to_string(), name.to_string())),
780//                             }
781//                         },
782//                         ref val => return Err(pc_helpers::Error::InvalidPropertyType {property: "<property path>".to_string(), "<struct type name>".to_string(), got: pc_helpers::type_name(val)}),
783//                     };
784//                 }
785fn generate_struct_field_from_content(
786    field: &FieldInfo,
787    field_alias: &Ident,
788    struct_type: &str,
789) -> proc_macro2::TokenStream {
790    let path = &field.field_data.vim_path;
791
792    quote! {
793        #path => {
794            #field_alias = match prop.val {
795                vim_rs::types::vim_any::VimAny::Object(obj) => {
796                    let name = obj.data_type().as_str();
797                    match obj.as_any_box().downcast() {
798                        Ok(val) => Some(*val),
799                        Err(_) => return Err(vim_rs::core::error::Error::invalid_property_type( #path.to_string(), #struct_type.to_string(), name.to_string())),
800                    }
801                },
802                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))),
803            };
804        }
805    }
806}
807
808fn generate_struct_field_from_update(
809    field: &FieldInfo,
810    field_alias: &Ident,
811    struct_type: &str,
812) -> proc_macro2::TokenStream {
813    let path = &field.field_data.vim_path;
814    quote! {
815        #path => {
816            #field_alias = match prop.val {
817                Some(vim_rs::types::vim_any::VimAny::Object(obj)) => {
818                    let name = obj.data_type().as_str();
819                    match obj.as_any_box().downcast() {
820                        Ok(val) => Some(*val),
821                        Err(_) => return Err(vim_rs::core::error::Error::invalid_property_type( #path.to_string(), #struct_type.to_string(), name.to_string())),
822                    }
823                },
824                None => continue,
825                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))),
826            };
827        }
828    }
829}
830
831fn generate_struct_field_apply(field: &FieldInfo, struct_type: &str) -> proc_macro2::TokenStream {
832    let path = &field.field_data.vim_path;
833    let field_name = &field.property_field.name;
834    let none_code;
835    let value_code;
836    if field.field_data.is_optional {
837        none_code = quote! { None };
838        value_code = quote! { Some(*val) };
839    } else {
840        none_code = quote! { return Err(vim_rs::core::error::Error::missing_required_field(#path.to_string())) };
841        value_code = quote! { *val };
842    };
843    quote! {
844        #path => {
845            self.#field_name = match prop.val {
846                Some(vim_rs::types::vim_any::VimAny::Object(obj)) => {
847                    let name = obj.data_type().as_str();
848                    match obj.as_any_box().downcast() {
849                        Ok(val) => #value_code,
850                        Err(_) => return Err(vim_rs::core::error::Error::invalid_property_type( #path.to_string(), #struct_type.to_string(), name.to_string())),
851                    }
852                },
853                None => #none_code,
854                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))),
855            };
856        }
857    }
858}
859
860// 3. Generate trait type deserialize code for structs with children
861//                "<property path>" => {
862//                     <field name with ordinal> = match prop.val {
863//                         vim_rs::types::vim_any::VimAny::Object(obj) => {
864//                             let name = obj.data_type().as_str();
865//                             match obj.into_box() {
866//                                 Ok(val) => Some(val),
867//                                 Err(_) => return Err(pc_helpers::Error::InvalidPropertyType {property: "<property path>".to_string(), "<trait type name>".to_string(), name.to_string())),
868//                             }
869//                         },
870//                         ref val => return Err(pc_helpers::Error::InvalidPropertyType {property: "<property path>".to_string(), "<trait type name>".to_string(), got: pc_helpers::type_name(val)}),
871//                     };
872//                 }
873fn generate_trait_field_from_content(
874    field: &FieldInfo,
875    field_alias: &Ident,
876    trait_type: &str,
877) -> proc_macro2::TokenStream {
878    let path = &field.field_data.vim_path;
879    quote! {
880        #path => {
881            #field_alias = match prop.val {
882                vim_rs::types::vim_any::VimAny::Object(obj) => {
883                    let name = obj.data_type().as_str();
884                    match vim_rs::types::convert::CastInto::into_box(obj) {
885                        Ok(val) => Some(val),
886                        Err(_) => return Err(vim_rs::core::error::Error::invalid_property_type( #path.to_string(), #trait_type.to_string(), name.to_string())),
887                    }
888                },
889                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))),
890            };
891        }
892    }
893}
894
895fn generate_trait_field_from_update(
896    field: &FieldInfo,
897    field_alias: &Ident,
898    trait_type: &str,
899) -> proc_macro2::TokenStream {
900    let path = &field.field_data.vim_path;
901    quote! {
902        #path => {
903            #field_alias = match prop.val {
904                Some(vim_rs::types::vim_any::VimAny::Object(obj)) => {
905                    let name = obj.data_type().as_str();
906                    match vim_rs::types::convert::CastInto::into_box(obj) {
907                        Ok(val) => Some(val),
908                        Err(_) => return Err(vim_rs::core::error::Error::invalid_property_type( #path.to_string(), #trait_type.to_string(), name.to_string())),
909                    }
910                },
911                None => continue,
912                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))),
913            };
914        }
915    }
916}
917
918fn generate_trait_field_apply(field: &FieldInfo, trait_type: &str) -> proc_macro2::TokenStream {
919    let path = &field.field_data.vim_path;
920    let field_name = &field.property_field.name;
921    let none_code;
922    let value_code;
923    if field.field_data.is_optional {
924        none_code = quote! { None };
925        value_code = quote! { Some(val) };
926    } else {
927        none_code = quote! { return Err(vim_rs::core::error::Error::missing_required_field(#path.to_string())) };
928        value_code = quote! { val };
929    };
930
931    quote! {
932        #path => {
933            self.#field_name = match prop.val {
934                Some(vim_rs::types::vim_any::VimAny::Object(obj)) => {
935                    let name = obj.data_type().as_str();
936                    match vim_rs::types::convert::CastInto::into_box(obj) {
937                        Ok(val) => #value_code,
938                        Err(_) => return Err(vim_rs::core::error::Error::invalid_property_type( #path.to_string(), #trait_type.to_string(), name.to_string())),
939                    }
940                },
941                None => #none_code,
942                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))),
943            };
944        }
945    }
946}