From aa4d9e16b134bd1996daf718661ae84f42f3e774 Mon Sep 17 00:00:00 2001 From: Artem Goncharov Date: Fri, 14 Nov 2025 13:33:55 +0100 Subject: [PATCH] Replace use of `unwrap` and `expect` in generated rust code In order to eliminate rust sdk/cli/tui runtime panics we must get rid of `unwrap` and `expect` use (in tests it can stay). Lot of not generated code was already cleaned and now we need to address what is produced by the generator. Change-Id: Id9782fb947c61c64fc88bd743596e9996fa56b44 Signed-off-by: Artem Goncharov --- codegenerator/rust_cli.py | 3 +-- codegenerator/templates/rust_cli/impl.rs.j2 | 3 ++- .../templates/rust_cli/impl_image_download.j2 | 11 ++++++++-- .../templates/rust_cli/invoke_download.j2 | 11 ++++++++-- .../templates/rust_cli/invoke_patch.j2 | 6 ++--- .../templates/rust_cli/set_body_parameters.j2 | 5 ++--- codegenerator/templates/rust_macros.j2 | 22 ++++++++++--------- codegenerator/tests/unit/test_rust_cli.py | 1 + 8 files changed, 39 insertions(+), 23 deletions(-) diff --git a/codegenerator/rust_cli.py b/codegenerator/rust_cli.py index c6d533f..d4c47c0 100644 --- a/codegenerator/rust_cli.py +++ b/codegenerator/rust_cli.py @@ -82,7 +82,7 @@ class JsonValue(common_rust.JsonValue): if self.original_data_type and isinstance( self.original_data_type, common_rust.Dictionary ): - imports.update(["std::collections::BTreeMap", "eyre::WrapErr"]) + imports.update(["std::collections::BTreeMap"]) return imports @@ -807,7 +807,6 @@ class RustCliGenerator(BaseGenerator): f"openstack_sdk::api::{'::'.join(link_res_name.split('/'))}::find" f" as find_{link_res_name.split('/')[-1]}" ) - global_additional_imports.add("eyre::OptionExt") global_additional_imports.add("eyre::eyre") # List of operation variants (based on the body) diff --git a/codegenerator/templates/rust_cli/impl.rs.j2 b/codegenerator/templates/rust_cli/impl.rs.j2 index 5dbfe70..9021016 100644 --- a/codegenerator/templates/rust_cli/impl.rs.j2 +++ b/codegenerator/templates/rust_cli/impl.rs.j2 @@ -23,6 +23,7 @@ {% import 'rust_macros.j2' as macros with context -%} use clap::Args; use tracing::info; +use eyre::{OptionExt, WrapErr}; use openstack_sdk::AsyncOpenStack; @@ -230,7 +231,7 @@ impl {{ target_class_name }}Command { let mut regexes: Vec = vec![ {%- for hdr, spec in resource_header_metadata.items() %} {%- if "*" in hdr %} - Regex::new(r"(?i){{ hdr | replace("*", "\.*") }}").unwrap(), + Regex::new(r"(?i){{ hdr | replace("*", "\.*") }}").wrap_err("failed to compile the regex")?, {%- endif %} {%- endfor %} ]; diff --git a/codegenerator/templates/rust_cli/impl_image_download.j2 b/codegenerator/templates/rust_cli/impl_image_download.j2 index 079890e..3ff836e 100644 --- a/codegenerator/templates/rust_cli/impl_image_download.j2 +++ b/codegenerator/templates/rust_cli/impl_image_download.j2 @@ -21,8 +21,15 @@ let size: u64 = headers .get("content-length") - .map(|x| x.to_str().expect("Header is a string")) + .and_then(|x| { + x.to_str() + .inspect_err(|e| { + tracing::warn!("content-length header cannot be treated as string: {}", e) + }) + .ok() + }) .unwrap_or("0") .parse() - .unwrap(); + .inspect_err(|e| tracing::warn!("content-length header mut represent u64 number: {}", e)) + .unwrap_or_default(); download_file(self.file.clone().unwrap_or(image_name), size, data).await?; diff --git a/codegenerator/templates/rust_cli/invoke_download.j2 b/codegenerator/templates/rust_cli/invoke_download.j2 index 4edb2b2..86d1215 100644 --- a/codegenerator/templates/rust_cli/invoke_download.j2 +++ b/codegenerator/templates/rust_cli/invoke_download.j2 @@ -2,10 +2,17 @@ let size: u64 = headers .get("content-length") - .map(|x| x.to_str().expect("Header is a string")) + .and_then(|x| { + x.to_str() + .inspect_err(|e| { + tracing::warn!("content-length header cannot be treated as string: {}", e) + }) + .ok() + }) .unwrap_or("0") .parse() - .unwrap(); + .inspect_err(|e| tracing::warn!("content-length header mut represent u64 number: {}", e)) + .unwrap_or_default(); download_file( self.file.clone().unwrap_or(self.{{ last_path_parameter.name }}.clone()), size, diff --git a/codegenerator/templates/rust_cli/invoke_patch.j2 b/codegenerator/templates/rust_cli/invoke_patch.j2 index a7fcae2..c49ba0d 100644 --- a/codegenerator/templates/rust_cli/invoke_patch.j2 +++ b/codegenerator/templates/rust_cli/invoke_patch.j2 @@ -3,7 +3,7 @@ // Patching resource requires fetching and calculating diff let resource_id = find_data["id"] .as_str() - .expect("Resource ID is a string") + .ok_or_else(|| eyre::eyre!("resource ID must be a string"))? .to_string(); let data: {{ response_class_name }} = serde_json::from_value(find_data)?; @@ -46,8 +46,8 @@ {%- endif %} {%- endfor %} - let curr_json = serde_json::to_value(&data).unwrap(); - let mut new_json = serde_json::to_value(&new).unwrap(); + let curr_json = serde_json::to_value(&data).wrap_err("current state must be a valid json object")?; + let mut new_json = serde_json::to_value(&new).wrap_err("new state must be a valid json object")?; {%- if root.additional_fields_type %} {#- additional properties are not present in the output and thus handleded on the raw json #} diff --git a/codegenerator/templates/rust_cli/set_body_parameters.j2 b/codegenerator/templates/rust_cli/set_body_parameters.j2 index cbf5c0c..f5d3eb6 100644 --- a/codegenerator/templates/rust_cli/set_body_parameters.j2 +++ b/codegenerator/templates/rust_cli/set_body_parameters.j2 @@ -37,8 +37,7 @@ {%- else %} .with_prompt("{{ k }}") {%- endif %} - .interact() - .unwrap(); + .interact()?; {{ builder_name }}.{{ v.remote_name }}(secret.to_string()); } {%- else %} @@ -46,7 +45,7 @@ {%- endif %} {% endfor %} - ep_builder.{{ root_field.remote_name }}({{ builder_name }}.build().unwrap()); + ep_builder.{{ root_field.remote_name }}({{ builder_name }}.build().wrap_err("error preparing the request data")?); {% if root_field.is_optional %} } {%- endif %} diff --git a/codegenerator/templates/rust_macros.j2 b/codegenerator/templates/rust_macros.j2 index f84a747..1a1472e 100644 --- a/codegenerator/templates/rust_macros.j2 +++ b/codegenerator/templates/rust_macros.j2 @@ -264,7 +264,7 @@ Some({{ val }}) {%- endif %} {%- endfor %} - {{ dst_var }}.{{ param.remote_name }}({{ builder_name }}.build().expect("A valid object")); + {{ dst_var }}.{{ param.remote_name }}({{ builder_name }}.build().wrap_err("error preparing the request data")?); {%- elif param.data_type.__class__.__name__ == "Struct" %} {% set builder_name = param.local_name + "_builder" %} @@ -297,7 +297,7 @@ Some({{ val }}) {%- endif %} {%- endfor %} - {{ dst_var }}.{{ param.remote_name }}({{ builder_name }}.build().expect("A valid object")); + {{ dst_var }}.{{ param.remote_name }}({{ builder_name }}.build().wrap_err("error preparing the request data")?); } {%- elif param.data_type.item_type.__class__.__name__ == "JsonValue" %} @@ -428,14 +428,16 @@ Some({{ val }}) use std::collections::BTreeMap; {{ dst_var }}.{{ param.remote_name }}( {{ val_var }}.iter() - .map( |v| { + .map(|v| { v.as_object() - .expect("Is a valid Json object") - .into_iter() - .map(|(k, v)| (k.into(), v.clone().into())) - .collect::>() + .ok_or_eyre("{{ param.remote_name }} must be a valid json object") + .map(|obj| { + obj.into_iter() + .map(|(k, v)| (k.into(), v.clone())) + .collect::>() + }) }) - .collect::>() + .collect::, _>>()?, ); {%- else %} {#- Normal array #} @@ -550,13 +552,13 @@ Some({{ val }}) if let Some(val) = &self.path.{{ v.local_name }} { {{ builder }}.{{ v.local_name }}(val); } else { - {{ builder }}.{{ v.local_name }}(client.get_current_project().expect("Project ID must be known").id); + {{ builder }}.{{ v.local_name }}(client.get_current_project().wrap_err("project ID must be known")?.id); } {%- endif %} {%- elif not find_mode and find_present and operation_type in ["show", "set", "download"] %} let resource_id = find_data["id"] .as_str() - .expect("Resource ID is a string") + .ok_or_else(|| eyre::eyre!("resource ID must be a string"))? .to_string(); {{ builder }}.{{ v.local_name }}(resource_id.clone()); {%- else %} diff --git a/codegenerator/tests/unit/test_rust_cli.py b/codegenerator/tests/unit/test_rust_cli.py index 6e1b9e3..af02397 100644 --- a/codegenerator/tests/unit/test_rust_cli.py +++ b/codegenerator/tests/unit/test_rust_cli.py @@ -53,6 +53,7 @@ class TestRustCliResponseManager(TestCase): use clap::Args; use tracing::info; +use eyre::{OptionExt, WrapErr}; use openstack_sdk::AsyncOpenStack; use crate::output::OutputProcessor; use crate::Cli;