use thiserror::Error;
use crate::field_data::lookup_field_data;
#[derive(Error, Debug, PartialEq, Eq)]
pub enum HierarchyError {
#[error("No properties found under node `{0}`")]
NoSubPropertiesAvailable(String),
#[error("Unsupported Object Type: `{0}`")]
UnsupportedObjectType(String),
#[error("Type `{0}` has no property field `{1}`")]
UnknownField(String, String),
}
pub type Result<T> = std::result::Result<T, HierarchyError>;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum FieldProcessingType {
Enum(&'static str),
Struct,
Trait,
}
#[derive(Debug, Clone)]
pub struct FieldData {
pub data_type: String,
pub is_optional: bool,
pub processing_type: FieldProcessingType,
pub vim_path: String,
pub doc: Option<&'static str>,
}
pub fn get_default_field_data() -> FieldData {
FieldData {
data_type: "String".to_string(),
is_optional: false,
processing_type: FieldProcessingType::Enum("PrimitiveString"),
vim_path: "name".to_string(),
doc: None,
}
}
#[derive(Clone, Debug)]
pub(crate) struct NodeData {
pub type_decl: &'static str,
pub type_name: &'static str,
pub is_optional: bool,
pub processing_type: FieldProcessingType,
pub path_segment: &'static str,
pub doc: Option<&'static str>,
}
pub fn resolve_path(managed_object: &str, path: &str) -> Result<FieldData> {
let tail = path.split('.').collect::<Vec<&str>>();
let mut obj: &str = managed_object;
let mut optional = false;
let mut path = Vec::new();
let mut node_data = &NodeData {
type_decl: "",
type_name: "",
is_optional: false,
processing_type: FieldProcessingType::Struct,
path_segment: "",
doc: None,
};
for segment in tail.iter() {
node_data = if let FieldProcessingType::Enum(enum_name) = node_data.processing_type {
if enum_name.starts_with("ArrayOf") && "length" == *segment {
&NodeData { type_decl: "i32", type_name: "", is_optional: false, processing_type: FieldProcessingType::Enum("PrimitiveInt"), path_segment: "length", doc: Some("Array length") }
} else {
return Err(HierarchyError::NoSubPropertiesAvailable(path.join(".").to_string()));
}
} else {
lookup_field_data(obj, segment)?
};
obj = node_data.type_name;
optional = optional | node_data.is_optional;
path.push(node_data.path_segment);
}
Ok(FieldData {
data_type: add_optional(&node_data.type_decl, optional),
is_optional: optional,
processing_type: node_data.processing_type.clone(),
vim_path: path.join("."),
doc: node_data.doc,
})
}
fn add_optional(type_decl: &str, is_optional: bool) -> String {
if is_optional {
format!("Option<{}>", type_decl)
} else {
type_decl.to_string()
}
}
#[cfg(test)]
mod tests {
use crate::resolver::{resolve_path, FieldProcessingType, HierarchyError};
#[test]
fn test_resolve_path() {
let path = "summary.guest.guest_full_name";
let result = resolve_path("VirtualMachine", path).unwrap();
assert_eq!(result.data_type, "Option<String>".to_string());
assert_eq!(result.vim_path, "summary.guest.guestFullName".to_string());
assert_eq!(result.is_optional, true);
assert_eq!(result.processing_type, FieldProcessingType::Enum("PrimitiveString"));
}
#[test]
fn test_resolve_path_struct() {
let path = "summary.guest";
let result = resolve_path("VirtualMachine", path).unwrap();
assert_eq!(result.data_type, "Option<vim_rs::types::structs::VirtualMachineGuestSummary>".to_string());
assert_eq!(result.vim_path, "summary.guest".to_string());
assert_eq!(result.is_optional, true);
assert_eq!(result.processing_type, FieldProcessingType::Struct);
}
#[test]
fn test_resolve_path_trait() {
let path = "config.ft_info";
let result = resolve_path("VirtualMachine", path).unwrap();
assert_eq!(result.data_type, "Option<Box<dyn vim_rs::types::traits::FaultToleranceConfigInfoTrait>>".to_string());
assert_eq!(result.vim_path, "config.ftInfo".to_string());
assert_eq!(result.is_optional, true);
assert_eq!(result.processing_type, FieldProcessingType::Trait);
}
#[test]
fn test_resolve_array() {
let path = "config.hardware.device";
let result = resolve_path("VirtualMachine", path).unwrap();
assert_eq!(result.data_type, "Option<Vec<Box<dyn vim_rs::types::traits::VirtualDeviceTrait>>>".to_string());
assert_eq!(result.vim_path, "config.hardware.device".to_string());
assert_eq!(result.is_optional, true);
assert_eq!(result.processing_type, FieldProcessingType::Enum("ArrayOfVirtualDevice"));
}
#[test]
fn test_resolve_array_length() {
let path = "config.hardware.device.length";
let result = resolve_path("VirtualMachine", path).unwrap();
assert_eq!(result.data_type, "Option<i32>".to_string());
assert_eq!(result.vim_path, "config.hardware.device.length".to_string());
assert_eq!(result.is_optional, true);
assert_eq!(result.processing_type, FieldProcessingType::Enum("PrimitiveInt"));
}
#[test]
fn test_resolve_array_length_vm() {
let path = "vm.length";
let result = resolve_path("Network", path).unwrap();
assert_eq!(result.data_type, "Option<i32>".to_string());
assert_eq!(result.vim_path, "vm.length".to_string());
assert_eq!(result.is_optional, true);
assert_eq!(result.processing_type, FieldProcessingType::Enum("PrimitiveInt"));
}
#[test]
fn test_resolve_path_invalid() {
let path = "summary.guest.guest_full_name.test";
let result = resolve_path("VirtualMachine", path);
assert_eq!(result.unwrap_err(), HierarchyError::NoSubPropertiesAvailable("summary.guest.guestFullName".to_string()));
}
#[test]
fn test_resolve_path_unknown_field() {
let path = "summary.guest.guest_name";
let result = resolve_path("VirtualMachine", path);
assert_eq!(result.unwrap_err(), HierarchyError::UnknownField("VirtualMachineGuestSummary".to_string(), "guest_name".to_string()));
}
#[test]
fn test_resolve_path_array_length_subprop() {
let path = "config.hardware.device.length.name";
let result = resolve_path("VirtualMachine", path);
assert_eq!(result.unwrap_err(), HierarchyError::NoSubPropertiesAvailable("config.hardware.device.length".to_string()));
}
#[test]
fn test_resolve_path_unknown_object() {
let path = "summary.guest.guest_full_name";
let result = resolve_path("VirtualShip", path);
assert_eq!(result.unwrap_err(), HierarchyError::UnsupportedObjectType("VirtualShip".to_string()));
}
}