Fix codegeneration for placement esoterics

Some of the placement schemas are too special and user unfriendly that
codegeneration currently fails for those. And since we could not even
test generated code complilation we simply skipped such cases from
enabling. Now try to fix codegeneration and see how it goes.

Change-Id: I6c74ace134b2c8cb7017f1adacf2a38469fe5777
This commit is contained in:
Artem Goncharov 2024-11-08 20:57:47 +01:00
parent b40adc5853
commit 5527731e17
13 changed files with 137 additions and 45 deletions

@ -413,6 +413,10 @@ def get_operation_variants(spec: dict, operation_name: str):
raise RuntimeError( raise RuntimeError(
f"Cannot find body specification for action {operation_name}" f"Cannot find body specification for action {operation_name}"
) )
else:
raise RuntimeError(
f"Unsupported discriminator {discriminator}"
)
else: else:
operation_variants.append( operation_variants.append(
{"body": json_body_schema, "mime_type": mime_type} {"body": json_body_schema, "mime_type": mime_type}

@ -513,6 +513,8 @@ class TypeManager:
x.capitalize() x.capitalize()
for x in re.split(common.SPLIT_NAME_RE, model_ref.name) for x in re.split(common.SPLIT_NAME_RE, model_ref.name)
) )
if name[0].isdigit():
return "x" + name
return name return name
def _get_adt_by_reference(self, model_ref): def _get_adt_by_reference(self, model_ref):

@ -1239,4 +1239,9 @@ def post_process_placement_operation(
operation.operation_type = "list_from_struct" operation.operation_type = "list_from_struct"
operation.targets["rust-cli"].response_key = "usages" operation.targets["rust-cli"].response_key = "usages"
operation.targets["rust-sdk"].response_key = "usages" operation.targets["rust-sdk"].response_key = "usages"
if operation_name == "delete_all":
operation.targets["rust-cli"].cli_full_command = operation.targets[
"rust-cli"
].cli_full_command.replace("delete-all", "purge")
return operation return operation

@ -17,6 +17,7 @@ import copy
import hashlib import hashlib
import json import json
import logging import logging
import string
from typing import Any from typing import Any
from typing import Type from typing import Type
import typing as ty import typing as ty
@ -393,11 +394,19 @@ class JsonSchemaParser:
if pattern_properties: if pattern_properties:
# `"type": "object", "pattern_properties": {...}}` # `"type": "object", "pattern_properties": {...}}`
for key_pattern, value_type in pattern_properties.items(): for key_pattern, value_type in pattern_properties.items():
type_kind: PrimitiveType | ADT = self.parse_schema( type_kind: PrimitiveType | ADT = self.parse_schema(
value_type, value_type,
results, results,
name=name, name=(name or "")
+ (
key_pattern.translate(
str.maketrans(dict.fromkeys(string.punctuation))
)
if len(pattern_properties.keys()) > 1
else "Item"
),
min_ver=min_ver, min_ver=min_ver,
max_ver=max_ver, max_ver=max_ver,
ignore_read_only=ignore_read_only, ignore_read_only=ignore_read_only,
@ -498,6 +507,7 @@ class JsonSchemaParser:
else: else:
obj.reference.name = new_name obj.reference.name = new_name
results.append(obj) results.append(obj)
return obj return obj
def parse_oneOf( def parse_oneOf(

@ -642,7 +642,6 @@ class OpenStackServerSourceBase:
end_version, end_version,
action_name, action_name,
) )
print(f"Bodies: {body_schemas} {mode}")
if hasattr(func, "_wsme_definition"): if hasattr(func, "_wsme_definition"):
fdef = getattr(func, "_wsme_definition") fdef = getattr(func, "_wsme_definition")
@ -878,7 +877,6 @@ class OpenStackServerSourceBase:
action_name, action_name,
): ):
# Body is not expected, exit (unless we are in the "action") # Body is not expected, exit (unless we are in the "action")
print(f"mode={mode}")
if body_schemas is None or (body_schemas == [] and mode != "action"): if body_schemas is None or (body_schemas == [] and mode != "action"):
return return
mime_type: str | None = "application/json" mime_type: str | None = "application/json"

@ -65,11 +65,7 @@ ALLOCATION_POST_REQUEST_SCHEMA: dict[str, Any] = {
"x-openstack": {"min-ver": "1.34", "max-ver": "1.37"}, "x-openstack": {"min-ver": "1.34", "max-ver": "1.37"},
}, },
{ {
**allocation.ALLOCATION_SCHEMA_V1_38, **allocation.POST_ALLOCATIONS_V1_38,
"x-openstack": {"min-ver": "1.38"},
},
{
**allocation.ALLOCATION_SCHEMA_V1_38,
"x-openstack": {"min-ver": "1.38"}, "x-openstack": {"min-ver": "1.38"},
}, },
], ],

@ -53,18 +53,19 @@ RESOURCE_CLASSES_SCHEMA: dict[str, Any] = {
RESOURCE_CLASS_UPDATE_REQUEST_SCHEMA: dict[str, Any] = { RESOURCE_CLASS_UPDATE_REQUEST_SCHEMA: dict[str, Any] = {
"oneOf": [ "oneOf": [
{ # {
"type": "object", # "type": "object",
"properties": { # "properties": {
"name": { # "name": {
"type": "string", # "type": "string",
"description": "The name of one resource class.", # "description": "The name of one resource class.",
} # }
}, # },
"x-openstack": {"min-ver": "1.7"}, # "x-openstack": {"min-ver": "1.2", "max-ver": "1.6"},
} # },
{"type": "null", "x-openstack": {"min-ver": "1.7"}}
], ],
"discriminator": "microversion", "x-openstack": {"discriminator": "microversion"},
} }

@ -45,14 +45,17 @@ USAGE_LIST_PARAMETERS: dict[str, Any] = {
"project_id": { "project_id": {
"in": "query", "in": "query",
"name": "project_id", "name": "project_id",
"required": True,
"description": "The uuid of a project.", "description": "The uuid of a project.",
"schema": {"type": "string", "format": "uuid"}, "schema": {"type": "string", "format": "uuid"},
"x-openstack": {"resource_link": "identity/v3/project.id"},
}, },
"user_id": { "user_id": {
"in": "query", "in": "query",
"name": "user_id", "name": "user_id",
"description": "The uuid of a user.", "description": "The uuid of a user.",
"schema": {"type": "string", "format": "uuid"}, "schema": {"type": "string", "format": "uuid"},
"x-openstack": {"resource_link": "identity/v3/user.id"},
}, },
"consumer_type": { "consumer_type": {
"in": "query", "in": "query",

@ -602,15 +602,35 @@ class RequestTypeManager(common_rust.TypeManager):
original_data_type=original_data_type, original_data_type=original_data_type,
item_type=String(), item_type=String(),
) )
elif isinstance(type_model, model.Dictionary) and isinstance( elif isinstance(type_model, model.Dictionary):
type_model.value_type, model.Dictionary if isinstance(type_model.value_type, model.Dictionary):
):
original_data_type = self.convert_model(type_model.value_type) original_data_type = self.convert_model(type_model.value_type)
typ = JsonValue( typ = JsonValue(
original_data_type=DictionaryInput( original_data_type=DictionaryInput(
value_type=original_data_type value_type=original_data_type
) )
) )
else:
if isinstance(type_model.value_type, model.Struct):
# Placement has dict of structs of dicts of structs ...
# Only simplify the top level stuff
original_data_type = self.convert_model(
type_model.value_type
)
typ = DictionaryInput(
description=type_model.value_type.description,
value_type=JsonValue(
original_data_type=original_data_type
),
)
if not model_ref:
model_ref = model.Reference(
name="Body", type=typ.__class__
)
if type_model.value_type.reference:
self.ignored_models.append(
type_model.value_type.reference
)
if typ: if typ:
if model_ref: if model_ref:

@ -70,9 +70,23 @@
.into_iter() .into_iter()
.map(|(k, v)| (k, v.as_ref().map(Into::into))), .map(|(k, v)| (k, v.as_ref().map(Into::into))),
); );
{%- else %}
{%- set original_type = root.value_type.original_data_type %}
{%- if root.value_type.__class__.__name__ == "JsonValue" and original_type.__class__.__name__ == "StructInput" %}
{#- Placement hacks #}
ep_builder.properties(
properties
.into_iter()
.map(|(k, v)| {
serde_json::from_value(v.to_owned()).map(|v: {{ sdk_mod_path[-1] }}::{{ original_type.name }}| (k, v))
})
.collect::<Result<Vec<_>, _>>()?
.into_iter(),
);
{%- else %} {%- else %}
ep_builder.properties(properties.iter().cloned()); ep_builder.properties(properties.iter().cloned());
{%- endif %} {%- endif %}
{%- endif %}
} }
{%- endif %} {%- endif %}
{%- endwith %} {%- endwith %}

@ -72,16 +72,25 @@ Option
{%- set dt = field.data_type if not is_opt else field.data_type.item_type %} {%- set dt = field.data_type if not is_opt else field.data_type.item_type %}
{%- set val_type = field.data_type.value_type %} {%- set val_type = field.data_type.value_type %}
{{ docstring(field.description, indent=4) }} {{ docstring(field.description, indent=4) }}
pub fn {{ field.local_name }}<I, K, V{{ ",K1, V1" if val_type.__class__.__name__ == "BTreeMap" else ""}}>(&mut self, iter: I) -> &mut Self pub fn {{ field.local_name }}<I, K, V
{%- if val_type.__class__.__name__ == "BTreeMap" -%}
, K1, V1
{%- elif val_type.__class__.__name__ == "Array" -%}
, V1
{%- endif -%}
>(&mut self, iter: I) -> &mut Self
where where
I: Iterator<Item = (K, V)>, I: Iterator<Item = (K, V)>,
K: Into<Cow<'a, str>>, K: Into<Cow<'a, str>>,
{%- if val_type.__class__.__name__ != "BTreeMap" %} {%- if val_type.__class__.__name__ == "BTreeMap" %}
V: Into<{{ dt.value_type.type_hint }}>,
{% else %}
V: Iterator<Item = (K1, V1)>, V: Iterator<Item = (K1, V1)>,
K1: Into<Cow<'a, str>>, K1: Into<Cow<'a, str>>,
V1: Into<{{ val_type.value_type.type_hint }}>, V1: Into<{{ val_type.value_type.type_hint }}>,
{%- elif val_type.__class__.__name__ == "Array" %}
V: IntoIterator<Item = V1>,
V1: Into<{{ val_type.item_type.type_hint }}>,
{% else %}
V: Into<{{ dt.value_type.type_hint }}>,
{% endif%} {% endif%}
{ {
self.{{ field.local_name }} self.{{ field.local_name }}
@ -94,13 +103,15 @@ Option
.get_or_insert_with(BTreeMap::new) .get_or_insert_with(BTreeMap::new)
.extend(iter.map(|(k, v)| ( .extend(iter.map(|(k, v)| (
k.into(), k.into(),
{%- if val_type.__class__.__name__ != "BTreeMap" %} {%- if val_type.__class__.__name__ == "BTreeMap" %}
v.into()
{%- else %}
BTreeMap::from_iter( BTreeMap::from_iter(
v.into_iter() v.into_iter()
.map(|(k1, v1)| {(k1.into(), v1.into())}) .map(|(k1, v1)| {(k1.into(), v1.into())})
) )
{%- elif val_type.__class__.__name__ == "Array" %}
v.into_iter().map(Into::into).collect()
{%- else %}
v.into()
{%- endif %} {%- endif %}
))); )));
self self
@ -145,12 +156,17 @@ Some({{ val }})
{#- Macros to render setting Request data from CLI input #} {#- Macros to render setting Request data from CLI input #}
{%- macro set_request_data_from_input(manager, dst_var, param, val_var) %} {%- macro set_request_data_from_input(manager, dst_var, param, val_var) %}
{%- set is_nullable = param.is_nullable if param.is_nullable is defined else False %} {%- set is_nullable = param.is_nullable if param.is_nullable is defined else False %}
{%- if param.type_hint in ["Option<Option<bool>>", "Option<Option<i32>>", "Option<Option<i64>>"] %} {%- if param.type_hint in ["Option<Option<bool>>", "Option<Option<i32>>", "Option<Option<i64>>"] %}
{{ dst_var }}.{{ param.remote_name }}({{ "*" + val_var }}); {{ dst_var }}.{{ param.remote_name }}({{ "*" + val_var }});
{%- elif param.type_hint in ["Option<i32>", "Option<i64>", "Option<f32>", "Option<f64>", "Option<bool>"] %} {%- elif param.type_hint in ["Option<i32>", "Option<i64>", "Option<f32>", "Option<f64>", "Option<bool>"] %}
{%- if param.is_optional is defined and not param.is_optional %}
if let Some(val) = {{ val_var }} {
{{ dst_var }}.{{ param.remote_name }}(*val);
}
{%- else %}
{{ dst_var }}.{{ param.remote_name }}({{ "*" + val_var }}); {{ dst_var }}.{{ param.remote_name }}({{ "*" + val_var }});
{%- endif %}
{%- elif param.type_hint in ["i32", "i64", "f32", "f64", "bool"] %} {%- elif param.type_hint in ["i32", "i64", "f32", "f64", "bool"] %}
{{ dst_var }}.{{ param.remote_name }}({{ val_var | replace("&", "" )}}); {{ dst_var }}.{{ param.remote_name }}({{ val_var | replace("&", "" )}});
@ -274,10 +290,15 @@ Some({{ val }})
{%- if param.data_type.value_type.__class__.__name__ == "JsonValue" and original_type.__class__.__name__ == "StructInput" %} {%- if param.data_type.value_type.__class__.__name__ == "JsonValue" and original_type.__class__.__name__ == "StructInput" %}
{% set builder_name = param.local_name + "_builder" %} {% set builder_name = param.local_name + "_builder" %}
{{ dst_var}}.{{ param.remote_name }}( {{ dst_var}}.{{ param.remote_name }}(
{%- if val_var.startswith("&self") %}
{{ val_var | replace("&", "") }}
{%- else %}
{{ val_var }} {{ val_var }}
.into_iter() {%- endif %}
.iter()
.map(|(k, v)| { .map(|(k, v)| {
serde_json::from_value(v.to_owned()).map(|v: {{ sdk_mod_path[-1] }}::{{ original_type.name }}| (k, v)) {#- sdk uses Struct while cli uses StructInput -> thus replace #}
serde_json::from_value(v.to_owned()).map(|v: {{ sdk_mod_path[-1] }}::{{ original_type.name | replace("StructInput", "Struct") }}| (k, v))
}) })
.collect::<Result<Vec<_>, _>>()? .collect::<Result<Vec<_>, _>>()?
.into_iter(), .into_iter(),
@ -285,6 +306,16 @@ Some({{ val }})
{%- elif param.data_type.value_type.__class__.__name__ == "Option" %} {%- elif 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)))); {{ dst_var }}.{{ param.remote_name }}({{ val_var | replace("&", "") }}.iter().cloned().map(|(k, v)| (k, v.map(Into::into))));
{%- elif param.data_type.value_type.__class__.__name__ == "JsonValue" and original_type.__class__.__name__ == "ArrayInput" %}
{{ dst_var }}.{{ param.remote_name }}(
{{ val_var }}.iter()
.map(|(k, v)| {
serde_json::from_value::<Vec<{{ original_type.item_type.type_hint }}>>(v.to_owned())
.map(|v| (k, v.into_iter()))
})
.collect::<Result<Vec<_>, _>>()?
.into_iter(),
);
{%- else %} {%- else %}
{{ dst_var }}.{{ param.remote_name }}({{ val_var | replace("&", "") }}.iter().cloned()); {{ dst_var }}.{{ param.remote_name }}({{ val_var | replace("&", "") }}.iter().cloned());
{%- endif %} {%- endif %}

@ -174,7 +174,11 @@ impl{{ type_manager.get_request_static_lifetimes(request) }} RestEndpoint for Re
self.{{ param.local_name }}.as_ref() self.{{ param.local_name }}.as_ref()
); );
{%- elif not param.type_hint.startswith("BTreeSet") %} {%- elif not param.type_hint.startswith("BTreeSet") %}
{%- if param.is_required %}
params.push(
{%- else %}
params.push_opt( params.push_opt(
{%- endif %}
"{{ param.remote_name }}", "{{ param.remote_name }}",
self.{{ param.local_name}} self.{{ param.local_name}}
{%- if "Cow<" in param.type_hint %} {%- if "Cow<" in param.type_hint %}
@ -232,7 +236,11 @@ impl{{ type_manager.get_request_static_lifetimes(request) }} RestEndpoint for Re
let mut params = JsonBodyParams::default(); let mut params = JsonBodyParams::default();
for (key, val) in &self._properties { for (key, val) in &self._properties {
{%- if request.value_type.base_type is defined and request.value_type.base_type == "struct" %}
params.push(key.clone(), serde_json::to_value(val)?);
{%- else %}
params.push(key.clone(), serde_json::Value::from(val.clone())); params.push(key.clone(), serde_json::Value::from(val.clone()));
{%- endif %}
} }
params.into_body() params.into_body()

@ -164,7 +164,7 @@ resources:
rust-cli: rust-cli:
module_name: delete_all module_name: delete_all
sdk_mod_name: delete_all sdk_mod_name: delete_all
cli_full_command: resource-provider inventory delete-all cli_full_command: resource-provider inventory purge
show: show:
operation_id: resource_providers/uuid/inventories/resource_class:get operation_id: resource_providers/uuid/inventories/resource_class:get
operation_type: show operation_type: show