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
This commit is contained in:
Artem Goncharov
2024-12-19 17:40:51 +01:00
parent 5d38233548
commit 9c8491f2b5
14 changed files with 516 additions and 74 deletions

View File

@@ -217,7 +217,7 @@ def main():
metadata_path = Path(args.metadata) metadata_path = Path(args.metadata)
generator.load_metadata(metadata_path) generator.load_metadata(metadata_path)
# Resulting mod_paths # 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(): for res, res_data in generator.metadata.resources.items():
if args.service and not res.startswith(args.service): if args.service and not res.startswith(args.service):
@@ -243,7 +243,7 @@ def main():
).resolve() ).resolve()
) )
for mod_path, mod_name, path in generators[ for mod_path, mod_name, path, class_name in generators[
args.target args.target
].generate( ].generate(
res, res,
@@ -252,7 +252,7 @@ def main():
operation_id=op_data.operation_id, operation_id=op_data.operation_id,
args=op_args, 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") rust_sdk_extensions = res_data.extensions.get("rust-sdk")
if rust_sdk_extensions: if rust_sdk_extensions:
additional_modules = rust_sdk_extensions.setdefault( additional_modules = rust_sdk_extensions.setdefault(
@@ -269,17 +269,16 @@ def main():
], ],
mod, mod,
"", "",
None,
) )
) )
if args.target in ["rust-sdk", "rust-tui"] and not args.resource: if args.target in ["rust-sdk", "rust-tui"] and not args.resource:
resource_results: dict[str, dict] = {} 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) mn = "/".join(mod_path)
x = resource_results.setdefault( x = resource_results.setdefault(mn, {"path": path, "mods": {}})
mn, {"path": path, "mods": set()} x["mods"][mod_name] = class_name
)
x["mods"].add(mod_name)
changed = True changed = True
while changed: while changed:
changed = False changed = False
@@ -292,15 +291,15 @@ def main():
mn = "/".join(mod_path[0:-1]) mn = "/".join(mod_path[0:-1])
mod_name = mod_path[-1] mod_name = mod_path[-1]
if mn in resource_results: if mn in resource_results:
if mod_name not in resource_results[mn]["mods"]: if mod_name not in resource_results[mn]["mods"].keys():
resource_results[mn]["mods"].add(mod_name) resource_results[mn]["mods"][mod_name] = None
changed = True changed = True
else: else:
changed = True changed = True
x = resource_results.setdefault( 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(): for path, gen_data in resource_results.items():
generators[args.target].generate_mod( generators[args.target].generate_mod(
@@ -308,7 +307,13 @@ def main():
path.split("/"), path.split("/"),
gen_data["mods"], gen_data["mods"],
gen_data["path"], 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], service_name=path.split("/")[0],
) )
exit(0) exit(0)

View File

@@ -37,6 +37,14 @@ class Boolean(BasePrimitiveType):
def get_sample(self): def get_sample(self):
return "false" 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): class Number(BasePrimitiveType):
format: str | None = None format: str | None = None
@@ -146,6 +154,14 @@ class Option(BaseCombinedType):
def get_sample(self): def get_sample(self):
return self.item_type.get_sample() 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): class Array(BaseCombinedType):
base_type: str = "vec" base_type: str = "vec"

View File

@@ -134,7 +134,7 @@ class BlockStorageMetadata(MetadataBase):
].cli_full_command.replace("delete-all", "purge") ].cli_full_command.replace("delete-all", "purge")
if resource_name in ["backup", "snapshot", "volume"]: if resource_name in ["backup", "snapshot", "volume"]:
if operation_name in ["list", "delete"]: if operation_name in ["list_detailed", "delete"]:
operation.targets.setdefault( operation.targets.setdefault(
"rust-tui", "rust-tui",
OperationTargetParams( OperationTargetParams(

View File

@@ -193,27 +193,35 @@ class ComputeMetadata(MetadataBase):
"rust-cli" "rust-cli"
].cli_full_command.replace("delete-all", "purge") ].cli_full_command.replace("delete-all", "purge")
if resource_name in [ if (
"aggregate", (
"flavor", resource_name in ["aggregate", "server/instance_action"]
"hypervisor", and operation_name in ["list", "delete", "show"]
"server", )
"server/instance_action", or (
]: resource_name == "server"
if operation_name in ["list", "delete", "show"]: and operation_name
operation.targets.setdefault( in ["list_detailed", "delete", "show", "os-get-console-output"]
"rust-tui", )
OperationTargetParams( or (
module_name=operation.targets["rust-sdk"].module_name resource_name in ["flavor", "hypervisor"]
), and operation_name in ["list_detailed", "show"]
) )
or (resource_name == "quota_set" and operation_name == "details")
if resource_name == "quota_set" and operation_name == "details": ):
operation.targets.setdefault( op = operation.targets.setdefault(
"rust-tui", "rust-tui",
OperationTargetParams( OperationTargetParams(
module_name=operation.targets["rust-sdk"].module_name 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 return operation

View File

@@ -149,7 +149,9 @@ class IdentityMetadata(MetadataBase):
"user", "user",
"user/application_credential", "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( operation.targets.setdefault(
"rust-tui", "rust-tui",
OperationTargetParams( OperationTargetParams(

View File

@@ -1538,4 +1538,4 @@ class RustCliGenerator(BaseGenerator):
self._format_code(impl_path) self._format_code(impl_path)
yield (cli_mod_path, mod_name, path) yield (cli_mod_path, mod_name, path, None)

View File

@@ -123,6 +123,11 @@ class Struct(common_rust.Struct):
"""Return Rust `<'lc>` lifetimes representation""" """Return Rust `<'lc>` lifetimes representation"""
return f"<{', '.join(self.lifetimes)}>" if self.lifetimes else "" 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): def get_sample(self):
res = [self.name + "Builder::default()"] res = [self.name + "Builder::default()"]
for field in sorted(self.fields.values(), key=lambda d: d.local_name): 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) self._format_code(impl_path)
yield (mod_path, mod_name, path) yield (mod_path, mod_name, path, class_name)
def generate_mod( def generate_mod(
self, target_dir, mod_path, mod_list, url, resource_name, service_name self, target_dir, mod_path, mod_list, url, resource_name, service_name
@@ -591,4 +596,4 @@ class RustSdkGenerator(BaseGenerator):
self._format_code(impl_path) self._format_code(impl_path)
return (mod_path, "find", "dummy") return (mod_path, "find", "dummy", "Request")

View File

@@ -22,14 +22,154 @@ from codegenerator import model
from codegenerator.common import BaseCompoundType from codegenerator.common import BaseCompoundType
from codegenerator.common import rust as common_rust from codegenerator.common import rust as common_rust
from codegenerator.rust_sdk import TypeManager as SdkTypeManager from codegenerator.rust_sdk import TypeManager as SdkTypeManager
from codegenerator import rust_sdk
class String(common_rust.String): class String(common_rust.String):
type_hint: str = "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): 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::<Vec<{'::'.join(sdk_mod_path)}::{self.item_type.name}>>()"
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::<Vec<_>>()"
)
else:
if into:
result += ".into_iter()"
else:
result += ".iter().cloned()"
result += f".map(Into::into).collect::<Vec<_>>()"
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<Self, Self::Error> {{"
)
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<Self, Self::Error> {{\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): class TypeManager(common_rust.TypeManager):
@@ -46,7 +186,7 @@ class TypeManager(common_rust.TypeManager):
} }
data_type_mapping = { data_type_mapping = {
model.Struct: common_rust.Struct, model.Struct: Struct,
model.Array: ArrayInput, model.Array: ArrayInput,
model.CommaSeparatedList: ArrayInput, model.CommaSeparatedList: ArrayInput,
} }
@@ -55,6 +195,8 @@ class TypeManager(common_rust.TypeManager):
common_rust.RequestParameter common_rust.RequestParameter
) )
sdk_type_manager: SdkTypeManager | None = None
def get_local_attribute_name(self, name: str) -> str: def get_local_attribute_name(self, name: str) -> str:
"""Get localized attribute name""" """Get localized attribute name"""
name = name.replace(".", "_") name = name.replace(".", "_")
@@ -69,6 +211,35 @@ class TypeManager(common_rust.TypeManager):
"""Get the attribute name on the SDK side""" """Get the attribute name on the SDK side"""
return self.get_local_attribute_name(name) 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): class RustTuiGenerator(BaseGenerator):
def __init__(self): def __init__(self):
@@ -170,7 +341,16 @@ class RustTuiGenerator(BaseGenerator):
service_name = common.get_rust_service_type_from_str( service_name = common.get_rust_service_type_from_str(
args.service_type 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") operation_body = operation_variant.get("body")
@@ -212,6 +392,7 @@ class RustTuiGenerator(BaseGenerator):
sdk_type_manager.set_models(all_types) sdk_type_manager.set_models(all_types)
# else: # else:
# logging.warn("Ignoring response type of action") # logging.warn("Ignoring response type of action")
type_manager.link_sdk_type_manager(sdk_type_manager)
if method == "patch": if method == "patch":
# There might be multiple supported mime types. We only select ones we are aware of # There might be multiple supported mime types. We only select ones we are aware of
@@ -258,7 +439,10 @@ class RustTuiGenerator(BaseGenerator):
pass pass
# response_def = (None,) # response_def = (None,)
response_key = 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 args.service_type, args.api_version, args.module_path or path
) )
sdk_mod_path: list[str] = sdk_mod_path_base.copy() sdk_mod_path: list[str] = sdk_mod_path_base.copy()
@@ -267,9 +451,7 @@ class RustTuiGenerator(BaseGenerator):
additional_imports = set() additional_imports = set()
additional_imports.add( additional_imports.add(
"openstack_sdk::api::" "::".join(sdk_mod_path) + "::RequestBuilder"
+ "::".join(sdk_mod_path)
+ "::RequestBuilder"
) )
additional_imports.add( additional_imports.add(
"openstack_sdk::{AsyncOpenStack, api::QueryAsync}" "openstack_sdk::{AsyncOpenStack, api::QueryAsync}"
@@ -323,7 +505,7 @@ class RustTuiGenerator(BaseGenerator):
self._format_code(impl_path) self._format_code(impl_path)
yield (mod_path, mod_name, path) yield (mod_path, mod_name, path, class_name)
def generate_mod( def generate_mod(
self, target_dir, mod_path, mod_list, url, resource_name, service_name self, target_dir, mod_path, mod_list, url, resource_name, service_name
@@ -336,9 +518,18 @@ class RustTuiGenerator(BaseGenerator):
"/".join(mod_path[0:-1]), "/".join(mod_path[0:-1]),
f"{mod_path[-1]}.rs", 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 = { context = {
"mod_list": mod_list, "mod_list": new_mod_list,
"mod_path": mod_path, "mod_path": mod_path,
"url": url, "url": url,
"resource_name": resource_name, "resource_name": resource_name,
@@ -346,6 +537,6 @@ class RustTuiGenerator(BaseGenerator):
} }
# Generate methods for the GET resource command # 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) self._format_code(impl_path)

View File

@@ -241,6 +241,11 @@ Some({{ val }})
{%- endfor %} {%- endfor %}
{{ dst_var }}.{{ param.remote_name }}({{ builder_name }}.build().expect("A valid object")); {{ 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" %} {%- elif param.data_type.__class__.__name__ == "String" %}
{%- if is_nullable and not param.is_optional %} {%- if is_nullable and not param.is_optional %}
{{ dst_var }}.{{ param.remote_name }}({{ val_var | replace("&", "") }}.clone()); {{ dst_var }}.{{ param.remote_name }}({{ val_var | replace("&", "") }}.clone());
@@ -377,7 +382,7 @@ Some({{ val }})
{%- elif param["setter_type"] is defined %} {%- elif param["setter_type"] is defined %}
{#- Param with setter present #} {#- Param with setter present #}
{{ dst_var }}.{{ param.remote_name }}( {{ 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" %} {%- elif original_item_type and original_item_type.__class__.__name__ == "DictionaryInput" %}
use std::collections::BTreeMap; use std::collections::BTreeMap;

View File

@@ -15,7 +15,8 @@
// WARNING: This file is automatically generated from OpenAPI schema using // WARNING: This file is automatically generated from OpenAPI schema using
// `openstack-codegenerator`. // `openstack-codegenerator`.
{% import 'rust_macros.j2' as macros with context -%} {% 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 serde::{Deserialize, Serialize};
use std::fmt; use std::fmt;
use tokio::sync::mpsc::UnboundedSender; use tokio::sync::mpsc::UnboundedSender;
@@ -32,33 +33,68 @@ use {{ mod }};
{%- set sdk_data_type = sdk_type_manager.get_root_data_type() %} {%- set sdk_data_type = sdk_type_manager.get_root_data_type() %}
{%- if data_type.__class__.__name__ == "Struct" %} {%- 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 }}{ pub struct {{ class_name }}{
{%- for name, param in type_manager.parameters|dictsort if param.location != "header" %} {%- for name, param in type_manager.parameters|dictsort if param.location != "header" %}
{{ param.local_name }}: {{ param.type_hint }}, {%- if param.type_hint.startswith("Option") %}
{%- if param.local_name.endswith("id") and (param.local_name[:-2] + "name") not in type_manager.parameters.keys() %} #[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 %} {%- set param_type = False %}
{{ param.local_name[:-2] }}name: #[builder(default)]
pub {{ param.local_name[:-2] }}name:
{%- if param.type_hint.startswith("Option") %} {%- if param.type_hint.startswith("Option") %}
{{ param.type_hint }}, {{ param.type_hint }},
{%- else %} {%- else %}
Option<{{ param.type_hint }}>, Option<{{ param.type_hint }}>,
{%- endif %} {%- endif %}
{%- 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 %} {%- 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 }} { impl fmt::Display for {{ class_name }} {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut parts: Vec<String> = Vec::new(); let mut parts: Vec<String> = Vec::new();
{%- for name, param in type_manager.parameters | dictsort %} {%- for name, param in type_manager.parameters | dictsort %}
{%- if param.location != "header" %} {%- 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" %} {%- set alt_name = param.local_name[:-2] + "name" %}
{%- if param.type_hint.startswith("Option") %} {%- if param.type_hint.startswith("Option") %}
if self.{{ param.local_name }}.is_some() || self.{{ alt_name }}.is_some() { if self.{{ param.local_name }}.is_some() || self.{{ alt_name }}.is_some() {
parts.push(format!( 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 }} self.{{ alt_name }}
.as_ref() .as_ref()
.or(self.{{ param.local_name }}.as_ref()) .or(self.{{ param.local_name }}.as_ref())
@@ -69,7 +105,7 @@ impl fmt::Display for {{ class_name }} {
} }
{% else %} {% else %}
parts.push(format!( parts.push(format!(
"{{ param.local_name[:-3] }}: {}", "{{ param.local_name[:-3] if param.local_name|length > 3 else "name/id" }}: {}",
self.{{ alt_name }} self.{{ alt_name }}
.clone() .clone()
.unwrap_or(self.{{ param.local_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 "" }} { impl TryFrom<&{{ class_name }}> for RequestBuilder{{ "<'_>" if sdk_type_manager.get_request_static_lifetimes(data_type) else "" }} {
fn from(value: &{{ class_name }}) -> Self { type Error = Report;
fn try_from(value: &{{ class_name }}) -> Result<Self, Self::Error> {
let mut ep_builder = Self::default(); let mut ep_builder = Self::default();
{%- for (k, v) in type_manager.get_parameters("path") %} {%- 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 )}} {{ macros.set_request_data_from_input(type_manager, "ep_builder", v, "value" + v.local_name, by_ref=True )}}
{%- endif %} {%- endif %}
{%- endfor %} {%- 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( async fn execute_request(
&self, &self,
session: &mut AsyncOpenStack, session: &mut AsyncOpenStack,
request: &ApiRequest, {{ "_" if operation_type == "delete" else ""}}request: &ApiRequest,
app_tx: &UnboundedSender<Action>, {{ "_" if operation_type == "delete" else ""}}app_tx: &UnboundedSender<Action>,
) -> Result<(), CloudWorkerError> { ) -> Result<(), CloudWorkerError> {
let ep = Into::<RequestBuilder>::into(self) let ep = TryInto::<RequestBuilder>::try_into(self)?
.build() .build()
.wrap_err("Cannot prepare request")?; .wrap_err("Cannot prepare request")?;
{%- if operation_type == "list" %} {%- if operation_type == "list" %}
@@ -140,6 +182,11 @@ impl ExecuteApiRequest for {{ class_name }} {
request: request.clone(), request: request.clone(),
data: ep.query_async(session).await?, 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" %} {%- elif operation_type == "delete" %}
ignore(ep).query_async(session).await?; ignore(ep).query_async(session).await?;
{%- endif %} {%- endif %}

View File

@@ -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<Action>,
) -> 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(())
}
}

View File

@@ -28,14 +28,14 @@ resources:
sdk_mod_name: list_detailed sdk_mod_name: list_detailed
find_implemented_by_sdk: true find_implemented_by_sdk: true
cli_full_command: volume list cli_full_command: volume list
rust-tui:
module_name: list_detailed
list: list:
operation_id: volumes:get operation_id: volumes:get
operation_type: list operation_type: list
targets: targets:
rust-sdk: rust-sdk:
module_name: list module_name: list
rust-tui:
module_name: list
create: create:
operation_id: volumes:post operation_id: volumes:post
operation_type: create operation_type: create
@@ -973,14 +973,14 @@ resources:
sdk_mod_name: list_detailed sdk_mod_name: list_detailed
find_implemented_by_sdk: true find_implemented_by_sdk: true
cli_full_command: snapshot list cli_full_command: snapshot list
rust-tui:
module_name: list_detailed
list: list:
operation_id: snapshots:get operation_id: snapshots:get
operation_type: list operation_type: list
targets: targets:
rust-sdk: rust-sdk:
module_name: list module_name: list
rust-tui:
module_name: list
create: create:
operation_id: snapshots:post operation_id: snapshots:post
operation_type: create operation_type: create
@@ -1376,14 +1376,14 @@ resources:
sdk_mod_name: list_detailed sdk_mod_name: list_detailed
find_implemented_by_sdk: true find_implemented_by_sdk: true
cli_full_command: backup list cli_full_command: backup list
rust-tui:
module_name: list_detailed
list: list:
operation_id: backups:get operation_id: backups:get
operation_type: list operation_type: list
targets: targets:
rust-sdk: rust-sdk:
module_name: list module_name: list
rust-tui:
module_name: list
create: create:
operation_id: backups:post operation_id: backups:post
operation_type: create operation_type: create

View File

@@ -57,8 +57,6 @@ resources:
targets: targets:
rust-sdk: rust-sdk:
module_name: list module_name: list
rust-tui:
module_name: list
create: create:
operation_id: flavors:post operation_id: flavors:post
operation_type: create operation_type: create
@@ -80,6 +78,8 @@ resources:
sdk_mod_name: list_detailed sdk_mod_name: list_detailed
find_implemented_by_sdk: true find_implemented_by_sdk: true
cli_full_command: flavor list cli_full_command: flavor list
rust-tui:
module_name: list_detailed
show: show:
operation_id: flavors/id:get operation_id: flavors/id:get
operation_type: show operation_type: show
@@ -115,8 +115,6 @@ resources:
sdk_mod_name: delete sdk_mod_name: delete
find_implemented_by_sdk: true find_implemented_by_sdk: true
cli_full_command: flavor delete cli_full_command: flavor delete
rust-tui:
module_name: delete
add-tenant-access: add-tenant-access:
operation_id: flavors/id/action:post operation_id: flavors/id/action:post
operation_type: action operation_type: action
@@ -273,6 +271,7 @@ resources:
rust-cli: rust-cli:
module_name: show module_name: show
sdk_mod_name: get sdk_mod_name: get
find_implemented_by_sdk: true
cli_full_command: aggregate show cli_full_command: aggregate show
rust-tui: rust-tui:
module_name: get module_name: get
@@ -285,6 +284,7 @@ resources:
rust-cli: rust-cli:
module_name: set module_name: set
sdk_mod_name: set sdk_mod_name: set
find_implemented_by_sdk: true
cli_full_command: aggregate set cli_full_command: aggregate set
delete: delete:
operation_id: os-aggregates/id:delete operation_id: os-aggregates/id:delete
@@ -295,6 +295,7 @@ resources:
rust-cli: rust-cli:
module_name: delete module_name: delete
sdk_mod_name: delete sdk_mod_name: delete
find_implemented_by_sdk: true
cli_full_command: aggregate delete cli_full_command: aggregate delete
rust-tui: rust-tui:
module_name: delete module_name: delete
@@ -311,6 +312,7 @@ resources:
sdk_mod_name: add_host sdk_mod_name: add_host
operation_name: add_host operation_name: add_host
response_key: aggregate response_key: aggregate
find_implemented_by_sdk: true
cli_full_command: aggregate add-host cli_full_command: aggregate add-host
remove-host: remove-host:
operation_id: os-aggregates/id/action:post operation_id: os-aggregates/id/action:post
@@ -325,6 +327,7 @@ resources:
sdk_mod_name: remove_host sdk_mod_name: remove_host
operation_name: remove_host operation_name: remove_host
response_key: aggregate response_key: aggregate
find_implemented_by_sdk: true
cli_full_command: aggregate remove-host cli_full_command: aggregate remove-host
set-metadata: set-metadata:
operation_id: os-aggregates/id/action:post operation_id: os-aggregates/id/action:post
@@ -339,7 +342,18 @@ resources:
sdk_mod_name: set_metadata sdk_mod_name: set_metadata
operation_name: set_metadata operation_name: set_metadata
response_key: aggregate response_key: aggregate
find_implemented_by_sdk: true
cli_full_command: aggregate set-metadata 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: compute.aggregate/image:
spec_file: wrk/openapi_specs/compute/v2.yaml spec_file: wrk/openapi_specs/compute/v2.yaml
api_version: v2 api_version: v2
@@ -524,8 +538,6 @@ resources:
targets: targets:
rust-sdk: rust-sdk:
module_name: list module_name: list
rust-tui:
module_name: list
list_detailed: list_detailed:
operation_id: os-hypervisors/detail:get operation_id: os-hypervisors/detail:get
operation_type: list operation_type: list
@@ -536,6 +548,8 @@ resources:
module_name: list module_name: list
sdk_mod_name: list_detailed sdk_mod_name: list_detailed
cli_full_command: hypervisor list cli_full_command: hypervisor list
rust-tui:
module_name: list_detailed
show: show:
operation_id: os-hypervisors/id:get operation_id: os-hypervisors/id:get
operation_type: show operation_type: show
@@ -917,8 +931,6 @@ resources:
targets: targets:
rust-sdk: rust-sdk:
module_name: list module_name: list
rust-tui:
module_name: list
create: create:
operation_id: servers:post operation_id: servers:post
operation_type: create operation_type: create
@@ -940,6 +952,8 @@ resources:
sdk_mod_name: list_detailed sdk_mod_name: list_detailed
find_implemented_by_sdk: true find_implemented_by_sdk: true
cli_full_command: server list cli_full_command: server list
rust-tui:
module_name: list_detailed
show: show:
operation_id: servers/id:get operation_id: servers/id:get
operation_type: show operation_type: show
@@ -1159,6 +1173,10 @@ resources:
operation_name: os-getConsoleOutput operation_name: os-getConsoleOutput
find_implemented_by_sdk: true find_implemented_by_sdk: true
cli_full_command: server get-console-output 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: create-backup:
operation_id: servers/id/action:post operation_id: servers/id/action:post
operation_type: action operation_type: action
@@ -1858,6 +1876,50 @@ resources:
module_name: list module_name: list
sdk_mod_name: list sdk_mod_name: list
cli_full_command: server security-groups 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: compute.server/tag:
spec_file: wrk/openapi_specs/compute/v2.yaml spec_file: wrk/openapi_specs/compute/v2.yaml
api_version: v2 api_version: v2

View File

@@ -3042,6 +3042,8 @@ resources:
sdk_mod_name: set sdk_mod_name: set
find_implemented_by_sdk: true find_implemented_by_sdk: true
cli_full_command: user set cli_full_command: user set
rust-tui:
module_name: set
list: list:
operation_id: users:get operation_id: users:get
operation_type: list operation_type: list