Skip to content
This repository has been archived by the owner on Apr 10, 2024. It is now read-only.

feat: Adapt generator for image.image rendering #4

Merged
merged 1 commit into from
Jan 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 70 additions & 39 deletions codegenerator/common/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
#
import logging
from pathlib import Path
from typing import Any
import re
Expand Down Expand Up @@ -107,46 +108,66 @@ def find_resource_schema(
if not found

"""
if "type" not in schema:
# Response of server create is a server or reservation_id
raise RuntimeError("No type in %s" % schema)
schema_type = schema["type"]
if schema_type == "array":
if (
parent
and resource_name
and parent == get_plural_form(resource_name)
):
return (schema["items"], parent)
elif not parent and schema.get("items", {}).get("type") == "object":
# Array on the top level. Most likely we are searching for items
# directly
return (schema["items"], None)
return find_resource_schema(
schema["items"], parent, resource_name=resource_name
)
elif schema_type == "object":
props = (
schema.properties
if hasattr(schema, "properties")
else schema.get("properties", {})
)
if not parent and resource_name in props:
# we are at the top level and there is property with the resource
# name - it is what we are searching for
return (props[resource_name], resource_name)
for name, item in props.items():
(r, path) = find_resource_schema(item, name, resource_name)
if r:
return (r, path)
if not parent:
# We are on top level and have not found anything.
keys = list(props.keys())
if len(keys) == 1:
# there is only one field in the object
return (props[keys[0]], keys[0])
try:
if "type" not in schema:
# Response of server create is a server or reservation_id
if "allOf" in schema:
kinds = {}
for kind in schema["allOf"]:
kinds.update(kind)
schema["type"] = kinds["type"]
elif schema == {}:
return (None, None)
elif "properties" in schema:
schema["type"] = "object"
else:
return (schema, None)
raise RuntimeError("No type in %s" % schema)
schema_type = schema["type"]
if schema_type == "array":
if (
parent
and resource_name
and parent == get_plural_form(resource_name)
):
return (schema["items"], parent)
elif (
not parent and schema.get("items", {}).get("type") == "object"
):
# Array on the top level. Most likely we are searching for items
# directly
return (schema["items"], None)
return find_resource_schema(
schema.get("items", {"type": "string"}),
parent,
resource_name=resource_name,
)
elif schema_type == "object":
props = (
schema.properties
if hasattr(schema, "properties")
else schema.get("properties", {})
)
if not parent and resource_name in props:
# we are at the top level and there is property with the resource
# name - it is what we are searching for
return (props[resource_name], resource_name)
for name, item in props.items():
(r, path) = find_resource_schema(item, name, resource_name)
if r:
return (r, path)
if not parent:
# We are on top level and have not found anything.
keys = list(props.keys())
if len(keys) == 1:
# there is only one field in the object
return (props[keys[0]], keys[0])
else:
return (schema, None)
except Exception as ex:
logging.exception(
f"Caught exception {ex} during processing of {schema}"
)
raise
return (None, None)


Expand Down Expand Up @@ -188,6 +209,16 @@ def get_resource_names_from_url(path: str):
path_resource_names.pop()
if len(path_resource_names) == 0:
return ["Version"]
if path.startswith("/v2/schemas/"):
# Image schemas should not be singularized (schema/images,
# schema/image)
if path.endswith("s") and not path_resource_names[-1].endswith("s"):
path_resource_names[-1] += "s"
if path.startswith("/v2/images") and path.endswith("/actions/deactivate"):
path_resource_names = ["image"]
if path.startswith("/v2/images") and path.endswith("/actions/reactivate"):
path_resource_names = ["image"]

return path_resource_names


Expand Down
29 changes: 26 additions & 3 deletions codegenerator/common/rust.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,9 @@ class Struct(BaseCompoundType):
base_type: str = "struct"
fields: dict[str, StructField] = {}
field_type_class_: Type[StructField] | StructField = StructField
additional_fields_type: BasePrimitiveType | BaseCombinedType | BaseCompoundType | None = (
None
)

@property
def type_hint(self):
Expand All @@ -237,6 +240,9 @@ def imports(self):
imports: set[str] = set(["serde::Deserialize"])
for field in self.fields.values():
imports.update(field.data_type.imports)
if self.additional_fields_type:
imports.add("std::collections::BTreeMap")
imports.update(self.additional_fields_type.imports)
return imports

@property
Expand Down Expand Up @@ -309,7 +315,7 @@ class StringEnum(BaseCompoundType):
"#[derive(Debug, Deserialize, Clone, Serialize)]"
)
builder_container_macros: str | None = None
serde_container_macros: str | None = "#[serde(untagged)]"
serde_container_macros: str | None = None # "#[serde(untagged)]"
serde_macros: set[str] | None = None
original_data_type: BaseCompoundType | BaseCompoundType | None = None

Expand All @@ -332,7 +338,7 @@ def variant_serde_macros(self, variant: str):
"""Return serde macros"""
return (
"#[serde("
+ ", ".join([f'alias="{x}"' for x in self.variants[variant]])
+ ", ".join([f'rename="{x}"' for x in self.variants[variant]])
+ ")]"
)

Expand Down Expand Up @@ -431,6 +437,10 @@ def get_local_attribute_name(self, name: str) -> str:
)
if attr_name == "type":
attr_name = "_type"
elif attr_name == "self":
attr_name = "_self"
elif attr_name == "enum":
attr_name = "_self"
return attr_name

def get_remote_attribute_name(self, name: str) -> str:
Expand All @@ -445,10 +455,11 @@ def get_model_name(self, model_ref: model.Reference | None) -> str:
"""Get the localized model type name"""
if not model_ref:
return "Request"
return "".join(
name = "".join(
x.capitalize()
for x in re.split(common.SPLIT_NAME_RE, model_ref.name)
)
return name

def _get_adt_by_reference(self, model_ref):
for model_ in self.models:
Expand Down Expand Up @@ -689,6 +700,15 @@ def _get_struct_type(self, type_model: model.Struct) -> Struct:
is_nullable=is_nullable,
)
mod.fields[field_name] = f
if type_model.additional_fields:
definition = type_model.additional_fields
# Structure allows additional fields
if isinstance(definition, bool):
mod.additional_fields_type = self.primitive_type_mapping[
model.PrimitiveAny
]
else:
mod.additional_fields_type = self.convert_model(definition)
return mod

def _simplify_oneof_combinations(self, type_model, kinds):
Expand Down Expand Up @@ -949,6 +969,9 @@ def get_operation_variants(spec: dict, operation_name: str):
)
else:
operation_variants.append({"body": json_body_schema})
elif "application/octet-stream" in content:
mime_type = "application/octet-stream"
operation_variants.append({"mime_type": mime_type})
elif "application/openstack-images-v2.1-json-patch" in content:
mime_type = "application/openstack-images-v2.1-json-patch"
operation_variants.append({"mime_type": mime_type})
Expand Down
50 changes: 48 additions & 2 deletions codegenerator/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,15 @@ def generate(
operations=dict(),
),
)
for method in ["head", "get", "put", "post", "delete", "options"]:
for method in [
"head",
"get",
"put",
"post",
"delete",
"options",
"patch",
]:
operation = getattr(spec, method, None)
if operation:
if not operation.operationId:
Expand All @@ -97,13 +105,24 @@ def generate(
operation_key = "show"
elif method == "put":
operation_key = "update"
elif method == "patch":
operation_key = "patch"
elif method == "post":
operation_key = "create"
elif method == "delete":
operation_key = "delete"
elif path.endswith("/detail"):
if method == "get":
operation_key = "list_detailed"
# elif path.endswith("/default"):
# operation_key = "default"
elif path == "/v2/images/{image_id}/file":
if method == "put":
operation_key = "upload"
elif method == "get":
operation_key = "download"
else:
raise NotImplementedError
elif response_schema and (
method == "get"
and (
Expand All @@ -122,6 +141,14 @@ def generate(
elif path.endswith("/action"):
# Action
operation_key = "action"
elif args.service_type == "image" and path.endswith(
"/actions/deactivate"
):
operation_key = "deactivate"
elif args.service_type == "image" and path.endswith(
"/actions/reactivate"
):
operation_key = "reactivate"
elif (
len(
[
Expand Down Expand Up @@ -149,16 +176,23 @@ def generate(
operation_key = "create"
elif method == "put":
operation_key = path.split("/")[-1]
elif method == "patch":
operation_key = "patch"
elif method == "delete":
operation_key = "delete"
if not operation_key:
logging.warn(
f"Cannot identify op name for {path}:{method}"
)

if operation_key in resource_model:
raise RuntimeError("Operation name conflict")
else:
if operation_key == "action":
if (
operation_key == "action"
and args.service_type
in ["compute", "block-storage"]
):
# For action we actually have multiple independent operations
try:
body_schema = operation.requestBody["content"][
Expand Down Expand Up @@ -273,6 +307,12 @@ def generate(
rust_sdk_params.response_list_item_key = (
"keypair"
)
# Image schemas are a JSON operation
if (
args.service_type == "image"
and resource_name.startswith("schema")
):
rust_cli_params.operation_type = "json"

op_model.targets["rust-sdk"] = rust_sdk_params
if rust_cli_params:
Expand Down Expand Up @@ -401,8 +441,14 @@ def get_operation_type_by_key(operation_key):
return "delete"
elif operation_key in ["create"]:
return "create"
elif operation_key == "patch":
return "set"
elif operation_key == "default":
return "get"
elif operation_key == "download":
return "download"
elif operation_key == "upload":
return "upload"
else:
return "action"

Expand Down
Loading