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

Commit

Permalink
Merge pull request #4 from gtema/f2
Browse files Browse the repository at this point in the history
feat: Adapt generator for image.image rendering
  • Loading branch information
gtema authored Jan 12, 2024
2 parents 496789a + af13019 commit df4a845
Show file tree
Hide file tree
Showing 24 changed files with 1,866 additions and 246 deletions.
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

0 comments on commit df4a845

Please sign in to comment.