{"id":27644940,"url":"https://github.com/noclue/vim_rs","last_synced_at":"2026-04-02T00:04:54.977Z","repository":{"id":282746029,"uuid":"949408915","full_name":"noclue/vim_rs","owner":"noclue","description":"Rust Bindings for the VMware by Broadcom vCenter VI JSON API","archived":false,"fork":false,"pushed_at":"2026-03-21T18:01:18.000Z","size":25222,"stargazers_count":30,"open_issues_count":0,"forks_count":3,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-03-22T04:31:21.739Z","etag":null,"topics":["rust-library","vcenter","vcenter-api","vsphere"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/noclue.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-03-16T11:55:04.000Z","updated_at":"2026-03-21T17:53:24.000Z","dependencies_parsed_at":"2025-04-12T14:35:47.987Z","dependency_job_id":"0c85abb3-53fc-4695-98b6-233fb39b2105","html_url":"https://github.com/noclue/vim_rs","commit_stats":null,"previous_names":["noclue/vim_rs"],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/noclue/vim_rs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noclue%2Fvim_rs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noclue%2Fvim_rs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noclue%2Fvim_rs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noclue%2Fvim_rs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/noclue","download_url":"https://codeload.github.com/noclue/vim_rs/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noclue%2Fvim_rs/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30835870,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-22T05:45:47.827Z","status":"ssl_error","status_checked_at":"2026-03-22T05:45:39.030Z","response_time":85,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["rust-library","vcenter","vcenter-api","vsphere"],"created_at":"2025-04-24T00:59:12.226Z","updated_at":"2026-04-02T00:04:54.963Z","avatar_url":"https://github.com/noclue.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# VMware vSphere API Client for Rust\n\nRust interface to the VMware vSphere VIM APIs, using the VI JSON API by default and an optional experimental SOAP/XML transport for direct ESXi connectivity.\n\n* **Fully Asynchronous**: Built on tokio runtime for efficient non-blocking operations\n* **Type-Safe**: Comprehensive Rust types for the vSphere API objects\n* **Macro System**: Simplify property retrieval and monitoring with vim_retrievable and vim_updatable macros\n* **Hybrid Type System**: Intelligently combines traits and enums to balance type safety with performance\n* **Documented**: The original VIM documentation rendered inline as rustdoc\n* **Examples**: A set of examples demonstrating use of the APIs to get you started quickly\n\nSee [`CHANGELOG.md`](CHANGELOG.md) for release notes and breaking changes.\n\n## 🤖 Supercharge your AI Assistant\n\n\u003e Sonnet 4.5: \"The MCP server doesn't just provide documentation—it provides understanding as a service. Semantic search + complete examples + common mistakes = AI that writes correct code on the first try.\"\n\n\u003e Gemini 3.0 Pro: \"The tools ... solved the hardest problem in vSphere development: Discovery. ... With them, I got the structure right on the second try.\"\n\nThe vSphere API is massive and deeply nested. We provide a **Model Context Protocol (MCP)** server that gives agents like **Claude Desktop** and **Cursor** semantic understanding of the `vim_rs` codebase.\n\nWith the MCP server, your AI assistant can:\n* **Semantic Search**: Find API methods using natural language (e.g., \"How do I power on a VM?\").\n* **Explore Properties**: Navigate the complex property paths (e.g., `summary.quick_stats.overall_cpu_usage`) without hallucinating.\n* **Get Examples**: Retrieve relevant usage examples for specific types.\n\n👉 **[Get Started with the vim_rs MCP Server](https://github.com/noclue/vim_rs/tree/main/mcp)**  \n*(Currently requires manual build from source. See instructions in the link.)*\n\n## Connecting to vCenter\n\nTo set up a connection, use a statement like the following:\n\n```rust\nuse vim_rs::core::ClientBuilder;\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c(), Box\u003cdyn std::error::Error\u003e\u003e {\n    // Create a client with username and password\n    let client = ClientBuilder::new(\"vcenter.example.com\")\n        .basic_authn(\"administrator@vsphere.local\", \"password\")\n        .app_details(env!(\"CARGO_PKG_NAME\"), env!(\"CARGO_PKG_VERSION\"))\n        .insecure(true) // For self-signed certs\n        .build()\n        .await?;\n\n     // Now you can use the client for API calls\n\n     Ok(())\n}\n``` \n`.app_details` describes your application as to be able to identify and troubleshoot sessions from the vCenter UI or SessionManager API.\n\nYou can add `insecure()` to your builder configuration to bypass TLS checks for both hostname and certificate.\n\nOne can set the `reqwest` preconfigured client through the builder's `http_client` method to reuse the `reqwest` connection and connection settings. The `vim_rs` client abstraction is cheap, but the `reqwest` HTTP client is not.\n\nThe `client` above is an `Arc` around the actual client object. Use `.clone()` to pass it around.\n\nIf the above goes well, you have a connection to the vCenter server with an initialized session and retrieved service content.\n\n## Optional XML transport\n\nThe default transport is VI JSON and behaves like `0.4.0`. XML support is an **optional, experimental**\nfeature that must be enabled explicitly and then selected through `ClientBuilder::transport()`.\n\nEnable the feature in `Cargo.toml`:\n\n```toml\nvim_rs = { version = \"0.4\", features = [\"xml\"] }\n```\n\nUse `TransportMode::Auto` when the target may be either vCenter or ESXi:\n\n```rust\nuse vim_rs::core::ClientBuilder;\nuse vim_rs::core::client::TransportMode;\n\nlet client = ClientBuilder::new(\"vc-or-esxi.example.com\")\n    .basic_authn(\"root\", \"password\")\n    .app_details(env!(\"CARGO_PKG_NAME\"), env!(\"CARGO_PKG_VERSION\"))\n    .insecure(true)\n    .transport(TransportMode::Auto)\n    .build()\n    .await?;\n```\n\nUse `TransportMode::Soap` when you know you need direct SOAP/XML, for example against ESXi:\n\n```rust\nuse vim_rs::core::ClientBuilder;\nuse vim_rs::core::client::TransportMode;\n\nlet client = ClientBuilder::new(\"esxi.example.com\")\n    .basic_authn(\"root\", \"password\")\n    .app_details(env!(\"CARGO_PKG_NAME\"), env!(\"CARGO_PKG_VERSION\"))\n    .insecure(true)\n    .transport(TransportMode::Soap)\n    .build()\n    .await?;\n```\n\nImportant caveats:\n\n- XML transport currently works only for the core VIM APIs.\n- Other APIs such as VSAN, SPBM/PBM, SMS, VSLM, and EAM currently return errors over XML.\n- XML support is experimental. If a call fails, enable `trace` logging for `vim_rs` and capture the failing request/response packets.\n- Enabling the `xml` feature increases release binary size by about 500 KB and increases debug build times by about 30-40%.\n- Disabling the `xml` feature restores `0.4.0` transport behavior, build times, and executable size characteristics.\n\n### XML transport caveats\n\nWhen XML transport is used, vim_rs does **not** use the Hello System API to negotiate the API\nrelease. Instead, it starts from the release used to build `vim_rs`.\n\n- If the remote server is older than `vim_rs`, it will use the server's latest supported release.\n- If the remote server is newer than `vim_rs`, it falls back to the `vim_rs` release automatically.\n- As a consequence, `client.api_release()` is **not** a reliable capability probe when XML transport is active.\n- To make decisions about remote capabilities, use `client.service_content().about.api_version` instead.\n\nOne additional deserialization caveat applies when the `xml` feature is enabled: if you manually\ndeserialize polymorphic JSON payloads, the `_typeName` key must appear before subtype-specific\nfields or deserialization can fail.\n\nManaged-object **property reads** over SOAP use `RetrievePropertiesEx` internally: the server\nresponse is parsed once into `VimAny`, and generated getters (e.g. `VirtualMachine::disabled_method`)\nnarrow to the concrete Rust type without an extra XML encode/decode round-trip.\n\nWhen you work with `VimAny` or `ValueElements` yourself, use `VimAny::into_any()` or\n`ValueElements::into_any()` and `downcast` to recover a concrete `T` instead of matching every\nvariant by hand.\n\n## Obtaining Stub for the APIs\nThe VIM API is a remote object-oriented API. The functionality is organized in methods of managed objects.\n\nTo set up a remote stub to a management object, one needs a connection, an object type, and an object identifier.\n\nFor example, to get a handle to stub for the default `PropertyCollector`, the following code does the trick:\n\n```rust\nlet content = client.service_content();\nlet property_collector = PropertyCollector::new(client.clone(), \u0026content.property_collector.value);\n```\n\nThe `service_content` is a structure that contains references to the root managed objects in the vCenter server. Note that the `PropertyCollector` is always present in the service content. Other objects may be optional, and a check is to be made if the reference is set.\n\n## Invoking APIs\nThis is simple and intuitive once you have a remote stub from the above step.\n\nThe VIM API has properties and methods. Both are exposed in the stubs. Properties are essentially remote methods that receive no parameters.\n\n```rust\n// Invoke a method\nlet events = collector.read_next_events(10).await?;\n\n// Fetch a property value\nlet vms = view.view().await?;\n```\n\nIn the examples above, `collector` is an instance of `PropertyCollector`, and `view` is an instance of a `View` like `ContainerView`.\n\n## Property Retrieval with Macros\n\nTwo powerful macros simplify working with vSphere properties. They are available directly from `vim_rs` (no separate `vim_macros` dependency needed):\n\n### One-time Property Retrieval with `vim_retrievable`\n\nDefine structures that map to vSphere object properties and retrieve them with a single call:\n\n```rust\nuse vim_rs::vim_retrievable;\nuse vim_rs::core::pc_retrieve::ObjectRetriever;\n\n// Define a struct mapping to HostSystem properties\nvim_retrievable!(\n    struct Host: HostSystem {\n        name = \"name\",\n        power_state = \"runtime.power_state\",\n        connected = \"runtime.connection_state\",\n        cpu_usage = \"summary.quick_stats.overall_cpu_usage\",\n        memory_usage = \"summary.quick_stats.overall_memory_usage\",\n        uptime = \"summary.quick_stats.uptime\",\n    }\n);\n\nasync fn print_hosts(client: \u0026Client) -\u003e Result\u003c()\u003e {\n   // Create a retriever using the client\n   let retriever = ObjectRetriever::new(client.clone())?;\n\n   // Retrieve all hosts with their properties in a single API call\n   let hosts: Vec\u003cHostInfo\u003e = retriever\n           .retrieve_objects_from_container(\u0026client.service_content().root_folder)\n           .await?;\n\n   // Work with strongly-typed host objects\n   for host in hosts {\n      println!(\"Host {} is {:?}\", host.name, host.power_state);\n   }\n\n   Ok(())\n}\n```\nThe object retriever also allows to pull objects from list of identifiers through \n`retrieve_objects_from_list`.\n\n### Continuous Property Monitoring with `vim_updatable`\n\nStay up to date with local inventory replica using `PropertyCollector::wait_for_updates_ex`:\n\n```rust\nvim_updatable!(\n    struct VmDetails: VirtualMachine {\n        name = \"name\",\n        power_state = \"runtime.power_state\",\n    }\n);\n\nimpl Display for VmDetails {\n   fn fmt(\u0026self, f: \u0026mut Formatter\u003c'_\u003e) -\u003e std::fmt::Result {\n      write!(\n         f,\n         \"VM ({}): {} with power state: {:?}\", self.id.value, self.name, self.power_state\n      )\n   }\n}\n\nstruct ChangeListener {}\n\nimpl ObjectCacheListener\u003cVmDetails\u003e for ChangeListener {\n   fn on_new(\u0026mut self, obj: \u0026VmDetails) -\u003e CacheAction {\n      info!(\"New VM: {}\", obj);\n      CacheAction::Keep\n   }\n\n   fn on_update(\u0026mut self, obj: \u0026VmDetails) -\u003e CacheAction {\n      info!(\"VM updated: {}\", obj);\n      CacheAction::Keep\n   }\n\n   fn on_remove(\u0026mut self, obj: VmDetails) {\n      info!(\"VM removed: {}\", obj);\n   }\n}\n\nasync fn monitor_vms(client: \u0026Arc\u003cClient\u003e) -\u003e Result\u003c(), Error\u003e {\n   let cache = Box::new(ObjectCache::new_with_listener(Box::new(ChangeListener {})));\n   let mut manager = CacheManager::new(client.clone())?;\n   let mut monitor = manager.create_monitor()?;\n\n   manager.add_container_cache(cache, \u0026client.service_content().root_folder).await?;\n\n   let start = Instant::now();\n   loop {\n      let updates = monitor.wait_updates(10).await?;\n      if let Some(updates) = updates {\n         manager.apply_updates(updates)?;\n      } \n      if start.elapsed().as_secs() \u003e 60 {\n         break;\n      }\n   }\n\n   manager.destroy().await?;\n   Ok(())\n}\n```\n`CacheManager` provides `add_list_cache` method to monitor a predefined list of objects.\n\n## Awaiting Task Completion\n\nMany vSphere operations are asynchronous and return a `Task` managed object reference (via methods ending in `*_Task`). The `TaskTracker` provides an efficient way to wait for these tasks to complete using the PropertyCollector.\n\n### Basic Usage\n\n```rust\nuse vim_rs::core::tasks::TaskTracker;\nuse vim_rs::mo::VirtualMachine;\n\nasync fn rename_vm(client: Arc\u003cClient\u003e, vm_ref: ManagedObjectReference, new_name: \u0026str) -\u003e Result\u003c()\u003e {\n    // Create a TaskTracker (reuse for multiple tasks)\n    let task_tracker = TaskTracker::new(client.clone());\n    \n    // Create a VirtualMachine proxy and call a *_Task method\n    let vm = VirtualMachine::new(client.clone(), \u0026vm_ref.value);\n    let task_ref = vm.rename_task(new_name).await?;\n    \n    // Wait for the task to complete\n    task_tracker.wait::\u003c()\u003e(task_ref).await?;\n    \n    Ok(())\n}\n```\n\n### Two APIs for Task Results\n\n`TaskTracker` provides two methods for awaiting task completion:\n\n**1. `wait::\u003cT\u003e()` - Convenient with Deserialization**\n\nUse when you know the expected result type. The result is automatically deserialized using the active transport (`miniserde` for JSON, SOAP/XML when XML transport is selected):\n\n```rust\n// For tasks that return no value (like rename, power operations)\ntask_tracker.wait::\u003c()\u003e(task_ref).await?;\n\n// For tasks that return a ManagedObjectReference (like create VM)\nlet vm_ref: ManagedObjectReference = task_tracker.wait(task_ref).await?;\n```\n\n**2. `wait_any()` - Zero-Allocation Path**\n\nUse when you want to avoid JSON conversion overhead and work with `VimAny` directly:\n\n```rust\nlet result: Option\u003cVimAny\u003e = task_tracker.wait_any(task_ref).await?;\nmatch result {\n    None =\u003e println!(\"Task completed with no return value\"),\n    Some(VimAny::Value(v)) =\u003e println!(\"Primitive result: {:?}\", v),\n    Some(VimAny::Object(o)) =\u003e println!(\"Object result: {:?}\", o.data_type()),\n}\n```\n\n### Key Points\n\n- **Create once, reuse**: Create a `TaskTracker` per `Client` and reuse it for multiple tasks.\n- **Efficient monitoring**: Uses PropertyCollector with a shared `ListView` to track tasks with minimal overhead.\n- **Automatic cleanup**: Tasks are automatically removed from tracking when they reach a terminal state.\n- **Error handling**: Failed tasks return an `Error::TaskFailed` containing the `MethodFault` details.\n- **Background loop**: The monitoring loop starts lazily on the first `wait_any()` call and stops when all tasks complete.\n\nSee [`examples/snippets/src/vm_rename.rs`](examples/snippets/src/vm_rename.rs) for a complete working example.\n\n## Client abstraction used by managed-object stubs\n\nManaged-object stubs in `vim_rs::mo` accept an `Arc\u003cdyn VimClient\u003e` internally. The concrete\n[`Client`](vim_rs/src/core/client.rs) implements this trait. Most callers can pass `client.clone()`.\n\n## Working with Polymorphic Types\nThe VIM API is conceptualized as a classic object-oriented API, much like the Java or C++ standard libraries. It has a root `Any` object from which all other objects descend. There is `DataObject` that is the root for all data structures. There is also `MethodFault` that is the root for all error types.\n\nThis object-oriented design is not native to Rust. There are two principal approaches in Rust to dealing with such situations - traits and enums. Enums are easy to deal with in Rust and are extremely powerful and very safe. Unfortunately, expressing the VIM API solely in enums produces very hard-to-use abstractions of many deeply nested enum definitions that are hard to work with. Traits solve some of the usability challenges while dramatically increasing the work for the Rust compiler, which is not famous for fast performance. So the `vim_rs` library takes a hybrid approach. The often deep and complex hierarchy of the `DataObject` and `MethodFault` are represented with traits. The shallow and big inventory of boxed arrays and primitive data types used with the property collector and other dynamic APIs leverage enums with the synthetic `ValueElements` types. `ValueElements::as_str()` returns the VIM API type name (e.g., `\"string\"`, `\"ArrayOfManagedObjectReference\"`) for type discrimination and logging. The VIM `Any` type is renamed to `VimAny` and is also represented as an `enum`.\n\nWorking with the trait system is a bit more complex. \n\nLet's look into the details.\n\n### Data Structs\n\nFirstly, for every structure type from the VIM API, we have a corresponding Rust struct type. The library uses **compositional inheritance** where child types contain their parent as a field and implement `Deref`/`DerefMut` for seamless field access.\n\nFor example, a network card could be described with the `VirtualE1000` structure:\n\n```rust\npub struct VirtualE1000 {\n    // Parent field - contains all VirtualEthernetCard fields (which contains VirtualDevice fields)\n    pub virtual_ethernet_card_: VirtualEthernetCard,\n}\n\nimpl Deref for VirtualE1000 {\n    type Target = VirtualEthernetCard;\n    fn deref(\u0026self) -\u003e \u0026Self::Target { \u0026self.virtual_ethernet_card_ }\n}\n\nimpl DerefMut for VirtualE1000 {\n    fn deref_mut(\u0026mut self) -\u003e \u0026mut Self::Target { \u0026mut self.virtual_ethernet_card_ }\n}\n```\n\nThanks to Deref coercion, you can access fields from the entire inheritance chain directly:\n\n```rust\nlet e1000: \u0026VirtualE1000 = /* ... */;\nlet key: i32 = e1000.key;                    // From VirtualDevice (grandparent)\nlet mac: \u0026Option\u003cString\u003e = \u0026e1000.mac_address; // From VirtualEthernetCard (parent)\n```\n\nFor reference, the parent `VirtualEthernetCard` structure looks like:\n\n```rust\npub struct VirtualEthernetCard {\n    pub virtual_device_: VirtualDevice,      // Parent field\n    pub address_type: Option\u003cString\u003e,\n    pub mac_address: Option\u003cString\u003e,\n    pub wake_on_lan_enabled: Option\u003cbool\u003e,\n    // ... other fields\n}\n```\n\nLet's look at some details. Fields optional in the API use Rust `Option` (e.g., `Option\u003ci32\u003e`) while required fields require a valid value (e.g., `i32`). Arrays are expressed as Rust `Vec`. For fields that have children or can form a cycle, `Box` indirection is used. For fields of polymorphic types, i.e., those that have children, a `dyn *Trait` type is used, which refers to a trait type implemented by all alternative structures (`Option\u003cBox\u003cdyn DescriptionTrait\u003e\u003e`).\n\nStructure types support `miniserde` JSON serialization and deserialization as well as debug print.\n\n### Traits\n\nTraits are generated for VIM structure types that have children types. The traits for a given type are implemented by all of its descendants. This allows the API to take in and return all possible types in a given field, i.e., casting an object to a trait its type implements is trivial in Rust.\n\nIn the Rust language unlike Java, Go, and C++ there is no straightforward mechanism to upcast or downcast trait objects into other trait objects or concrete structure types. To make these possible, the `vim_rs` crate provides utilities.\n\nFor casting to concrete structure types, all traits in the VIM API have `AsAny` trait bound. `AsAny` allows conversion of a reference or `Box` to a reference to `\u0026dyn Any` or `\u0026Box\u003cdyn Any\u003e`. Further, a developer can use `Any` or `Box` methods to attempt fallible conversion to the target type. For example, converting a `VirtualDeviceTrait` reference to a `VirtualE1000` structure is done as follows (unwrap should be replaced with appropriate handling):\n\n```rust\nlet e1000 = vd[0].as_any_ref().downcast_ref::\u003cVirtualE1000\u003e().unwrap();\n```\n\nSometimes we want to convert from one trait to another. For example, if we want to read the MAC address of any network card device in a VM, we need to convert `VirtualDeviceTrait` into `VirtualEthernetCardTrait`. There are 2 options provided with the `CastInto` trait. One option is to convert to `Box`, and the other is to convert a borrowed reference.\n\nIn the examples below, we see how to convert `Box\u003cdyn VirtualDevice\u003e` into a reference and `Box` respectively:\n\n```rust\nlet eth: \u0026dyn VirtualEthernetCardTrait = vd.as_ref().into_ref().unwrap();\n\nlet eth: Box\u003cdyn VirtualEthernetCardTrait\u003e = vd.into_box().unwrap();\n```\n\nAs Rust `TryInto` mirrors the `TryFrom` trait, `CastInto` has a mirror `CastFrom` trait.\n\nChild structs access parent fields through Deref coercion. For example, `VirtualE1000` contains a `virtual_ethernet_card_` field and implements `Deref\u003cTarget = VirtualEthernetCard\u003e`, which in turn derefs to `VirtualDevice`. This allows direct field access:\n\n```rust\n// Direct field access through Deref chain\nlet key: i32 = device.key;                           // From VirtualDevice\nlet mac: \u0026Option\u003cString\u003e = \u0026eth_card.mac_address;    // From VirtualEthernetCard\nlet backing = \u0026device.backing;                        // From VirtualDevice\n```\n\nThis compositional model provides the same ergonomic access as the previous expanded-fields approach while significantly reducing generated code size.\nThe new model supports both read and mutation operations and is thus much\neasier when an update is required.\n\nFor more details on design decisions and performance considerations, please see the FAQ section below.\n\n### Pruned Types\n\nAs discussed, the VIM API is big and has a deep inheritance hierarchy. To limit the size of the library, a number of optimizations and compromises are made. One specific optimization has a direct impact on the programming model. The descendant data types of `MethodFault` and `Event` types are not generated (See [PRUNED_TYPES](vim_build/src/main.rs)). This reduces the generated code and compilation times significantly at the cost of some utility.\n\nThe `MethodFault` type represents errors that can occur when invoking VIM API methods, and the `Event` type represents events that occur in the vCenter server.\n\nThe `MethodFault` and `Event` types do not have traits, and no descendant types are generated. Instead, both types receive 2 additional members:\n\n* `type_: Option\u003cStructType\u003e` - holding the discriminator value, e.g., `EventEx`, `NotFound`, etc.\n* `extra_fields_: HashMap\u003cString, miniserde::json::Value\u003e` - holding any data fields that are not present in the base type, e.g., `eventTypeId`.\n\nNote that `extra_fields_` content uses the API native names in camelCase convention instead of the Rust-friendly names used throughout `vim_rs`.\n\nBelow is a snippet on how to decode the semantic event type using `type_name_` and `extra_fields_`:\n\n```rust\nfn get_event_type_id(event: \u0026Event) -\u003e String {\n    let Some(type_) = event.type_ else {\n        return \"Event\".to_string();\n    };\n    if type_.child_of(StructType::EventEx) || type_.child_of(StructType::ExtendedEvent) {\n        if let Some(event_type_id) = event.extra_fields_[\"eventTypeId\"].as_str() {\n            return event_type_id.to_string();\n        }\n    }\n    type_.as_str().to_string()\n}\n```\n\nNote that `StructType` implements the `child_of` method, allowing to check if a type is the same or a descendant of another.\n\nIn our example above, we check if the event is `EventEx` or `ExtendedEvent` to access the `eventTypeId` field.\n\n## Optional `defaults` Feature\n\nTo simplify construction of vim_rs types, enable the opt-in `defaults` feature:\n\n```toml\nvim_rs = { version = \"0.4\", features = [\"defaults\"] }\n```\n\nThis provides `Default` implementations for all structs, enums, and trait objects, enabling struct update syntax:\n\n```rust\n// Without defaults: dozens of explicit None fields\n// With defaults: concise construction\nlet config_spec = VirtualMachineConfigSpec {\n    device_change: Some(device_changes),\n    ..Default::default()\n};\n```\n\nSee [`examples/snippets/src/vm_toggle_wol.rs`](examples/snippets/src/vm_toggle_wol.rs) for a complete example.\n\nSometimes one will want to convert part of the dynamic-like objects into proper binding. For example, the `managedObject` in the `ExtendedEvent` can be read into `ManagedObjectReference` as follows:\n\n```rust\nuse vim_rs::types::mini_helpers::from_value;\n\nlet value = event.extra_fields_[\"managedObject\"].clone();\nlet managed_object: ManagedObjectReference = from_value(value)?;\n```\n\n# Repo Topology \u0026 Maintenance\n\nThere are a few crates:\n\n1. `vim_rs` - the library code for calling the VIM API. Contains data types and stubs for the VIM API.\n2. `vim_build` - a code generation tool that reads VI-JSON OpenAPI specs and turns them into library code.\n3. `examples` - small programs demonstrating the use of the VIM API.\n4. `openapi30` - an indigenous OpenAPI 3.0.x data library used to load the OpenAPI docs. In addition to the base OpenAPI syntax, it allows for documentation strings on fields when those refer to other defined types in the spec. The library has some rudimentary validation logic. I tried to use ready OpenAPI Rust crates, and none had sufficient maturity. In some of the crates, data elements would be missing. In others, the model is tailored to a specific flavor of OpenAPI, making the use of VIM tedious, e.g., for lacking support around inheritance via `allOf`.\n\n## Generating Bindings\n\nTo generate new `vim` content, run `vim_build/src/main`.\n\n## Updating the OpenAPI Specification\n\nThe OpenAPI specification is held in `vim_build/data`. We use a JSON conversion of the original specification as `DatastoreAccessible_enum` with values `True` and `False` is ambiguously rendered in YAML, i.e., as per the YAML specs, the values are interpreted as boolean constants and not strings.\n\nTo add a newer version of the OpenAPI spec, use a tool like `yq` to convert to JSON, e.g., `yq -o=json eval vi_json_openapi_specification_v8_0_2_0.yaml`. Review if `DatastoreAccessible_enum` values are rendered as boolean flags or strings. The correct way is to have Strings - `True` and `False`.\n\n## `vim_rs` Structure\n\nThe `vim_rs` crate has a few packages worth understanding:\n\n1. `core` - `client.rs` contains an API client abstraction that manages the session header. `helpers.rs` is a utility to process base64 encoded values in JSON as `u8` arrays.\n2. `mo` - contains bindings for the individual managed object types. Managed object types in VIM have all the remote invocation endpoints. All the objects in `mo` require a `Client` to make HTTP calls.\n3. `types` - contains the definitions of the VIM data types. It is a bit unwieldy and is the main culprit for slow compilation and large executable size (40MB+ when optimized for size).\n    * `structs.rs` has all the struct types and is the big monstrosity.\n    * `defaults.rs` (with `defaults` feature) provides `Default` implementations for all types.\n    * `traits.rs` defines trait types for all VIM types that have children. In addition, there is logic for cross-trait conversion and JSON serialization.\n    * `enums.rs` contains all enum definitions.\n    * `struct_enum.rs` is an enum with all struct types that allows for relatively efficient type comparison operations.\n    * `vim_object_trait.rs` implements a common trait for all VIM data types.\n    * `deserialize.rs` and `dyn_serialize.rs` provide logic for polymorphic JSON serialization and deserialization, i.e., trait types.\n    * `boxed_types.rs` is an enum used for all boxed types, i.e., arrays of a given type used in any placeholder and primitives.\n    * `as_any.rs`, `vim_any.rs`, and `convert.rs` define utility types that are agnostic of code generation.\n\n## `vim_build` Structure\n\nThe generator has three packages:\n\n1. `vim_model` - contains an API model that is closer to the Rust semantics and logic to convert OpenAPI 3.0.x. The `loader.rs` converts the OpenAPI model to the Rust-like model.\n2. `rs_emitter` - contains code generation logic reading from `vim_model` and rendering the actual Rust code for the `vim` crate.\n3. `printer.rs` provides a basic wrapper on an output stream for code generation.\n\n## FAQ\n\n**Why aren’t standard traits like `PartialEq`, `Eq`, `Hash`, and `Clone` implemented on VIM struct types?**  \nBecause including these traits across the extensive VIM data model would greatly increase compilation time and binary size. We decided to implement only the essentials to keep build times and executable sizes under control. `Default` is available as an opt-in feature (see Optional `defaults` Feature above).\n\n**What are the expected compilation times for vim-tests?**  \nOn a good machine, the first-time compilation of vim-tests can take between 2 and 5 minutes, with subsequent compilations (especially from within an IDE) taking about a minute.\n\n**Why does the design use a hybrid approach with both traits and enums?**  \nThe VIM API is inherently polymorphic, and while enums are safe and idiomatic in Rust, using only enums would lead to unwieldy type definitions. The hybrid approach—with traits for the deep hierarchical parts and enums for simpler aspects—strikes a balance between performance and usability.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnoclue%2Fvim_rs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnoclue%2Fvim_rs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnoclue%2Fvim_rs/lists"}