From 9c8491f2b518851d441fa40571d4611970e2a94e Mon Sep 17 00:00:00 2001 From: Artem Goncharov Date: Thu, 19 Dec 2024 17:40:51 +0100 Subject: [PATCH] Adapt generated tui code - generate subtypes (necessary for non-GET request) - generate TryFrom for custom structs into Request and RequestBuilder - adapt metadata to generate few missed operations Change-Id: I0efd28b86bbd03f7aed2ece4552b30e91deac110 --- codegenerator/cli.py | 31 +-- codegenerator/common/rust.py | 16 ++ codegenerator/metadata/block_storage.py | 2 +- codegenerator/metadata/compute.py | 42 ++-- codegenerator/metadata/identity.py | 4 +- codegenerator/rust_cli.py | 2 +- codegenerator/rust_sdk.py | 9 +- codegenerator/rust_tui.py | 211 +++++++++++++++++++- codegenerator/templates/rust_macros.j2 | 7 +- codegenerator/templates/rust_tui/impl.rs.j2 | 75 +++++-- codegenerator/templates/rust_tui/mod.rs.j2 | 99 +++++++++ metadata/block-storage_metadata.yaml | 12 +- metadata/compute_metadata.yaml | 78 +++++++- metadata/identity_metadata.yaml | 2 + 14 files changed, 516 insertions(+), 74 deletions(-) create mode 100644 codegenerator/templates/rust_tui/mod.rs.j2 diff --git a/codegenerator/cli.py b/codegenerator/cli.py index e32b679..e53ce4d 100644 --- a/codegenerator/cli.py +++ b/codegenerator/cli.py @@ -217,7 +217,7 @@ def main(): metadata_path = Path(args.metadata) generator.load_metadata(metadata_path) # Resulting mod_paths - res_mods = [] + res_mods: list[tuple[list[str | None], str, str, str | None]] = [] for res, res_data in generator.metadata.resources.items(): if args.service and not res.startswith(args.service): @@ -243,7 +243,7 @@ def main(): ).resolve() ) - for mod_path, mod_name, path in generators[ + for mod_path, mod_name, path, class_name in generators[ args.target ].generate( res, @@ -252,7 +252,7 @@ def main(): operation_id=op_data.operation_id, args=op_args, ): - res_mods.append((mod_path, mod_name, path)) + res_mods.append((mod_path, mod_name, path, class_name)) rust_sdk_extensions = res_data.extensions.get("rust-sdk") if rust_sdk_extensions: additional_modules = rust_sdk_extensions.setdefault( @@ -269,17 +269,16 @@ def main(): ], mod, "", + None, ) ) if args.target in ["rust-sdk", "rust-tui"] and not args.resource: resource_results: dict[str, dict] = {} - for mod_path, mod_name, path in res_mods: + for mod_path, mod_name, path, class_name in res_mods: mn = "/".join(mod_path) - x = resource_results.setdefault( - mn, {"path": path, "mods": set()} - ) - x["mods"].add(mod_name) + x = resource_results.setdefault(mn, {"path": path, "mods": {}}) + x["mods"][mod_name] = class_name changed = True while changed: changed = False @@ -292,15 +291,15 @@ def main(): mn = "/".join(mod_path[0:-1]) mod_name = mod_path[-1] if mn in resource_results: - if mod_name not in resource_results[mn]["mods"]: - resource_results[mn]["mods"].add(mod_name) + if mod_name not in resource_results[mn]["mods"].keys(): + resource_results[mn]["mods"][mod_name] = None changed = True else: changed = True x = resource_results.setdefault( - mn, {"path": path, "mods": set()} + mn, {"path": path, "mods": {}} ) - x["mods"].add(mod_name) + x["mods"][mod_name] = None for path, gen_data in resource_results.items(): generators[args.target].generate_mod( @@ -308,7 +307,13 @@ def main(): path.split("/"), gen_data["mods"], gen_data["path"], - res.split(".")[-1].capitalize(), + "".join( + "".join( + y.title() + for z in path.split("/")[2:] + for y in z.split("_") + ) + ), service_name=path.split("/")[0], ) exit(0) diff --git a/codegenerator/common/rust.py b/codegenerator/common/rust.py index 8128b90..fde08aa 100644 --- a/codegenerator/common/rust.py +++ b/codegenerator/common/rust.py @@ -37,6 +37,14 @@ class Boolean(BasePrimitiveType): def get_sample(self): return "false" + def get_sdk_setter( + self, source_var_name: str, sdk_mod_path: str, into: bool = False + ) -> str: + if into: + return f"*{source_var_name}" + else: + return f"*{source_var_name}" + class Number(BasePrimitiveType): format: str | None = None @@ -146,6 +154,14 @@ class Option(BaseCombinedType): def get_sample(self): return self.item_type.get_sample() + def get_sdk_setter( + self, source_var_name: str, sdk_mod_path: str, into: bool = False + ) -> str: + if into: + return f"{self.item_type.get_sdk_setter(source_var_name, sdk_mod_path, into=into)}" + else: + return f"{source_var_name}.clone().map(Into::into)" + class Array(BaseCombinedType): base_type: str = "vec" diff --git a/codegenerator/metadata/block_storage.py b/codegenerator/metadata/block_storage.py index 011ae48..50851ca 100644 --- a/codegenerator/metadata/block_storage.py +++ b/codegenerator/metadata/block_storage.py @@ -134,7 +134,7 @@ class BlockStorageMetadata(MetadataBase): ].cli_full_command.replace("delete-all", "purge") if resource_name in ["backup", "snapshot", "volume"]: - if operation_name in ["list", "delete"]: + if operation_name in ["list_detailed", "delete"]: operation.targets.setdefault( "rust-tui", OperationTargetParams( diff --git a/codegenerator/metadata/compute.py b/codegenerator/metadata/compute.py index a2811cd..2ef4dff 100644 --- a/codegenerator/metadata/compute.py +++ b/codegenerator/metadata/compute.py @@ -193,27 +193,35 @@ class ComputeMetadata(MetadataBase): "rust-cli" ].cli_full_command.replace("delete-all", "purge") - if resource_name in [ - "aggregate", - "flavor", - "hypervisor", - "server", - "server/instance_action", - ]: - if operation_name in ["list", "delete", "show"]: - operation.targets.setdefault( - "rust-tui", - OperationTargetParams( - module_name=operation.targets["rust-sdk"].module_name - ), - ) - - if resource_name == "quota_set" and operation_name == "details": - operation.targets.setdefault( + if ( + ( + resource_name in ["aggregate", "server/instance_action"] + and operation_name in ["list", "delete", "show"] + ) + or ( + resource_name == "server" + and operation_name + in ["list_detailed", "delete", "show", "os-get-console-output"] + ) + or ( + resource_name in ["flavor", "hypervisor"] + and operation_name in ["list_detailed", "show"] + ) + or (resource_name == "quota_set" and operation_name == "details") + ): + op = operation.targets.setdefault( "rust-tui", OperationTargetParams( module_name=operation.targets["rust-sdk"].module_name ), ) + if operation_name == "os-get-console-output": + op.module_name = operation.targets[ + "rust-sdk" + ].module_name.replace("os_", "") + op.sdk_mod_name = operation.targets["rust-sdk"].module_name + op.operation_name = operation.targets[ + "rust-sdk" + ].operation_name return operation diff --git a/codegenerator/metadata/identity.py b/codegenerator/metadata/identity.py index 92eb8ba..282a443 100644 --- a/codegenerator/metadata/identity.py +++ b/codegenerator/metadata/identity.py @@ -149,7 +149,9 @@ class IdentityMetadata(MetadataBase): "user", "user/application_credential", ]: - if operation_name in ["list", "delete"]: + if operation_name in ["list", "delete"] or ( + resource_name == "user" and operation_name == "update" + ): operation.targets.setdefault( "rust-tui", OperationTargetParams( diff --git a/codegenerator/rust_cli.py b/codegenerator/rust_cli.py index 2e5d8c0..b5a550f 100644 --- a/codegenerator/rust_cli.py +++ b/codegenerator/rust_cli.py @@ -1538,4 +1538,4 @@ class RustCliGenerator(BaseGenerator): self._format_code(impl_path) - yield (cli_mod_path, mod_name, path) + yield (cli_mod_path, mod_name, path, None) diff --git a/codegenerator/rust_sdk.py b/codegenerator/rust_sdk.py index 905db47..869d8fc 100644 --- a/codegenerator/rust_sdk.py +++ b/codegenerator/rust_sdk.py @@ -123,6 +123,11 @@ class Struct(common_rust.Struct): """Return Rust `<'lc>` lifetimes representation""" return f"<{', '.join(self.lifetimes)}>" if self.lifetimes else "" + @property + def static_lifetime_anonymous(self): + """Return Rust `<'lc>` lifetimes representation""" + return self.static_lifetime.replace("'a", "'_") + def get_sample(self): res = [self.name + "Builder::default()"] for field in sorted(self.fields.values(), key=lambda d: d.local_name): @@ -505,7 +510,7 @@ class RustSdkGenerator(BaseGenerator): self._format_code(impl_path) - yield (mod_path, mod_name, path) + yield (mod_path, mod_name, path, class_name) def generate_mod( self, target_dir, mod_path, mod_list, url, resource_name, service_name @@ -591,4 +596,4 @@ class RustSdkGenerator(BaseGenerator): self._format_code(impl_path) - return (mod_path, "find", "dummy") + return (mod_path, "find", "dummy", "Request") diff --git a/codegenerator/rust_tui.py b/codegenerator/rust_tui.py index 7a5bbf8..b2ccb87 100644 --- a/codegenerator/rust_tui.py +++ b/codegenerator/rust_tui.py @@ -22,14 +22,154 @@ from codegenerator import model from codegenerator.common import BaseCompoundType from codegenerator.common import rust as common_rust from codegenerator.rust_sdk import TypeManager as SdkTypeManager +from codegenerator import rust_sdk class String(common_rust.String): type_hint: str = "String" + def get_sdk_setter( + self, source_var_name: str, sdk_mod_path: str, into: bool = False + ) -> str: + if into: + return f"{source_var_name}.into()" + else: + return f"{source_var_name}.clone()" + class ArrayInput(common_rust.Array): - pass + original_data_type: ( + common_rust.BaseCompoundType + | common_rust.BaseCombinedType + | common_rust.BasePrimitiveType + | None + ) = None + + def get_sdk_setter( + self, + source_var_name: str, + sdk_mod_path: str, + into: bool = False, + ord_num: int = 0, + ) -> str: + ord_num += 1 + result: str = source_var_name + if isinstance(self.item_type, common_rust.BaseCompoundType): + result += f".iter().flat_map(|x| TryFrom::try_from(x)).collect::>()" + elif isinstance(self.item_type, common_rust.BaseCombinedType): + if into: + result += ".iter()" + else: + result += ".iter().cloned()" + result += ( + f".map(|x{ord_num}| " + + self.item_type.get_sdk_setter( + f"x{ord_num}", sdk_mod_path, into=True + ) + + ").collect::>()" + ) + else: + if into: + result += ".into_iter()" + else: + result += ".iter().cloned()" + result += f".map(Into::into).collect::>()" + return result + + +class StructField(rust_sdk.StructField): + def get_sdk_setter( + self, + sdk_field: rust_sdk.StructField, + source_var: str, + dest_var: str, + sdk_mod_path: str, + into: bool = False, + ) -> str: + result: str = "" + source = "val" if self.is_optional else f"value.{self.local_name}" + if self.is_optional: + result += f"if let Some(val) = &{source_var}.{self.local_name} {{" + if isinstance(sdk_field.data_type, rust_sdk.Struct): + if not self.is_optional and not into: + source = f"&{source}" + result += ( + f"{dest_var}.{sdk_field.local_name}(TryInto::<{'::'.join(sdk_mod_path)}::{sdk_field.data_type.name}>::try_into(" + + source + + ")?);" + ) + else: + result += ( + f"{dest_var}.{sdk_field.local_name}(" + + self.data_type.get_sdk_setter( + source, sdk_mod_path, into=into + ) + + ");" + ) + if self.is_optional: + result += "}\n" + return result + + +class Struct(rust_sdk.Struct): + field_type_class_: Type[StructField] | StructField = StructField + original_data_type: BaseCompoundType | BaseCompoundType | None = None + is_required: bool = False + + @property + def static_lifetime(self): + """Return Rust `<'lc>` lifetimes representation""" + return f"<{', '.join(self.lifetimes)}>" if self.lifetimes else "" + + def get_sdk_builder_try_from( + self, sdk_struct: rust_sdk.Struct, sdk_mod_path: list[str] + ) -> str: + result: str = f"impl TryFrom<&{self.name}> for {'::'.join(sdk_mod_path)}::{sdk_struct.name}Builder{sdk_struct.static_lifetime_anonymous} {{" + result += "type Error = Report;\n" + result += ( + f"fn try_from(value: &{self.name}) -> Result {{" + ) + result += "let mut ep_builder = Self::default();\n" + + result += self.get_set_sdk_struct_fields( + sdk_struct, "value", "ep_builder", sdk_mod_path + ) + + result += "Ok(ep_builder)" + result += "}\n" + result += "}" + return result + + def get_set_sdk_struct_fields( + self, + sdk_struct: rust_sdk.Struct, + source_var: str, + dest_var: str, + sdk_mod_path: list[str], + ) -> str: + result: str = "" + + for (field, field_data), (_, sdk_field_data) in zip( + self.fields.items(), sdk_struct.fields.items() + ): + result += field_data.get_sdk_setter( + sdk_field_data, source_var, dest_var, sdk_mod_path, into=False + ) + + return result + + def get_sdk_type_try_from( + self, sdk_struct: rust_sdk.Struct, sdk_mod_path: list[str] + ) -> str: + result: str = f"impl TryFrom<&{self.name}> for {'::'.join(sdk_mod_path)}::{sdk_struct.name}{sdk_struct.static_lifetime_anonymous} {{" + result += "type Error = Report;\n" + result += f"fn try_from(value: &{self.name}) -> Result {{\n" + result += f"let ep_builder: {'::'.join(sdk_mod_path)}::{sdk_struct.name}Builder = TryFrom::try_from(value)?;\n" + + result += f'ep_builder.build().wrap_err("cannot prepare request element `{self.name}`")' + result += "}\n" + result += "}" + return result class TypeManager(common_rust.TypeManager): @@ -46,7 +186,7 @@ class TypeManager(common_rust.TypeManager): } data_type_mapping = { - model.Struct: common_rust.Struct, + model.Struct: Struct, model.Array: ArrayInput, model.CommaSeparatedList: ArrayInput, } @@ -55,6 +195,8 @@ class TypeManager(common_rust.TypeManager): common_rust.RequestParameter ) + sdk_type_manager: SdkTypeManager | None = None + def get_local_attribute_name(self, name: str) -> str: """Get localized attribute name""" name = name.replace(".", "_") @@ -69,6 +211,35 @@ class TypeManager(common_rust.TypeManager): """Get the attribute name on the SDK side""" return self.get_local_attribute_name(name) + def link_sdk_type_manager(self, sdk_type_manager: SdkTypeManager) -> None: + self.sdk_type_manager = sdk_type_manager + + def get_subtypes_with_sdk(self): + """Get all subtypes excluding TLA""" + for k, v in self.refs.items(): + if self.sdk_type_manager: + if k.name == "Body": + sdk_type = self.sdk_type_manager.get_root_data_type() + else: + sdk_type = self.sdk_type_manager.refs[k] + else: + sdk_type = None + if ( + k + and isinstance( + v, (common_rust.Enum, Struct, common_rust.StringEnum) + ) + and k.name != "Body" + ): + yield (v, sdk_type) + elif ( + k + and k.name != "Body" + and isinstance(v, self.option_type_class) + ): + if isinstance(v.item_type, common_rust.Enum): + yield (v.item_type, sdk_type) + class RustTuiGenerator(BaseGenerator): def __init__(self): @@ -170,7 +341,16 @@ class RustTuiGenerator(BaseGenerator): service_name = common.get_rust_service_type_from_str( args.service_type ) - class_name = f"{service_name}{res_name.title()}{args.operation_type.title()}".replace( + operation_name: str = ( + args.operation_type + if args.operation_type != "action" + else args.module_name + ) + operation_name = "".join( + x.title() + for x in re.split(r"[-_]", operation_name.replace("os-", "")) + ) + class_name = f"{service_name}{''.join(x.title() for x in path_resources)}{operation_name}".replace( "_", "" ) operation_body = operation_variant.get("body") @@ -212,6 +392,7 @@ class RustTuiGenerator(BaseGenerator): sdk_type_manager.set_models(all_types) # else: # logging.warn("Ignoring response type of action") + type_manager.link_sdk_type_manager(sdk_type_manager) if method == "patch": # There might be multiple supported mime types. We only select ones we are aware of @@ -258,7 +439,10 @@ class RustTuiGenerator(BaseGenerator): pass # response_def = (None,) response_key = None - sdk_mod_path_base = common.get_rust_sdk_mod_path( + sdk_mod_path_base = [ + "openstack_sdk", + "api", + ] + common.get_rust_sdk_mod_path( args.service_type, args.api_version, args.module_path or path ) sdk_mod_path: list[str] = sdk_mod_path_base.copy() @@ -267,9 +451,7 @@ class RustTuiGenerator(BaseGenerator): additional_imports = set() additional_imports.add( - "openstack_sdk::api::" - + "::".join(sdk_mod_path) - + "::RequestBuilder" + "::".join(sdk_mod_path) + "::RequestBuilder" ) additional_imports.add( "openstack_sdk::{AsyncOpenStack, api::QueryAsync}" @@ -323,7 +505,7 @@ class RustTuiGenerator(BaseGenerator): self._format_code(impl_path) - yield (mod_path, mod_name, path) + yield (mod_path, mod_name, path, class_name) def generate_mod( self, target_dir, mod_path, mod_list, url, resource_name, service_name @@ -336,9 +518,18 @@ class RustTuiGenerator(BaseGenerator): "/".join(mod_path[0:-1]), f"{mod_path[-1]}.rs", ) + service_name = "".join(x.title() for x in service_name.split("_")) + + new_mod_list: dict[str, dict[str, str]] = {} + for mod_name, class_name in mod_list.items(): + name = "".join(x.title() for x in mod_name.split("_")) + full_name = "".join(x.title() for x in mod_path[2:]) + name + if not class_name: + class_name = f"{service_name}{full_name}ApiRequest" + new_mod_list[mod_name] = {"name": name, "class_name": class_name} context = { - "mod_list": mod_list, + "mod_list": new_mod_list, "mod_path": mod_path, "url": url, "resource_name": resource_name, @@ -346,6 +537,6 @@ class RustTuiGenerator(BaseGenerator): } # Generate methods for the GET resource command - self._render_command(context, "rust_sdk/mod.rs.j2", impl_path) + self._render_command(context, "rust_tui/mod.rs.j2", impl_path) self._format_code(impl_path) diff --git a/codegenerator/templates/rust_macros.j2 b/codegenerator/templates/rust_macros.j2 index b2f5d63..b3c5d95 100644 --- a/codegenerator/templates/rust_macros.j2 +++ b/codegenerator/templates/rust_macros.j2 @@ -241,6 +241,11 @@ Some({{ val }}) {%- endfor %} {{ dst_var }}.{{ param.remote_name }}({{ builder_name }}.build().expect("A valid object")); +{%- elif param.data_type.__class__.__name__ == "Struct" %} + {% set builder_name = param.local_name + "_builder" %} + let {{ builder_name }}: openstack_sdk::api::{{ sdk_mod_path | join("::") }}::{{ param.data_type.name }}Builder = TryFrom::try_from(&{{ val_var}})?; + {{ dst_var }}.{{ param.remote_name }}({{ builder_name }}.build()?); + {%- elif param.data_type.__class__.__name__ == "String" %} {%- if is_nullable and not param.is_optional %} {{ dst_var }}.{{ param.remote_name }}({{ val_var | replace("&", "") }}.clone()); @@ -377,7 +382,7 @@ Some({{ val }}) {%- elif param["setter_type"] is defined %} {#- Param with setter present #} {{ dst_var }}.{{ param.remote_name }}( - {{ val_var }}{{ ".iter()" if not by_ref else ".into_iter().cloned()" }} + {{ val_var }}{{ ".iter()" if not by_ref else ".iter().cloned()" }} ); {%- elif original_item_type and original_item_type.__class__.__name__ == "DictionaryInput" %} use std::collections::BTreeMap; diff --git a/codegenerator/templates/rust_tui/impl.rs.j2 b/codegenerator/templates/rust_tui/impl.rs.j2 index 3444e30..e972cb6 100644 --- a/codegenerator/templates/rust_tui/impl.rs.j2 +++ b/codegenerator/templates/rust_tui/impl.rs.j2 @@ -15,7 +15,8 @@ // WARNING: This file is automatically generated from OpenAPI schema using // `openstack-codegenerator`. {% import 'rust_macros.j2' as macros with context -%} -use eyre::{Result, WrapErr}; +use derive_builder::Builder; +use eyre::{Result, WrapErr, Report}; use serde::{Deserialize, Serialize}; use std::fmt; use tokio::sync::mpsc::UnboundedSender; @@ -32,33 +33,68 @@ use {{ mod }}; {%- set sdk_data_type = sdk_type_manager.get_root_data_type() %} {%- if data_type.__class__.__name__ == "Struct" %} -#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Builder, Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[builder(setter(strip_option))] pub struct {{ class_name }}{ {%- for name, param in type_manager.parameters|dictsort if param.location != "header" %} - {{ param.local_name }}: {{ param.type_hint }}, - {%- if param.local_name.endswith("id") and (param.local_name[:-2] + "name") not in type_manager.parameters.keys() %} + {%- if param.type_hint.startswith("Option") %} + #[builder(default)] + {%- endif %} + pub {{ param.local_name }}: {{ param.type_hint }}, + {%- if param.local_name.endswith("id") and param.local_name not in ["uuid"] and (param.local_name[:-2] + "name") not in type_manager.parameters.keys() %} {%- set param_type = False %} - {{ param.local_name[:-2] }}name: + #[builder(default)] + pub {{ param.local_name[:-2] }}name: {%- if param.type_hint.startswith("Option") %} {{ param.type_hint }}, {%- else %} Option<{{ param.type_hint }}>, {%- endif %} {%- endif %} + +{%- if data_type["fields"] is defined %} +{#- Structure #} + {%- for name, field in data_type.fields | dictsort %} + {%- if field.data_type.__class__.__name__ in ["Struct"] %} + {{ macros.docstring(field.description, indent=4) }} + {{ field.local_name }}: {{ field.type_hint }}, + {%- endif %} + {%- endfor %} +{%- endif %} + {%- endfor %} } +{%- for type, sdk_type in type_manager.get_subtypes_with_sdk() %} +{%- if type["base_type"] == "struct" %} +/// {{ type.name }} data +#[derive(Builder, Debug, Default, Deserialize, Clone, Eq, PartialEq, Serialize)] +#[builder(setter(strip_option))] +pub struct {{ type.name }} { + {%- for _, field in type.fields | dictsort %} + {{ macros.docstring(field.description, indent=4) }} + {{ field.builder_macros }} + pub {{ field.local_name }}: {{ field.type_hint }}, + {%- endfor %} +} + +{{ type.get_sdk_builder_try_from(sdk_type, sdk_mod_path) }} +{{ type.get_sdk_type_try_from(sdk_type, sdk_mod_path) }} + +{%- endif %} +{% endfor %} + impl fmt::Display for {{ class_name }} { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut parts: Vec = Vec::new(); {%- for name, param in type_manager.parameters | dictsort %} {%- if param.location != "header" %} - {%- if param.local_name.endswith("id") %} + {%- if param.local_name.endswith("id") and param.local_name not in ["uuid"] %} {%- set alt_name = param.local_name[:-2] + "name" %} {%- if param.type_hint.startswith("Option") %} if self.{{ param.local_name }}.is_some() || self.{{ alt_name }}.is_some() { parts.push(format!( - "{{ param.local_name[:-3] or "name/id" }}: {}", + "{{ param.local_name[:-3] if param.local_name|length > 3 else "name/id" }}: {}", self.{{ alt_name }} .as_ref() .or(self.{{ param.local_name }}.as_ref()) @@ -69,7 +105,7 @@ impl fmt::Display for {{ class_name }} { } {% else %} parts.push(format!( - "{{ param.local_name[:-3] }}: {}", + "{{ param.local_name[:-3] if param.local_name|length > 3 else "name/id" }}: {}", self.{{ alt_name }} .clone() .unwrap_or(self.{{ param.local_name }}.clone()) @@ -82,8 +118,9 @@ impl fmt::Display for {{ class_name }} { } } -impl From<&{{ class_name }}> for RequestBuilder{{ "<'_>" if sdk_type_manager.get_request_static_lifetimes(data_type) else "" }} { - fn from(value: &{{ class_name }}) -> Self { +impl TryFrom<&{{ class_name }}> for RequestBuilder{{ "<'_>" if sdk_type_manager.get_request_static_lifetimes(data_type) else "" }} { + type Error = Report; + fn try_from(value: &{{ class_name }}) -> Result { let mut ep_builder = Self::default(); {%- for (k, v) in type_manager.get_parameters("path") %} @@ -112,7 +149,12 @@ impl From<&{{ class_name }}> for RequestBuilder{{ "<'_>" if sdk_type_manager.get {{ macros.set_request_data_from_input(type_manager, "ep_builder", v, "value" + v.local_name, by_ref=True )}} {%- endif %} {%- endfor %} - ep_builder + +{%- if data_type.__class__.__name__ == "Struct" %} +{{ data_type.get_set_sdk_struct_fields(sdk_data_type, "value", "ep_builder", sdk_mod_path) }} +{%- endif %} + + Ok(ep_builder) } } @@ -120,10 +162,10 @@ impl ExecuteApiRequest for {{ class_name }} { async fn execute_request( &self, session: &mut AsyncOpenStack, - request: &ApiRequest, - app_tx: &UnboundedSender, + {{ "_" if operation_type == "delete" else ""}}request: &ApiRequest, + {{ "_" if operation_type == "delete" else ""}}app_tx: &UnboundedSender, ) -> Result<(), CloudWorkerError> { - let ep = Into::::into(self) + let ep = TryInto::::try_into(self)? .build() .wrap_err("Cannot prepare request")?; {%- if operation_type == "list" %} @@ -140,6 +182,11 @@ impl ExecuteApiRequest for {{ class_name }} { request: request.clone(), data: ep.query_async(session).await?, })?; + {%- elif operation_type in ["action", "set"] %} + app_tx.send(Action::ApiResponseData { + request: request.clone(), + data: ep.query_async(session).await?, + })?; {%- elif operation_type == "delete" %} ignore(ep).query_async(session).await?; {%- endif %} diff --git a/codegenerator/templates/rust_tui/mod.rs.j2 b/codegenerator/templates/rust_tui/mod.rs.j2 new file mode 100644 index 0000000..4e71914 --- /dev/null +++ b/codegenerator/templates/rust_tui/mod.rs.j2 @@ -0,0 +1,99 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 +// +// WARNING: This file is automatically generated from OpenAPI schema using +// `openstack-codegenerator`. +{% if mod_path|length > 2 %} +//! `{{ url }}` REST operations bindings of {{ service_name }} +{%- else %} +//! `{{ service_name|capitalize }}` Service bindings +{%- endif %} + +use eyre::Result; +use serde::{Deserialize, Serialize}; +use tokio::sync::mpsc::UnboundedSender; + +use openstack_sdk::AsyncOpenStack; + +use crate::action::Action; +use crate::cloud_worker::common::CloudWorkerError; +use crate::cloud_worker::types::{ApiRequest, ExecuteApiRequest}; +{%- if mod_path | length > 2 %} +use crate::cloud_worker::{{ service_name }}ApiRequest; +{%- endif %} +{%- set prefix = service_name + resource_name %} + +{% for mod in mod_list|sort %} +pub mod {{ "r#" + mod if mod in ["trait", "type"] else mod }}; +{%- endfor %} + +{% for mod in mod_list|sort %} +pub use {{ "r#" + mod if mod in ["trait", "type"] else mod }}::*; +{%- endfor %} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum {{ prefix }}ApiRequest { +{%- for mod_name, mod_data in mod_list|dictsort %} + /// {{ mod_data.name }} + {{ mod_data.name }}(Box<{{ mod_data['class_name'] }}>), +{%- endfor %} +} + +impl From<{{ prefix }}ApiRequest> for ApiRequest { + fn from(item: {{ prefix }}ApiRequest) -> Self { + ApiRequest::{{ service_name }}({{ service_name }}ApiRequest::from(item)) + } +} + +{% for mod, mod_data in mod_list|dictsort -%} +{%- if mod not in ["list", "show", "get", "delete", "update", "create", "set", "details"] %} +impl From<{{ mod_data.class_name }}> for {{ prefix }}ApiRequest { + fn from(item: {{ mod_data.class_name }}) -> Self { + {{ prefix }}ApiRequest::{{ mod_data.name }}(Box::new({{ mod_data.class_name }}::from(item))) + } +} +{%- endif %} +{% endfor %} + +{%- if mod_path | length > 2 %} +{% for mod, mod_data in mod_list|dictsort -%} +{%- if mod not in ["list", "show", "get", "delete", "update", "create", "set", "details"] %} +impl From<{{ mod_data.class_name }}> for {{ service_name }}ApiRequest { + fn from(item: {{ mod_data.class_name }}) -> Self { + {{ service_name }}ApiRequest::{{ resource_name | title }}(Box::new({{ prefix }}ApiRequest::from(item))) + } +} +{%- endif %} +{% endfor %} +{%- endif %} + +impl ExecuteApiRequest for {{ prefix }}ApiRequest { + async fn execute_request( + &self, + session: &mut AsyncOpenStack, + request: &ApiRequest, + app_tx: &UnboundedSender, + ) -> Result<(), CloudWorkerError> { + match self { + {% for mod, mod_data in mod_list|dictsort -%} + {{ prefix }}ApiRequest::{{ mod_data.name }}(ref req) => { + req.execute_request(session, request, app_tx).await?; + } + {% endfor %} + } + Ok(()) + } +} + + diff --git a/metadata/block-storage_metadata.yaml b/metadata/block-storage_metadata.yaml index a63943e..250733b 100644 --- a/metadata/block-storage_metadata.yaml +++ b/metadata/block-storage_metadata.yaml @@ -28,14 +28,14 @@ resources: sdk_mod_name: list_detailed find_implemented_by_sdk: true cli_full_command: volume list + rust-tui: + module_name: list_detailed list: operation_id: volumes:get operation_type: list targets: rust-sdk: module_name: list - rust-tui: - module_name: list create: operation_id: volumes:post operation_type: create @@ -973,14 +973,14 @@ resources: sdk_mod_name: list_detailed find_implemented_by_sdk: true cli_full_command: snapshot list + rust-tui: + module_name: list_detailed list: operation_id: snapshots:get operation_type: list targets: rust-sdk: module_name: list - rust-tui: - module_name: list create: operation_id: snapshots:post operation_type: create @@ -1376,14 +1376,14 @@ resources: sdk_mod_name: list_detailed find_implemented_by_sdk: true cli_full_command: backup list + rust-tui: + module_name: list_detailed list: operation_id: backups:get operation_type: list targets: rust-sdk: module_name: list - rust-tui: - module_name: list create: operation_id: backups:post operation_type: create diff --git a/metadata/compute_metadata.yaml b/metadata/compute_metadata.yaml index efc226c..41555fe 100644 --- a/metadata/compute_metadata.yaml +++ b/metadata/compute_metadata.yaml @@ -57,8 +57,6 @@ resources: targets: rust-sdk: module_name: list - rust-tui: - module_name: list create: operation_id: flavors:post operation_type: create @@ -80,6 +78,8 @@ resources: sdk_mod_name: list_detailed find_implemented_by_sdk: true cli_full_command: flavor list + rust-tui: + module_name: list_detailed show: operation_id: flavors/id:get operation_type: show @@ -115,8 +115,6 @@ resources: sdk_mod_name: delete find_implemented_by_sdk: true cli_full_command: flavor delete - rust-tui: - module_name: delete add-tenant-access: operation_id: flavors/id/action:post operation_type: action @@ -273,6 +271,7 @@ resources: rust-cli: module_name: show sdk_mod_name: get + find_implemented_by_sdk: true cli_full_command: aggregate show rust-tui: module_name: get @@ -285,6 +284,7 @@ resources: rust-cli: module_name: set sdk_mod_name: set + find_implemented_by_sdk: true cli_full_command: aggregate set delete: operation_id: os-aggregates/id:delete @@ -295,6 +295,7 @@ resources: rust-cli: module_name: delete sdk_mod_name: delete + find_implemented_by_sdk: true cli_full_command: aggregate delete rust-tui: module_name: delete @@ -311,6 +312,7 @@ resources: sdk_mod_name: add_host operation_name: add_host response_key: aggregate + find_implemented_by_sdk: true cli_full_command: aggregate add-host remove-host: operation_id: os-aggregates/id/action:post @@ -325,6 +327,7 @@ resources: sdk_mod_name: remove_host operation_name: remove_host response_key: aggregate + find_implemented_by_sdk: true cli_full_command: aggregate remove-host set-metadata: operation_id: os-aggregates/id/action:post @@ -339,7 +342,18 @@ resources: sdk_mod_name: set_metadata operation_name: set_metadata response_key: aggregate + find_implemented_by_sdk: true cli_full_command: aggregate set-metadata + find: + operation_id: os-aggregates:get + operation_type: find + targets: + rust-sdk: + module_name: find + sdk_mod_path: compute::v2::aggregate + name_field: name + name_filter_supported: false + list_mod: list compute.aggregate/image: spec_file: wrk/openapi_specs/compute/v2.yaml api_version: v2 @@ -524,8 +538,6 @@ resources: targets: rust-sdk: module_name: list - rust-tui: - module_name: list list_detailed: operation_id: os-hypervisors/detail:get operation_type: list @@ -536,6 +548,8 @@ resources: module_name: list sdk_mod_name: list_detailed cli_full_command: hypervisor list + rust-tui: + module_name: list_detailed show: operation_id: os-hypervisors/id:get operation_type: show @@ -917,8 +931,6 @@ resources: targets: rust-sdk: module_name: list - rust-tui: - module_name: list create: operation_id: servers:post operation_type: create @@ -940,6 +952,8 @@ resources: sdk_mod_name: list_detailed find_implemented_by_sdk: true cli_full_command: server list + rust-tui: + module_name: list_detailed show: operation_id: servers/id:get operation_type: show @@ -1159,6 +1173,10 @@ resources: operation_name: os-getConsoleOutput find_implemented_by_sdk: true cli_full_command: server get-console-output + rust-tui: + module_name: get_console_output + sdk_mod_name: os_get_console_output + operation_name: os-getConsoleOutput create-backup: operation_id: servers/id/action:post operation_type: action @@ -1858,6 +1876,50 @@ resources: module_name: list sdk_mod_name: list cli_full_command: server security-groups + compute.server/share: + spec_file: wrk/openapi_specs/compute/v2.yaml + api_version: v2 + operations: + list: + operation_id: servers/server_id/shares:get + operation_type: list + targets: + rust-sdk: + module_name: list + rust-cli: + module_name: list + sdk_mod_name: list + cli_full_command: server share list + create: + operation_id: servers/server_id/shares:post + operation_type: create + targets: + rust-sdk: + module_name: create + rust-cli: + module_name: create + sdk_mod_name: create + cli_full_command: server share create + show: + operation_id: servers/server_id/shares/id:get + operation_type: show + targets: + rust-sdk: + module_name: get + rust-cli: + module_name: show + sdk_mod_name: get + cli_full_command: server share show + delete: + operation_id: servers/server_id/shares/id:delete + operation_type: delete + targets: + rust-sdk: + module_name: delete + rust-cli: + module_name: delete + sdk_mod_name: delete + cli_full_command: server share delete compute.server/tag: spec_file: wrk/openapi_specs/compute/v2.yaml api_version: v2 diff --git a/metadata/identity_metadata.yaml b/metadata/identity_metadata.yaml index 4b72770..6af0f8a 100644 --- a/metadata/identity_metadata.yaml +++ b/metadata/identity_metadata.yaml @@ -3042,6 +3042,8 @@ resources: sdk_mod_name: set find_implemented_by_sdk: true cli_full_command: user set + rust-tui: + module_name: set list: operation_id: users:get operation_type: list