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 <artem.goncharov@gmail.com>
This commit is contained in:
Artem Goncharov
2025-11-14 13:33:55 +01:00
parent 2ff8837172
commit aa4d9e16b1
8 changed files with 39 additions and 23 deletions

View File

@@ -82,7 +82,7 @@ class JsonValue(common_rust.JsonValue):
if self.original_data_type and isinstance( if self.original_data_type and isinstance(
self.original_data_type, common_rust.Dictionary self.original_data_type, common_rust.Dictionary
): ):
imports.update(["std::collections::BTreeMap", "eyre::WrapErr"]) imports.update(["std::collections::BTreeMap"])
return imports return imports
@@ -807,7 +807,6 @@ class RustCliGenerator(BaseGenerator):
f"openstack_sdk::api::{'::'.join(link_res_name.split('/'))}::find" f"openstack_sdk::api::{'::'.join(link_res_name.split('/'))}::find"
f" as find_{link_res_name.split('/')[-1]}" f" as find_{link_res_name.split('/')[-1]}"
) )
global_additional_imports.add("eyre::OptionExt")
global_additional_imports.add("eyre::eyre") global_additional_imports.add("eyre::eyre")
# List of operation variants (based on the body) # List of operation variants (based on the body)

View File

@@ -23,6 +23,7 @@
{% import 'rust_macros.j2' as macros with context -%} {% import 'rust_macros.j2' as macros with context -%}
use clap::Args; use clap::Args;
use tracing::info; use tracing::info;
use eyre::{OptionExt, WrapErr};
use openstack_sdk::AsyncOpenStack; use openstack_sdk::AsyncOpenStack;
@@ -230,7 +231,7 @@ impl {{ target_class_name }}Command {
let mut regexes: Vec<Regex> = vec![ let mut regexes: Vec<Regex> = vec![
{%- for hdr, spec in resource_header_metadata.items() %} {%- for hdr, spec in resource_header_metadata.items() %}
{%- if "*" in hdr %} {%- if "*" in hdr %}
Regex::new(r"(?i){{ hdr | replace("*", "\.*") }}").unwrap(), Regex::new(r"(?i){{ hdr | replace("*", "\.*") }}").wrap_err("failed to compile the regex")?,
{%- endif %} {%- endif %}
{%- endfor %} {%- endfor %}
]; ];

View File

@@ -21,8 +21,15 @@
let size: u64 = headers let size: u64 = headers
.get("content-length") .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") .unwrap_or("0")
.parse() .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?; download_file(self.file.clone().unwrap_or(image_name), size, data).await?;

View File

@@ -2,10 +2,17 @@
let size: u64 = headers let size: u64 = headers
.get("content-length") .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") .unwrap_or("0")
.parse() .parse()
.unwrap(); .inspect_err(|e| tracing::warn!("content-length header mut represent u64 number: {}", e))
.unwrap_or_default();
download_file( download_file(
self.file.clone().unwrap_or(self.{{ last_path_parameter.name }}.clone()), self.file.clone().unwrap_or(self.{{ last_path_parameter.name }}.clone()),
size, size,

View File

@@ -3,7 +3,7 @@
// Patching resource requires fetching and calculating diff // Patching resource requires fetching and calculating diff
let resource_id = find_data["id"] let resource_id = find_data["id"]
.as_str() .as_str()
.expect("Resource ID is a string") .ok_or_else(|| eyre::eyre!("resource ID must be a string"))?
.to_string(); .to_string();
let data: {{ response_class_name }} = serde_json::from_value(find_data)?; let data: {{ response_class_name }} = serde_json::from_value(find_data)?;
@@ -46,8 +46,8 @@
{%- endif %} {%- endif %}
{%- endfor %} {%- endfor %}
let curr_json = serde_json::to_value(&data).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).unwrap(); let mut new_json = serde_json::to_value(&new).wrap_err("new state must be a valid json object")?;
{%- if root.additional_fields_type %} {%- if root.additional_fields_type %}
{#- additional properties are not present in the output and thus handleded on the raw json #} {#- additional properties are not present in the output and thus handleded on the raw json #}

View File

@@ -37,8 +37,7 @@
{%- else %} {%- else %}
.with_prompt("{{ k }}") .with_prompt("{{ k }}")
{%- endif %} {%- endif %}
.interact() .interact()?;
.unwrap();
{{ builder_name }}.{{ v.remote_name }}(secret.to_string()); {{ builder_name }}.{{ v.remote_name }}(secret.to_string());
} }
{%- else %} {%- else %}
@@ -46,7 +45,7 @@
{%- endif %} {%- endif %}
{% endfor %} {% 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 %} {% if root_field.is_optional %}
} }
{%- endif %} {%- endif %}

View File

@@ -264,7 +264,7 @@ Some({{ val }})
{%- endif %} {%- endif %}
{%- endfor %} {%- 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" %} {%- elif param.data_type.__class__.__name__ == "Struct" %}
{% set builder_name = param.local_name + "_builder" %} {% set builder_name = param.local_name + "_builder" %}
@@ -297,7 +297,7 @@ Some({{ val }})
{%- endif %} {%- endif %}
{%- endfor %} {%- 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" %} {%- elif param.data_type.item_type.__class__.__name__ == "JsonValue" %}
@@ -428,14 +428,16 @@ Some({{ val }})
use std::collections::BTreeMap; use std::collections::BTreeMap;
{{ dst_var }}.{{ param.remote_name }}( {{ dst_var }}.{{ param.remote_name }}(
{{ val_var }}.iter() {{ val_var }}.iter()
.map( |v| { .map(|v| {
v.as_object() v.as_object()
.expect("Is a valid Json object") .ok_or_eyre("{{ param.remote_name }} must be a valid json object")
.into_iter() .map(|obj| {
.map(|(k, v)| (k.into(), v.clone().into())) obj.into_iter()
.collect::<BTreeMap<_,Value>>() .map(|(k, v)| (k.into(), v.clone()))
.collect::<BTreeMap<_, Value>>()
}) })
.collect::<Vec<_>>() })
.collect::<Result<Vec<_>, _>>()?,
); );
{%- else %} {%- else %}
{#- Normal array #} {#- Normal array #}
@@ -550,13 +552,13 @@ Some({{ val }})
if let Some(val) = &self.path.{{ v.local_name }} { if let Some(val) = &self.path.{{ v.local_name }} {
{{ builder }}.{{ v.local_name }}(val); {{ builder }}.{{ v.local_name }}(val);
} else { } 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 %} {%- endif %}
{%- elif not find_mode and find_present and operation_type in ["show", "set", "download"] %} {%- elif not find_mode and find_present and operation_type in ["show", "set", "download"] %}
let resource_id = find_data["id"] let resource_id = find_data["id"]
.as_str() .as_str()
.expect("Resource ID is a string") .ok_or_else(|| eyre::eyre!("resource ID must be a string"))?
.to_string(); .to_string();
{{ builder }}.{{ v.local_name }}(resource_id.clone()); {{ builder }}.{{ v.local_name }}(resource_id.clone());
{%- else %} {%- else %}

View File

@@ -53,6 +53,7 @@ class TestRustCliResponseManager(TestCase):
use clap::Args; use clap::Args;
use tracing::info; use tracing::info;
use eyre::{OptionExt, WrapErr};
use openstack_sdk::AsyncOpenStack; use openstack_sdk::AsyncOpenStack;
use crate::output::OutputProcessor; use crate::output::OutputProcessor;
use crate::Cli; use crate::Cli;