diff --git a/codegenerator/common/rust.py b/codegenerator/common/rust.py index 94a9dbd..0a69158 100644 --- a/codegenerator/common/rust.py +++ b/codegenerator/common/rust.py @@ -75,7 +75,7 @@ def get_sample(self): class Null(BasePrimitiveType): type_hint: str = "Value" imports: set[str] = set(["serde_json::Value"]) - builder_macros: set[str] = set(['default = "Value::Null"']) + builder_macros: set[str] = set([]) clap_macros: set[str] = set() original_data_type: BaseCompoundType | BaseCompoundType | None = None @@ -352,11 +352,15 @@ def get_sample(self): def variant_serde_macros(self, variant: str): """Return serde macros""" - return ( - "#[serde(" - + ", ".join([f'rename="{x}"' for x in self.variants[variant]]) - + ")]" - ) + macros = set([]) + vals = self.variants[variant] + if len(vals) > 1: + macros.add(f'rename(serialize = "{sorted(vals)[0]}")') + for val in vals: + macros.add(f'alias="{val}"') + else: + macros.add(f'rename = "{list(vals)[0]}"') + return "#[serde(" + ", ".join(sorted(macros)) + ")]" class RequestParameter(BaseModel): @@ -762,6 +766,13 @@ def _simplify_oneof_combinations(self, type_model, kinds): kinds.clear() jsonval_klass = self.primitive_type_mapping[model.PrimitiveAny] kinds.append({"local": jsonval_klass(), "class": jsonval_klass}) + elif len(set(kinds_classes)) == 1 and string_klass in kinds_classes: + # in the output oneOf of same type (but maybe different formats) + # makes no sense + # Example is server addresses which are ipv4 or ipv6 + bck = kinds[0].copy() + kinds.clear() + kinds.append(bck) def set_models(self, models): """Process (translate) ADT models into Rust SDK style""" @@ -998,6 +1009,8 @@ def get_operation_variants(spec: dict, operation_name: str): elif "application/json-patch+json" in content: mime_type = "application/json-patch+json" operation_variants.append({"mime_type": mime_type}) + elif content == {}: + operation_variants.append({"body": None}) else: # Explicitly register variant without body operation_variants.append({"body": None}) diff --git a/codegenerator/common/schema.py b/codegenerator/common/schema.py index 1f792d5..d6b1034 100644 --- a/codegenerator/common/schema.py +++ b/codegenerator/common/schema.py @@ -24,6 +24,7 @@ class TypeSchema(BaseModel): type: Optional[str | List[str]] = None format: Optional[str] = None description: Optional[str] = None + summary: str | None = None default: Optional[Any] = None items: Optional[Dict[str, Any]] = None # circular reference cause issues on deserializing diff --git a/codegenerator/metadata.py b/codegenerator/metadata.py index c76aa58..9c296bd 100644 --- a/codegenerator/metadata.py +++ b/codegenerator/metadata.py @@ -59,6 +59,13 @@ def generate( resource_name = "/".join( [x for x in common.get_resource_names_from_url(path)] ) + if args.service_type == "object-store": + if path == "/v1/{account}": + resource_name = "account" + elif path == "/v1/{account}/{container}": + resource_name = "container" + if path == "/v1/{account}/{object}": + resource_name = "object" if args.service_type == "compute" and resource_name in [ "agent", "baremetal_node", @@ -84,6 +91,8 @@ def generate( "security_group_default_rule", "security_group_rule", "security_group", + "server/console", + "server/virtual_interface", "snapshot", "tenant_network", "volume", @@ -173,6 +182,18 @@ def generate( and method == "post" ): operation_key = "action" + elif ( + args.service_type == "compute" + and resource_name == "server/security_group" + and method == "get" + ): + operation_key = "list" + elif ( + args.service_type == "compute" + and resource_name == "server/topology" + and method == "get" + ): + operation_key = "list" elif response_schema and ( method == "get" @@ -623,6 +644,24 @@ def post_process_compute_operation( elif resource_name == "keypair": if operation_name == "list": operation.targets["rust-sdk"].response_list_item_key = "keypair" + elif resource_name == "server/instance_action": + if operation_name == "list": + operation.targets["rust-sdk"].response_key = "instanceActions" + operation.targets["rust-cli"].response_key = "instanceActions" + else: + operation.targets["rust-sdk"].response_key = "instanceAction" + operation.targets["rust-cli"].response_key = "instanceAction" + elif resource_name == "server/topology": + if operation_name == "list": + operation.targets["rust-sdk"].response_key = "nodes" + operation.targets["rust-cli"].response_key = "nodes" + elif resource_name == "server/volume_attachment": + if operation_name == "list": + operation.targets["rust-sdk"].response_key = "volumeAttachments" + operation.targets["rust-cli"].response_key = "volumeAttachments" + elif operation_name in ["create", "show", "update"]: + operation.targets["rust-sdk"].response_key = "volumeAttachment" + operation.targets["rust-cli"].response_key = "volumeAttachment" return operation diff --git a/codegenerator/model.py b/codegenerator/model.py index d212903..e3bb4ce 100644 --- a/codegenerator/model.py +++ b/codegenerator/model.py @@ -449,7 +449,19 @@ def parse_typelist( parent_name: str | None = None, ignore_read_only: bool | None = False, ): + if len(schema.get("type")) == 1: + # Bad schema with type being a list of 1 entry + schema["type"] = schema["type"][0] + obj = self.parse_schema( + schema, + results, + name=name, + ignore_read_only=ignore_read_only, + ) + return obj + obj = OneOfType() + for kind_type in schema.get("type"): kind_schema = copy.deepcopy(schema) kind_schema["type"] = kind_type diff --git a/codegenerator/openapi/base.py b/codegenerator/openapi/base.py index 3de5cca..261f893 100644 --- a/codegenerator/openapi/base.py +++ b/codegenerator/openapi/base.py @@ -380,6 +380,8 @@ def _process_route( # Versioned actions in nova can be themelves as a # version_select wrapped callable (i.e. baremetal.action) key = closurevars.nonlocals.get("key", None) + slf = closurevars.nonlocals.get("self", None) + if key and key in versioned_methods: # ACTION with version bounds if len(versioned_methods[key]) > 1: @@ -392,6 +394,20 @@ def _process_route( end_version = ver_method.end_version func = ver_method.func logging.info("Versioned action %s", func) + elif slf and key: + vm = getattr(slf, "versioned_methods", None) + if vm and key in vm: + # ACTION with version bounds + if len(vm[key]) > 1: + raise RuntimeError( + "Multiple versioned methods for action %s", + action, + ) + for ver_method in vm[key]: + start_version = ver_method.start_version + end_version = ver_method.end_version + func = ver_method.func + logging.info("Versioned action %s", func) else: func = op_name @@ -551,8 +567,8 @@ def process_operation( while hasattr(f, "__wrapped__"): closure = inspect.getclosurevars(f) closure_locals = closure.nonlocals - min_ver = closure_locals.get("min_version") - max_ver = closure_locals.get("max_version") + min_ver = closure_locals.get("min_version", start_version) + max_ver = closure_locals.get("max_version", end_version) if "errors" in closure_locals: expected_errors = closure_locals["errors"] @@ -831,7 +847,7 @@ def process_body_parameters( if cont_schema_name in openapi_spec.components.schemas: # if we have already oneOf - add there cont_schema = openapi_spec.components.schemas[cont_schema_name] - if body_schemas[0] not in [ + if cont_schema.oneOf and body_schemas[0] not in [ x["$ref"] for x in cont_schema.oneOf ]: cont_schema.oneOf.append({"$ref": body_schemas[0]}) diff --git a/codegenerator/openapi/nova.py b/codegenerator/openapi/nova.py index 6143ee2..0fe4f91 100644 --- a/codegenerator/openapi/nova.py +++ b/codegenerator/openapi/nova.py @@ -594,6 +594,12 @@ def _get_schema_ref( name, TypeSchema(**nova_schemas.SERVER_TOPOLOGY_SCHEMA) ) ref = f"#/components/schemas/{name}" + elif name == "ServersOs_Security_GroupsListResponse": + schema = openapi_spec.components.schemas.setdefault( + name, + TypeSchema(**nova_schemas.SERVER_SECURITY_GROUPS_LIST_SCHEMA), + ) + ref = f"#/components/schemas/{name}" elif name in [ "ServersTagsListResponse", "ServersTagsUpdate_All", diff --git a/codegenerator/openapi/nova_schemas.py b/codegenerator/openapi/nova_schemas.py index 5f05133..3f1ee18 100644 --- a/codegenerator/openapi/nova_schemas.py +++ b/codegenerator/openapi/nova_schemas.py @@ -2202,6 +2202,60 @@ } }, } +SERVER_SECURITY_GROUPS_LIST_SCHEMA: dict[str, Any] = { + "type": "object", + "properties": { + "security_groups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "The ID of the security group.", + }, + "name": { + "type": "string", + "description": "The security group name.", + }, + "description": { + "type": "string", + "description": "Security group description.", + }, + "tenant_id": { + "type": "string", + "format": "uuid", + "description": "The UUID of the tenant in a multi-tenancy cloud.", + }, + "rules": { + "type": "array", + "description": "The list of security group rules.", + "items": { + "type": "object", + "properties": { + "id": {"type": "string", "format": "uuid"}, + "from_port": {"type": "integer"}, + "to_port": {"type": "integer"}, + "ip_protocol": {"type": "string"}, + "ip_range": {"type": "object"}, + "group": { + "type": "object", + "properties": {"name": {"type": "string"}}, + }, + "parent_group_id": { + "type": "string", + "format": "uuid", + }, + }, + }, + }, + }, + }, + }, + }, +} + VOLUME_ATTACHMENT_SCHEMA: dict[str, Any] = { "type": "object", diff --git a/codegenerator/openapi/utils.py b/codegenerator/openapi/utils.py index ffdb0b5..1a0d08d 100644 --- a/codegenerator/openapi/utils.py +++ b/codegenerator/openapi/utils.py @@ -32,6 +32,8 @@ def merge_api_ref_doc( :param doc_ver_prefix: Use additional path prefix to find url match """ + # Set of processed operationIds. + processed_operations: set[str] = set() with open(api_ref_src, "r") as fp: html_doc = fp.read() @@ -75,6 +77,7 @@ def merge_api_ref_doc( "span", class_="label" ) method = method_span.string + # Find operation path_spec = openapi_spec.paths.get(url) if ( @@ -153,6 +156,15 @@ def merge_api_ref_doc( ) continue + if ( + op_spec.operationId in processed_operations + and not url.endswith("/action") + ): + # Do not update operation we have already processed + continue + else: + processed_operations.add(op_spec.operationId) + # Find the button in the operaion container to get ID of the # details section details_button = op.find("button") @@ -216,6 +228,10 @@ def merge_api_ref_doc( schema_specs, doc_source_param_mapping, ) + + if url.endswith("/action"): + for sch in schema_specs: + sch.summary = summary # Neutron sometimes has h4 instead of h3 and "Response Parameters" instead of "Response" elif ( details_child.h3 @@ -470,9 +486,19 @@ def _get_schema_candidates( elif not action_name and section_description: if candidate_action_name and ( - candidate_action_name in section_summary - or candidate_action_name - in "".join(section_description) + re.search( + rf"\b{candidate_action_name}\b", section_summary + ) + or ( + url.endswith("/volumes/{volume_id}/action") + # Cinder doc does not contain action name in the + # summary, but looking only to description causes + # faulty matches in Nova + and re.search( + rf"\b{candidate_action_name}\b", + section_description, + ) + ) ): # This is an action we are hopefully interested in # Now we can have single schema or multiple (i.e. microversions) diff --git a/codegenerator/rust_cli.py b/codegenerator/rust_cli.py index d343f5f..8002e07 100644 --- a/codegenerator/rust_cli.py +++ b/codegenerator/rust_cli.py @@ -141,6 +141,10 @@ def clap_macros(self): # For substrucs (and maybe enums) we tell Clap to flatten subtype # instead of exposing attr itself return "#[command(flatten)]" + if isinstance(self.data_type, common_rust.Option) and isinstance( + self.data_type.item_type, common_rust.Struct + ): + return "#[command(flatten)]" macros = set(["long"]) try: if self.data_type.clap_macros: @@ -367,6 +371,12 @@ def clap_macros(self): macros: set[str] = set() if not self.is_required: macros.add("long") + if self.location == "path": + # Sometime there is a collision of path params and body params. + # In order to prevent this force clap arg ID to be prefixed, while + # the value_name is turned back to the expected value + macros.add(f'id = "path_param_{self.local_name}"') + macros.add(f'value_name = "{self.local_name.upper()}"') return f"#[arg({', '.join(macros)})]" @@ -495,6 +505,20 @@ def convert_model( model_ref = type_model.reference # CLI hacks + if isinstance(type_model, model.Struct) and not type_model.reference: + # Check the root structure + if len(type_model.fields) == 1: + # Struct with only 1 key + only_field = list(type_model.fields.keys())[0] + if isinstance( + type_model.fields[only_field].data_type, + model.PrimitiveNull, + ): + # The only field is null. No input is necessary + logging.debug( + "API accepts only 1 field of type Null. No input is required." + ) + type_model.fields = {} if isinstance(type_model, model.Array): if isinstance(type_model.item_type, model.Reference): item_type = self._get_adt_by_reference(type_model.item_type) @@ -649,8 +673,8 @@ class ResponseTypeManager(common_rust.TypeManager): primitive_type_mapping: dict[ Type[model.PrimitiveType], Type[BasePrimitiveType] ] = { - model.PrimitiveString: String, - model.ConstraintString: String, + model.PrimitiveString: common_rust.String, + model.ConstraintString: common_rust.String, } data_type_mapping = { @@ -892,6 +916,7 @@ def generate( ResponseTypeManager() ) result_is_list: bool = False + is_list_paginated: bool = False if operation_params: type_manager.set_parameters(operation_params) @@ -931,8 +956,10 @@ def generate( # struct is there. For the cli it makes no sense and # we filter it out from the parsed data object_to_remove = "OS-SCH-HNT:scheduler_hints" - if parsed_type.reference == model.Reference( - name=object_to_remove, type=model.Struct + if ( + parsed_type.reference + and parsed_type.reference.name == object_to_remove + and parsed_type.reference.type == model.Struct ): request_types.remove(parsed_type) elif parsed_type.reference is None and isinstance( @@ -1068,21 +1095,53 @@ def generate( ): result_is_list = True - additional_imports.add( - "openstack_sdk::api::" - + "::".join( - f"r#{x}" if x in ["type"] else x for x in sdk_mod_path - ) - ) root_type = response_type_manager.get_root_data_type() + + mod_import_name = "openstack_sdk::api::" + "::".join( + f"r#{x}" if x in ["type"] else x for x in sdk_mod_path + ) + + if not ( + args.find_implemented_by_sdk + and args.operation_type + in [ + "show", + "download", + ] + ): + additional_imports.add(mod_import_name) + + if args.find_implemented_by_sdk and args.operation_type in [ + "show", + "set", + "download", + ]: + additional_imports.add("openstack_sdk::api::find") + additional_imports.add( + "::".join( + [ + "openstack_sdk::api", + "::".join( + f"r#{x}" if x in ["type"] else x + for x in sdk_mod_path[:-1] + ), + "find", + ] + ) + ) + if args.operation_type == "list": # Make plural form for listing target_class_name = common.get_plural_form( target_class_name ) - additional_imports.update( - ["openstack_sdk::api::{paged, Pagination}"] - ) + if "limit" in [ + k for (k, _) in type_manager.get_parameters("query") + ]: + is_list_paginated = True + additional_imports.add( + "openstack_sdk::api::{paged, Pagination}" + ) if args.operation_type == "download": additional_imports.add("crate::common::download_file") if args.operation_type == "upload": @@ -1103,6 +1162,11 @@ def generate( additional_imports.add("openstack_sdk::api::QueryAsync") else: additional_imports.add("openstack_sdk::api::RawQueryAsync") + additional_imports.add("http::Response") + additional_imports.add("bytes::Bytes") + + if isinstance(root_type, StructResponse): + additional_imports.add("structable_derive::StructTable") if resource_header_metadata: additional_imports.add( @@ -1121,24 +1185,12 @@ def generate( ): additional_imports.add("regex::Regex") - additional_imports.update(type_manager.get_imports()) - additional_imports.update(response_type_manager.get_imports()) - # Deserialize is already in template since it is uncoditionally required - additional_imports.discard("serde::Deserialize") - if args.find_implemented_by_sdk: - additional_imports.add("openstack_sdk::api::find") - additional_imports.add( - "::".join( - [ - "openstack_sdk::api", - "::".join( - f"r#{x}" if x in ["type"] else x - for x in sdk_mod_path[:-1] - ), - "find", - ] - ) - ) + for st in response_type_manager.get_subtypes(): + if isinstance(st, StructResponse) or getattr( + st, "base_type", None + ) in ["vec", "dict"]: + additional_imports.add("std::fmt") + break if is_image_download: additional_imports.add("openstack_sdk::api::find") @@ -1153,11 +1205,35 @@ def generate( ) ) + additional_imports.update(type_manager.get_imports()) + additional_imports.update(response_type_manager.get_imports()) + # Deserialize is already in template since it is uncoditionally required + additional_imports.discard("serde::Deserialize") + + command_description: str = spec.get("description") + command_summary: str = spec.get("summary") + if args.operation_type == "action": + command_description = operation_body.get( + "description", command_description + ) + command_summary = operation_body.get( + "summary", command_summary + ) + + if command_summary and microversion: + command_summary += f" (microversion = {microversion})" + if not command_description: + command_description = ( + "Command without description in OpenAPI" + ) context = dict( operation_id=operation_id, operation_type=args.operation_type, command_description=common_rust.sanitize_rust_docstrings( - spec.get("description") + command_description + ), + command_summary=common_rust.sanitize_rust_docstrings( + command_summary ), type_manager=type_manager, resource_name=resource_name, @@ -1186,6 +1262,7 @@ def generate( result_is_list=result_is_list, is_image_download=is_image_download, is_json_patch=is_json_patch, + is_list_paginated=is_list_paginated, ) if not args.cli_mod_path: diff --git a/codegenerator/rust_sdk.py b/codegenerator/rust_sdk.py index 15f0bbe..450b619 100644 --- a/codegenerator/rust_sdk.py +++ b/codegenerator/rust_sdk.py @@ -79,7 +79,13 @@ def builder_macros(self): if "private" in macros: macros.add(f'setter(name="_{self.local_name}")') if self.is_optional: - macros.add("default") + default_set: bool = False + for macro in macros: + if "default" in macro: + default_set = True + break + if not default_set: + macros.add("default") return f"#[builder({', '.join(sorted(macros))})]" @property @@ -131,13 +137,14 @@ def get_sample(self): def get_mandatory_init(self): res = [] for field in self.fields.values(): - if not field.is_optional: - el = field.data_type.get_sample() - if el: - data = f".{field.local_name}(" - data += el - data += ")" - res.append(data) + if not isinstance(field.data_type, common_rust.Null): + if not field.is_optional: + el = field.data_type.get_sample() + if el: + data = f".{field.local_name}(" + data += el + data += ")" + res.append(data) return "".join(res) diff --git a/codegenerator/templates/rust_cli/impl.rs.j2 b/codegenerator/templates/rust_cli/impl.rs.j2 index 13a3d65..3f38f53 100644 --- a/codegenerator/templates/rust_cli/impl.rs.j2 +++ b/codegenerator/templates/rust_cli/impl.rs.j2 @@ -1,34 +1,50 @@ -{%- import 'rust_macros.j2' as macros with context -%} -{{ macros.mod_docstring(command_description) }} -use async_trait::async_trait; +// 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`. + +//! {{ operation_type | title }} {{ target_class_name }} command +{%- if microversion %} [microversion = {{ microversion }}]{%- endif %} +//! +//! Wraps invoking of the `{{ url }}` with `{{ method|upper }}` method + +{% import 'rust_macros.j2' as macros with context -%} use clap::Args; use serde::{Deserialize, Serialize}; use tracing::info; -use http::Response; -{%- if not result_def %} -use http::{HeaderName, HeaderValue}; -use bytes::Bytes; -{%- endif %} use anyhow::Result; +use openstack_sdk::AsyncOpenStack; + use crate::output::OutputProcessor; use crate::Cli; use crate::OutputConfig; use crate::StructTable; -use crate::{Command, error::OpenStackCliError}; -use structable_derive::StructTable; -use std::fmt; - -use openstack_sdk::{AsyncOpenStack, types::ServiceType}; +use crate::OpenStackCliError; {% for mod in additional_imports | sort %} use {{ mod }}; {%- endfor %} -/// Command arguments -#[derive(Args, Clone, Debug)] -pub struct {{ target_class_name }}Args { +{{ macros.docstring(command_description) }} +#[derive(Args)] +{%- if command_summary %} +#[command(about = "{{ command_summary }}")] +{%- endif %} +pub struct {{ target_class_name }}Command { /// Request Query parameters #[command(flatten)] query: QueryParameters, @@ -62,7 +78,7 @@ pub struct {{ target_class_name }}Args { {%- for type in type_manager.get_subtypes() %} {%- if type["variants"] is defined %} {{ macros.docstring(type.description, indent=0) }} -#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd, ValueEnum)] +#[derive(Clone, Eq, Ord, PartialEq, PartialOrd, ValueEnum)] enum {{ type.name }} { {%- for k in type.variants.keys()|sort %} {{ k }}, @@ -71,7 +87,7 @@ enum {{ type.name }} { {%- elif type["base_type"] == "struct" %} /// {{ type.name }} Body data -#[derive(Args, Debug, Clone)] +#[derive(Args)] {%- if type["is_group"] is defined and type.is_group %} #[group(required={{ type.is_required | lower }}, multiple={{ "true" if type.__class__.__name__ != "EnumGroupStruct" else "false" }})] {%- endif %} @@ -85,25 +101,19 @@ struct {{ type.name }} { {%- endif %} {% endfor %} -/// {{ target_class_name }} {{ operation_type }} command -pub struct {{ target_class_name }}Cmd { - pub args: {{ target_class_name }}Args, -} - {%- include 'rust_cli/response_struct.j2' %} -#[async_trait] -impl Command for {{ target_class_name }}Cmd { - async fn take_action( +impl {{ target_class_name }}Command { + /// Perform command action + pub async fn take_action( &self, parsed_args: &Cli, client: &mut AsyncOpenStack, ) -> Result<(), OpenStackCliError> { - info!("{{ operation_type | title }} {{ target_class_name }} with {:?}", self.args); + info!("{{ operation_type | title }} {{ target_class_name }}"); let op = OutputProcessor::from_args(parsed_args); op.validate_args(parsed_args)?; - info!("Parsed args: {:?}", self.args); {%- if operation_type == "download" and is_image_download %} {%- include 'rust_cli/impl_image_download.j2' %} @@ -145,7 +155,7 @@ impl Command for {{ target_class_name }}Cmd { {%- with data_type = response_type_manager.get_root_data_type() %} - {%- if (data_type.__class__.__name__ == "StructResponse" and data_type.fields or (data_type.tuple_fields is defined and data_type.tuple_fields)) or (data_type.__class__.__name__ == "HashMapResponse") %} + {%- if (data_type.__class__.__name__ == "StructResponse" and data_type.fields) or data_type.__class__.__name__ == "TupleStruct" or data_type.__class__.__name__ == "HashMapResponse" %} {#- there is result structure meand we can render response #} {%- if operation_type == "list" %} @@ -177,12 +187,12 @@ impl Command for {{ target_class_name }}Cmd { {%- endif %} {%- elif operation_type not in ["delete", "download", "upload", "json"] %} {#- there is no result structure - raw mode #} - let rsp: Response = ep.raw_query_async(client).await?; + let _rsp: Response = ep.raw_query_async(client).await?; {%- if resource_header_metadata %} {#- metadata from headers for now can be only returned when there is no response struct #} let mut metadata: HashMap = HashMap::new(); - let headers = rsp.headers(); + let headers = _rsp.headers(); let mut regexes: Vec = vec![ {%- for hdr, spec in resource_header_metadata.items() %} @@ -220,7 +230,7 @@ impl Command for {{ target_class_name }}Cmd { // Maybe output some headers metadata op.output_human::(&data)?; {%- elif operation_type == "delete" %} - let rsp: Response = ep.raw_query_async(client).await?; + let _rsp: Response = ep.raw_query_async(client).await?; {%- elif operation_type == "download" %} {%- include 'rust_cli/invoke_download.j2' %} diff --git a/codegenerator/templates/rust_cli/impl_image_download.j2 b/codegenerator/templates/rust_cli/impl_image_download.j2 index 088e1e2..079890e 100644 --- a/codegenerator/templates/rust_cli/impl_image_download.j2 +++ b/codegenerator/templates/rust_cli/impl_image_download.j2 @@ -1,5 +1,5 @@ let find_ep = find::Request::builder() - .id(&self.args.path.image_id) + .id(&self.path.image_id) .build() .map_err(|x| OpenStackCliError::EndpointBuild(x.to_string()))?; let image_data: serde_json::Value = find(find_ep).query_async(client).await?; @@ -25,4 +25,4 @@ .unwrap_or("0") .parse() .unwrap(); - download_file(self.args.file.clone().unwrap_or(image_name), size, data).await?; + 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 e7b419c..4edb2b2 100644 --- a/codegenerator/templates/rust_cli/invoke_download.j2 +++ b/codegenerator/templates/rust_cli/invoke_download.j2 @@ -7,7 +7,7 @@ .parse() .unwrap(); download_file( - self.args.file.clone().unwrap_or(self.args.{{ last_path_parameter.name }}.clone()), + self.file.clone().unwrap_or(self.{{ last_path_parameter.name }}.clone()), size, data, ) diff --git a/codegenerator/templates/rust_cli/invoke_list.j2 b/codegenerator/templates/rust_cli/invoke_list.j2 index c625f8f..ceef01b 100644 --- a/codegenerator/templates/rust_cli/invoke_list.j2 +++ b/codegenerator/templates/rust_cli/invoke_list.j2 @@ -1,8 +1,8 @@ {#- List operation #} -{%- if data_type.__class__.__name__ == "StructResponse" %} - {%- if "limit" in type_manager.get_parameters("query")|list|map(attribute=0) %} +{%- if data_type.__class__.__name__ in ["StructResponse", "TupleStruct"] %} + {%- if is_list_paginated %} {#- paginated list #} - let data: Vec = paged(ep, Pagination::Limit(self.args.max_items)).query_async(client).await?; + let data: Vec = paged(ep, Pagination::Limit(self.max_items)).query_async(client).await?; {%- else %} let data: Vec = ep.query_async(client).await?; {%- endif %} diff --git a/codegenerator/templates/rust_cli/invoke_patch.j2 b/codegenerator/templates/rust_cli/invoke_patch.j2 index 88c954c..ee85727 100644 --- a/codegenerator/templates/rust_cli/invoke_patch.j2 +++ b/codegenerator/templates/rust_cli/invoke_patch.j2 @@ -11,7 +11,7 @@ {%- for attr_name, field in root.fields.items() %} {%- if attr_name != "id" %}{# glance doesn't hide "ID" from change #} - if let Some(val) = &self.args.{{ field.local_name }} { + if let Some(val) = &self.{{ field.local_name }} { {%- if field.type_hint == "Option>" %} new.{{ field.local_name }} = Some(VecString(val.clone())); @@ -50,7 +50,7 @@ {%- if root.additional_fields_type %} {#- additional properties are not present in the output and thus handleded on the raw json #} - if let Some(properties) = &self.args.properties { + if let Some(properties) = &self.properties { for (key, val) in properties { new_json[key] = json!(val); } diff --git a/codegenerator/templates/rust_cli/invoke_upload.j2 b/codegenerator/templates/rust_cli/invoke_upload.j2 index ab6be90..15edb73 100644 --- a/codegenerator/templates/rust_cli/invoke_upload.j2 +++ b/codegenerator/templates/rust_cli/invoke_upload.j2 @@ -1,4 +1,4 @@ - let dst = self.args.file.clone(); + let dst = self.file.clone(); let data = build_upload_asyncread(dst).await?; let _rsp: Response = ep.raw_query_read_body_async(client, data).await?; diff --git a/codegenerator/templates/rust_cli/path_parameters.j2 b/codegenerator/templates/rust_cli/path_parameters.j2 index a0f709c..0eb1789 100644 --- a/codegenerator/templates/rust_cli/path_parameters.j2 +++ b/codegenerator/templates/rust_cli/path_parameters.j2 @@ -1,6 +1,6 @@ /// Path parameters -#[derive(Args, Clone, Debug)] +#[derive(Args)] pub struct PathParameters { {%- for param in type_manager.parameters.values() %} {%- if param.location == "path"%} diff --git a/codegenerator/templates/rust_cli/query_parameters.j2 b/codegenerator/templates/rust_cli/query_parameters.j2 index f58a5c7..a08e81b 100644 --- a/codegenerator/templates/rust_cli/query_parameters.j2 +++ b/codegenerator/templates/rust_cli/query_parameters.j2 @@ -1,6 +1,6 @@ /// Query parameters -#[derive(Args, Clone, Debug)] +#[derive(Args)] pub struct QueryParameters { {%- for param in type_manager.parameters.values() %} {%- if param.location == "query" %} diff --git a/codegenerator/templates/rust_cli/response_struct.j2 b/codegenerator/templates/rust_cli/response_struct.j2 index 4b9b229..4cb5d1d 100644 --- a/codegenerator/templates/rust_cli/response_struct.j2 +++ b/codegenerator/templates/rust_cli/response_struct.j2 @@ -3,7 +3,8 @@ {%- if data_type.__class__.__name__ == "StructResponse" %} {%- if data_type.fields %} /// {{ target_class_name }} response representation - #[derive(Deserialize, Debug, Clone, Serialize, StructTable)] + #[derive(Deserialize, Serialize)] + #[derive(Clone, StructTable)] pub struct ResponseData { {%- for k, v in data_type.fields.items() %} {{ macros.docstring(v.description, indent=4) }} @@ -16,14 +17,16 @@ {%- else %} {#- No response data at all #} /// {{ target_class_name }} response representation - #[derive(Deserialize, Debug, Clone, Serialize, StructTable)] + #[derive(Deserialize, Serialize)] + #[derive(Clone, StructTable)] pub struct ResponseData {} {%- endif %} {%- elif data_type.__class__.__name__ == "TupleStruct" %} {#- tuple struct requires custom implementation of StructTable #} /// {{ target_class_name }} response representation - #[derive(Deserialize, Debug, Clone, Serialize)] + #[derive(Deserialize, Serialize)] + #[derive(Clone)] pub struct ResponseData( {%- for field in data_type.tuple_fields %} {{ field.type_hint }}, @@ -31,7 +34,7 @@ ); impl StructTable for ResponseData { - fn build(&self, options: &OutputConfig) -> (Vec, + fn build(&self, _: &OutputConfig) -> (Vec, Vec>) { let headers: Vec = Vec::from(["Value".to_string()]); let res: Vec> = Vec::from([Vec::from([self.0. @@ -41,7 +44,7 @@ } impl StructTable for Vec { - fn build(&self, options: &OutputConfig) -> (Vec, + fn build(&self, _: &OutputConfig) -> (Vec, Vec>) { let headers: Vec = Vec::from(["Values".to_string()]); let res: Vec> = @@ -51,11 +54,12 @@ } } {%- elif data_type.__class__.__name__ == "HashMapResponse" %} - #[derive(Deserialize, Debug, Clone, Serialize)] + /// Response data as HashMap type + #[derive(Deserialize, Serialize)] pub struct ResponseData(HashMap); impl StructTable for ResponseData { - fn build(&self, options: &OutputConfig) -> (Vec, + fn build(&self, _options: &OutputConfig) -> (Vec, Vec>) { let headers: Vec = Vec::from(["Name". to_string(), "Value".to_string()]); @@ -73,7 +77,10 @@ {%- for subtype in response_type_manager.get_subtypes() %} {%- if subtype["fields"] is defined %} -#[derive(Deserialize, Debug, Default, Clone, Serialize)] +/// {{ subtype.base_type }} response type +#[derive(Default)] +#[derive(Clone)] +#[derive(Deserialize, Serialize)] {{ subtype.base_type }} {{ subtype.name }} { {%- for k, v in subtype.fields.items() %} {{ v.local_name }}: {{ v.type_hint }}, @@ -84,7 +91,18 @@ impl fmt::Display for {{ subtype.name }} { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let data = Vec::from([ {%- for k, v in subtype.fields.items() %} - format!("{{v.local_name}}={}", self.{{ v.local_name }}{{ ".clone().map(|v| v.to_string()).unwrap_or(\"\".to_string())" if v.type_hint.startswith("Option") }}), + format!( + "{{v.local_name}}={}", + self + .{{ v.local_name }} + {%- if v.type_hint.startswith("Option") %} + {%- if v.type_hint not in ["Option", "Option", "Option", "Option", "Option"] %} + .clone() + {%- endif %} + .map(|v| v.to_string()) + .unwrap_or("".to_string()) + {%- endif %} + ), {%- endfor %} ]); write!( @@ -96,7 +114,10 @@ impl fmt::Display for {{ subtype.name }} { } } {%- elif subtype.base_type == "vec" %} -#[derive(Deserialize, Default, Debug, Clone, Serialize)] +/// Vector of {{ subtype.item_type.type_hint}} response type +#[derive(Default)] +#[derive(Clone)] +#[derive(Deserialize, Serialize)] pub struct Vec{{ subtype.item_type.type_hint}}(Vec<{{subtype.item_type.type_hint}}>); impl fmt::Display for Vec{{ subtype.item_type.type_hint }} { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -112,7 +133,10 @@ impl fmt::Display for Vec{{ subtype.item_type.type_hint }} { } } {%- elif subtype.base_type == "dict" %} -#[derive(Deserialize, Default, Debug, Clone, Serialize)] +/// HashMap of {{ subtype.value_type.type_hint }} response type +#[derive(Default)] +#[derive(Clone)] +#[derive(Deserialize, Serialize)] pub struct HashMapString{{ subtype.value_type.type_hint}}(HashMap); impl fmt::Display for HashMapString{{ subtype.value_type.type_hint }} { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/codegenerator/templates/rust_cli/set_body_parameters.j2 b/codegenerator/templates/rust_cli/set_body_parameters.j2 index 433b155..2fba9bd 100644 --- a/codegenerator/templates/rust_cli/set_body_parameters.j2 +++ b/codegenerator/templates/rust_cli/set_body_parameters.j2 @@ -6,9 +6,9 @@ {%- for root_attr, root_field in root.fields.items() %} // Set Request.{{ root_field.remote_name }} data {%- if root_field.is_optional %} - if let Some(args) = &self.args.{{ root_field.local_name }} { + if let Some(args) = &self.{{ root_field.local_name }} { {%- else %} - let args = &self.args.{{ root_field.local_name }}; + let args = &self.{{ root_field.local_name }}; {%- endif %} {%- if root_field.data_type.__class__.__name__ == "StructInput" %} @@ -50,12 +50,12 @@ {%- endif %} {% endfor %} {%- if root.additional_fields_type %} - if let Some(properties) = &self.args.properties { + if let Some(properties) = &self.properties { ep_builder.properties(properties.iter().cloned()); } {%- endif %} {%- elif root.__class__.__name__ == "DictionaryInput" %} - if let Some(properties) = &self.args.properties { + if let Some(properties) = &self.properties { ep_builder.properties(properties.iter().cloned()); } {%- endif %} diff --git a/codegenerator/templates/rust_cli/set_path_parameters.j2 b/codegenerator/templates/rust_cli/set_path_parameters.j2 index f8f23a4..b4c0a48 100644 --- a/codegenerator/templates/rust_cli/set_path_parameters.j2 +++ b/codegenerator/templates/rust_cli/set_path_parameters.j2 @@ -2,17 +2,17 @@ {%- for (k, v) in type_manager.get_parameters("path") %} {%- if not v.is_required %} {%- if k != "project_id" %} - if let Some(val) = &self.args.path.{{ v.local_name }} { + if let Some(val) = &self.path.{{ v.local_name }} { ep_builder.{{ v.local_name }}(val); } {%- else %} - if let Some(val) = &self.args.path.{{ v.local_name }} { + if let Some(val) = &self.path.{{ v.local_name }} { ep_builder.{{ v.local_name }}(val); } else { ep_builder.{{ v.local_name }}(client.get_current_project().expect("Project ID must be known").id); } {%- endif %} {%- else %} - ep_builder.{{ v.local_name }}(&self.args.path.{{ v.local_name }}); + ep_builder.{{ v.local_name }}(&self.path.{{ v.local_name }}); {%- endif %} {%- endfor %} diff --git a/codegenerator/templates/rust_cli/set_query_parameters.j2 b/codegenerator/templates/rust_cli/set_query_parameters.j2 index 49fb3ea..d6be583 100644 --- a/codegenerator/templates/rust_cli/set_query_parameters.j2 +++ b/codegenerator/templates/rust_cli/set_query_parameters.j2 @@ -2,10 +2,10 @@ // Set query parameters {%- for (k, v) in type_manager.get_parameters("query") %} {%- if not v.is_required %} - if let Some(val) = &self.args.query.{{ v.local_name }} { + if let Some(val) = &self.query.{{ v.local_name }} { {{ macros.set_request_data_from_input("ep_builder", v, "val")}} } {%- else %} - {{ macros.set_request_data_from_input("ep_builder", v, "&self.args.query." + v.local_name )}} + {{ macros.set_request_data_from_input("ep_builder", v, "&self.query." + v.local_name )}} {%- endif %} {%- endfor %} diff --git a/codegenerator/templates/rust_macros.j2 b/codegenerator/templates/rust_macros.j2 index 01fe058..c941853 100644 --- a/codegenerator/templates/rust_macros.j2 +++ b/codegenerator/templates/rust_macros.j2 @@ -137,7 +137,7 @@ Some({{ val }}) {%- elif param.data_type.__class__.__name__ in ["ArrayInput"] %} {{ sdk_plain_array_setter(param, val_var, dst_var) }} {%- elif param.data_type.__class__.__name__ in ["JsonValue"] %} - // let sub: {{ sdk_mod_path[-1] }} + {{ dst_var }}.{{ param.remote_name }}({{ val_var | replace("&", "" )}}.clone()); {%- elif param.data_type.__class__.__name__ == "DictionaryInput" %} {%- if param.data_type.value_type.__class__.__name__ == "Option" %} {{ dst_var }}.{{ param.remote_name }}({{ val_var | replace("&", "") }}.iter().cloned().map(|(k, v)| (k, v.map(Into::into)))); @@ -195,7 +195,26 @@ Some({{ val }}) {{ dst_var }}.{{ param.remote_name }}({{ val_var | replace("&", "") }}.clone()); {%- endif %} {%- elif param.data_type.__class__.__name__ == "Option" %} + {%- if param.data_type.item_type.__class__.__name__ == "StructInput" %} + if let Some(l{{ param.local_name }}) = &{{ val_var }} { + {% set builder_name = param.local_name + "_builder" %} + let mut {{ builder_name }} = {{ sdk_mod_path[-1] }}::{{ param.data_type.item_type.name }}Builder::default(); + {%- for k, v in param.data_type.item_type.fields.items() %} + {%- if v.is_optional %} + if let Some(val) = &l{{ param.local_name }}.{{ v.local_name }} { + {{ set_request_data_from_input(builder_name, v, "val") }} + } + {%- else %} + {{ set_request_data_from_input(builder_name, v, "&l" + param.local_name + "." + v.local_name) }} + {%- endif %} + + {%- endfor %} + {{ dst_var }}.{{ param.remote_name }}({{ builder_name }}.build().expect("A valid object")); + } + + {%- else %} {{ dst_var }}.{{ param.remote_name }}({{ val_var }}.clone().map(|v| v.into())); + {%- endif %} {%- else %} {{ dst_var }}.{{ param.remote_name }}({{ val_var }}); {%- endif %} @@ -256,7 +275,7 @@ Some({{ val }}) {%- elif param["setter_type"] is defined %} {#- Param with setter present #} {{ dst_var }}.{{ param.remote_name }}( - {{ val_var }}.into_iter() + {{ val_var }}.iter() ); {%- elif original_item_type and original_item_type.__class__.__name__ == "DictionaryInput" %} use std::collections::BTreeMap; @@ -294,11 +313,11 @@ Some({{ val }}) {%- for (k, v) in type_manager.get_parameters("path") %} {%- if not v.is_required %} {%- if k != "project_id" %} - if let Some(val) = &self.args.path.{{ v.local_name }} { + if let Some(val) = &self.path.{{ v.local_name }} { {{ builder }}.{{ v.local_name }}(val); } {%- else %} - if let Some(val) = &self.args.path.{{ v.local_name }} { + 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); @@ -311,7 +330,7 @@ Some({{ val }}) .to_string(); {{ builder }}.{{ v.local_name }}(resource_id.clone()); {%- else %} - {{ builder }}.{{ v.local_name }}(&self.args.path.{{ v.local_name }}); + {{ builder }}.{{ v.local_name }}(&self.path.{{ v.local_name }}); {%- endif %} {%- endfor %} {%- endmacro %} diff --git a/codegenerator/templates/rust_sdk/find.rs.j2 b/codegenerator/templates/rust_sdk/find.rs.j2 index 23b4fad..cb739d7 100644 --- a/codegenerator/templates/rust_sdk/find.rs.j2 +++ b/codegenerator/templates/rust_sdk/find.rs.j2 @@ -1,4 +1,20 @@ -{%- import 'rust_macros.j2' as macros with context -%} +// 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`. +{% import 'rust_macros.j2' as macros with context -%} use derive_builder::Builder; use http::{HeaderMap, HeaderName, HeaderValue}; use serde::de::DeserializeOwned; diff --git a/codegenerator/templates/rust_sdk/impl.rs.j2 b/codegenerator/templates/rust_sdk/impl.rs.j2 index 17d3235..1dd0dd6 100644 --- a/codegenerator/templates/rust_sdk/impl.rs.j2 +++ b/codegenerator/templates/rust_sdk/impl.rs.j2 @@ -1,4 +1,20 @@ -{%- import 'rust_macros.j2' as macros with context -%} +// 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`. +{% import 'rust_macros.j2' as macros with context -%} {{ macros.mod_docstring(command_description) }} use derive_builder::Builder; use http::{HeaderMap, HeaderName, HeaderValue}; @@ -178,6 +194,7 @@ impl{{ type_manager.get_request_static_lifetimes(request) }} RestEndpoint for Re let mut params = JsonBodyParams::default(); {% for k, v in request.fields.items() %} + {%- if v.data_type.__class__.__name__ != "Null" %} {%- if v.is_optional %} if let Some(val) = &self.{{ v.local_name }} { params.push("{{ k }}", serde_json::to_value(val)?); @@ -185,6 +202,9 @@ impl{{ type_manager.get_request_static_lifetimes(request) }} RestEndpoint for Re {%- else %} params.push("{{ k }}", serde_json::to_value(&self.{{v.local_name}})?); {%- endif %} + {%- else %} + params.push("{{ k }}", Value::Null); + {%- endif %} {%- endfor %} {%- if request.additional_fields_type %} diff --git a/codegenerator/templates/rust_sdk/mod.rs.j2 b/codegenerator/templates/rust_sdk/mod.rs.j2 index 1185aaf..f161d9a 100644 --- a/codegenerator/templates/rust_sdk/mod.rs.j2 +++ b/codegenerator/templates/rust_sdk/mod.rs.j2 @@ -1,4 +1,20 @@ -{%- if mod_path|length > 2 %} +// 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 of {{ service_name }} {%- else %} //! `{{ service_name|capitalize }}` Service bindings diff --git a/codegenerator/templates/rust_sdk/request_struct.j2 b/codegenerator/templates/rust_sdk/request_struct.j2 index 4a0f8bb..f28805f 100644 --- a/codegenerator/templates/rust_sdk/request_struct.j2 +++ b/codegenerator/templates/rust_sdk/request_struct.j2 @@ -7,9 +7,11 @@ #[builder(setter(strip_option))] pub struct {{ data_type.name }}{{ type_manager.get_request_static_lifetimes(data_type) }} { {%- for field in data_type.fields.values() %} + {%- if field.data_type.__class__.__name__ != "Null" %} {{ macros.docstring(field.description, indent=4) }} {{ field.builder_macros }} pub(crate) {{ field.local_name }}: {{ field.type_hint }}, + {%- endif %} {%- endfor %} {%- for k, param in type_manager.parameters.items() %} diff --git a/codegenerator/tests/unit/test_model.py b/codegenerator/tests/unit/test_model.py index 2b75160..3f35c0e 100644 --- a/codegenerator/tests/unit/test_model.py +++ b/codegenerator/tests/unit/test_model.py @@ -1297,3 +1297,34 @@ def test_parse_array_of_array_of_strings(self): k = res.kinds[0] self.assertIsInstance(k, model.Array) self.assertIsInstance(k.item_type, model.Array) + + def test_server_unshelve(self): + schema = { + "type": "object", + "properties": { + "unshelve": { + "oneOf": [ + { + "type": ["object"], + "properties": { + "availability_zone": { + "oneOf": [ + {"type": ["null"]}, + {"type": "string"}, + ] + }, + "host": {"type": "string"}, + }, + "additionalProperties": False, + }, + {"type": ["null"]}, + ] + } + }, + "additionalProperties": False, + "x-openstack": {"min-ver": "2.91", "action-name": "unshelve"}, + "required": ["unshelve"], + } + parser = model.OpenAPISchemaParser() + (res, all_models) = parser.parse(schema) + self.assertEqual(4, len(all_models)) diff --git a/metadata/compute_metadata.yaml b/metadata/compute_metadata.yaml index 16c4e48..12a6a00 100644 --- a/metadata/compute_metadata.yaml +++ b/metadata/compute_metadata.yaml @@ -1419,46 +1419,6 @@ resources: name_field: name name_filter_supported: true list_mod: list_detailed - compute.server/console: - spec_file: wrk/openapi_specs/compute/v2.yaml - api_version: v2 - operations: - list: - operation_id: servers/server_id/consoles:get - operation_type: list - targets: - rust-sdk: - module_name: list - rust-cli: - module_name: list - sdk_mod_name: list - create: - operation_id: servers/server_id/consoles:post - operation_type: create - targets: - rust-sdk: - module_name: create - rust-cli: - module_name: create - sdk_mod_name: create - show: - operation_id: servers/server_id/consoles/id:get - operation_type: show - targets: - rust-sdk: - module_name: get - rust-cli: - module_name: show - sdk_mod_name: get - delete: - operation_id: servers/server_id/consoles/id:delete - operation_type: delete - targets: - rust-sdk: - module_name: delete - rust-cli: - module_name: delete - sdk_mod_name: delete compute.server/diagnostic: spec_file: wrk/openapi_specs/compute/v2.yaml api_version: v2 @@ -1617,18 +1577,22 @@ resources: targets: rust-sdk: module_name: list + response_key: instanceActions rust-cli: module_name: list sdk_mod_name: list + response_key: instanceActions show: operation_id: servers/server_id/os-instance-actions/id:get operation_type: show targets: rust-sdk: module_name: get + response_key: instanceAction rust-cli: module_name: show sdk_mod_name: get + response_key: instanceAction compute.server/interface: spec_file: wrk/openapi_specs/compute/v2.yaml api_version: v2 @@ -1691,19 +1655,6 @@ resources: rust-cli: module_name: delete sdk_mod_name: delete - compute.server/virtual_interface: - spec_file: wrk/openapi_specs/compute/v2.yaml - api_version: v2 - operations: - get: - operation_id: servers/server_id/os-virtual-interfaces:get - operation_type: get - targets: - rust-sdk: - module_name: get - rust-cli: - module_name: get - sdk_mod_name: get compute.server/volume_attachment: spec_file: wrk/openapi_specs/compute/v2.yaml api_version: v2 @@ -1714,27 +1665,33 @@ resources: targets: rust-sdk: module_name: list + response_key: volumeAttachments rust-cli: module_name: list sdk_mod_name: list + response_key: volumeAttachments create: operation_id: servers/server_id/os-volume_attachments:post operation_type: create targets: rust-sdk: module_name: create + response_key: volumeAttachment rust-cli: module_name: create sdk_mod_name: create + response_key: volumeAttachment show: operation_id: servers/server_id/os-volume_attachments/id:get operation_type: show targets: rust-sdk: module_name: get + response_key: volumeAttachment rust-cli: module_name: show sdk_mod_name: get + response_key: volumeAttachment find_implemented_by_sdk: true update: operation_id: servers/server_id/os-volume_attachments/id:put @@ -1742,9 +1699,11 @@ resources: targets: rust-sdk: module_name: set + response_key: volumeAttachment rust-cli: module_name: set sdk_mod_name: set + response_key: volumeAttachment find_implemented_by_sdk: true delete: operation_id: servers/server_id/os-volume_attachments/id:delete @@ -1783,15 +1742,15 @@ resources: spec_file: wrk/openapi_specs/compute/v2.yaml api_version: v2 operations: - get: + list: operation_id: servers/server_id/os-security-groups:get - operation_type: get + operation_type: list targets: rust-sdk: - module_name: get + module_name: list rust-cli: - module_name: get - sdk_mod_name: get + module_name: list + sdk_mod_name: list compute.server/tag: spec_file: wrk/openapi_specs/compute/v2.yaml api_version: v2 @@ -1854,12 +1813,14 @@ resources: spec_file: wrk/openapi_specs/compute/v2.yaml api_version: v2 operations: - get: + list: operation_id: servers/server_id/topology:get - operation_type: get + operation_type: list targets: rust-sdk: - module_name: get + module_name: list + response_key: nodes rust-cli: - module_name: get - sdk_mod_name: get + module_name: list + sdk_mod_name: list + response_key: nodes