From fb41208b55c28949c4d15d3eef70e465deacd97f Mon Sep 17 00:00:00 2001
From: Artem Goncharov <artem.goncharov@gmail.com>
Date: Sat, 17 Aug 2024 15:52:13 +0200
Subject: [PATCH] Flip use of UNSET

in the previous change we added support for UNSET to have empty schema.
It is more logical to use UNSET when the schema is not defined and not
when it is empty. Fix that.

Change-Id: I6568ca826d119b42f74d3f2ee9a691ac1278bc26
---
 codegenerator/openapi/base.py     | 67 ++++++++++++++++++-------------
 codegenerator/openapi/cinder.py   |  2 +-
 codegenerator/openapi/glance.py   |  4 +-
 codegenerator/openapi/keystone.py |  2 +-
 codegenerator/openapi/nova.py     |  5 ++-
 5 files changed, 47 insertions(+), 33 deletions(-)

diff --git a/codegenerator/openapi/base.py b/codegenerator/openapi/base.py
index 6920ea4..8d37cf9 100644
--- a/codegenerator/openapi/base.py
+++ b/codegenerator/openapi/base.py
@@ -601,7 +601,7 @@ class OpenStackServerSourceBase:
 
         action_name = None
         query_params_versions = []
-        body_schemas = []
+        body_schemas: list[str | None] | Unset = UNSET
         expected_errors = ["404"]
         response_code = None
         # Version bound on an operation are set only when it is not an
@@ -682,13 +682,16 @@ class OpenStackServerSourceBase:
                 openapi_spec.components.schemas.setdefault(
                     schema_name, TypeSchema(**body_schema)
                 )
-                body_schemas.append(f"#/components/schemas/{schema_name}")
+                if body_schemas is UNSET:
+                    body_schemas = []
+                if isinstance(body_schemas, list):
+                    body_schemas.append(f"#/components/schemas/{schema_name}")
             rsp_spec = getattr(fdef, "return_type", None)
             if rsp_spec:
                 ser_schema = _convert_wsme_to_jsonschema(rsp_spec)
             response_code = getattr(fdef, "status_code", None)
 
-        if not body_schemas and deser_schema:
+        if body_schemas is UNSET and deser_schema:
             # Glance may have request deserializer attached schema
             schema_name = (
                 "".join([x.title() for x in path_resource_names])
@@ -903,7 +906,10 @@ class OpenStackServerSourceBase:
         mode,
         action_name,
     ):
-        mime_type: str = "application/json"
+        # Body is not expected, exit (unless we are in the "action")
+        if body_schemas is None or (body_schemas == [] and mode != "action"):
+            return
+        mime_type: str | None = "application/json"
         schema_name = None
         schema_ref: str | Unset | None = None
         # We should not modify path_resource_names of the caller
@@ -917,10 +923,10 @@ class OpenStackServerSourceBase:
         )
         cont_schema = None
 
-        if len(body_schemas) == 1:
+        if body_schemas is not UNSET and len(body_schemas) == 1:
             # There is only one body known at the moment
             # None is a special case with explicitly no body supported
-            if body_schemas[0] is not UNSET:
+            if True:  # body_schemas[0] is not UNSET:
                 if cont_schema_name in openapi_spec.components.schemas:
                     # if we have already oneOf - add there
                     cont_schema = openapi_spec.components.schemas[
@@ -934,7 +940,7 @@ class OpenStackServerSourceBase:
                 else:
                     # otherwise just use schema as body
                     schema_ref = body_schemas[0]
-        elif len(body_schemas) > 1:
+        elif body_schemas is not UNSET and len(body_schemas) > 1:
             # We may end up here multiple times if we have versioned operation. In this case merge to what we have already
             op_body = operation_spec.requestBody.setdefault("content", {})
             old_schema = op_body.get(mime_type, {}).get("schema", {})
@@ -968,7 +974,7 @@ class OpenStackServerSourceBase:
                 # not to create container. Now we need to change that by
                 # merging with previous data
                 cont_schema.oneOf.append({"$ref": old_ref})
-        elif len(body_schemas) == 0 and mode == "action":
+        elif mode == "action":
             # There are actions without a real body description, but we know that action requires dummy body
             cont_schema = openapi_spec.components.schemas.setdefault(
                 cont_schema_name,
@@ -980,7 +986,7 @@ class OpenStackServerSourceBase:
                 ),
             )
             schema_ref = f"#/components/schemas/{cont_schema_name}"
-        elif len(body_schemas) == 0:
+        elif body_schemas is UNSET:
             # We know nothing about request
             schema_name = (
                 "".join([x.title() for x in path_resource_names])
@@ -996,6 +1002,7 @@ class OpenStackServerSourceBase:
                 schema_name,
                 description=f"Request of the {operation_spec.operationId} operation",
                 action_name=action_name,
+                schema_def=UNSET,
             )
 
         if mode == "action":
@@ -1011,7 +1018,7 @@ class OpenStackServerSourceBase:
             os_ext["discriminator"] = "action"
             if cont_schema and action_name:
                 cont_schema.openstack["action-name"] = action_name
-        elif schema_ref is not None and schema_ref is not UNSET:
+        elif schema_ref is not None:
             op_body = operation_spec.requestBody.setdefault("content", {})
             js_content = op_body.setdefault(mime_type, {})
             body_schema = js_content.setdefault("schema", {})
@@ -1134,19 +1141,20 @@ class OpenStackServerSourceBase:
         self,
         openapi_spec,
         name,
-        description=None,
-        schema_def=None,
+        description: str | None = None,
+        schema_def=UNSET,
         action_name=None,
-    ) -> tuple[str, str]:
-        if not schema_def:
+    ) -> tuple[str | None, str | None]:
+        if schema_def is UNSET:
             logging.warn(
                 "No Schema definition for %s[%s] is known", name, action_name
             )
+            # Create dummy schema since we got no data for it
             schema_def = {
                 "type": "object",
                 "description": LiteralScalarString(description),
             }
-        if schema_def is not UNSET:
+        if schema_def is not None:
             schema = openapi_spec.components.schemas.setdefault(
                 name,
                 TypeSchema(
@@ -1154,12 +1162,14 @@ class OpenStackServerSourceBase:
                 ),
             )
 
-        if action_name:
-            if not schema.openstack:
-                schema.openstack = {}
-            schema.openstack.setdefault("action-name", action_name)
+            if action_name:
+                if not schema.openstack:
+                    schema.openstack = {}
+                schema.openstack.setdefault("action-name", action_name)
 
-        return (f"#/components/schemas/{name}", "application/json")
+            return (f"#/components/schemas/{name}", "application/json")
+        else:
+            return (None, None)
 
     def _get_tags_for_url(self, url):
         """Return Tag (group) name based on the URL"""
@@ -1200,7 +1210,7 @@ class OpenStackServerSourceBase:
         """Extract schemas from the decorated method."""
         # Unwrap operation decorators to access all properties
         expected_errors: list[str] = []
-        body_schemas: list[str | Unset] = []
+        body_schemas: list[str | None] | Unset = UNSET
         query_params_versions: list[tuple] = []
         response_body_schema: dict | Unset | None = UNSET
 
@@ -1234,6 +1244,9 @@ class OpenStackServerSourceBase:
                     "request_body_schema",
                     getattr(f, "_request_body_schema", {}),
                 )
+                # body schemas are not UNSET anymore
+                if body_schemas is UNSET:
+                    body_schemas = []
                 if obj is not None:
                     if obj.get("type") in ["object", "array"]:
                         # We only allow object and array bodies
@@ -1272,9 +1285,12 @@ class OpenStackServerSourceBase:
                             comp_schema.openstack["action-name"] = action_name
 
                         ref_name = f"#/components/schemas/{typ_name}"
-                        body_schemas.append(ref_name)
+                        if isinstance(body_schemas, list):
+                            body_schemas.append(ref_name)
                 else:
-                    body_schemas.append(UNSET)
+                    # register no-body
+                    if isinstance(body_schemas, list):
+                        body_schemas.append(None)
 
             if "response_body_schema" in closure_locals or hasattr(
                 f, "_response_body_schema"
@@ -1284,10 +1300,7 @@ class OpenStackServerSourceBase:
                     "response_body_schema",
                     getattr(f, "_response_body_schema", {}),
                 )
-                if obj is not None:
-                    response_body_schema = obj
-                else:
-                    response_body_schema = UNSET
+                response_body_schema = obj
             if "query_params_schema" in closure_locals or hasattr(
                 f, "_request_query_schema"
             ):
diff --git a/codegenerator/openapi/cinder.py b/codegenerator/openapi/cinder.py
index 367d875..5e31f00 100644
--- a/codegenerator/openapi/cinder.py
+++ b/codegenerator/openapi/cinder.py
@@ -203,7 +203,7 @@ class CinderV3Generator(OpenStackServerSourceBase):
         schema_def=None,
         action_name=None,
     ):
-        mime_type: str = "application/json"
+        mime_type: str | None = "application/json"
 
         # Invoke modularized schema _get_schema_ref
         for resource_mod in self.RESOURCE_MODULES:
diff --git a/codegenerator/openapi/glance.py b/codegenerator/openapi/glance.py
index 251ab9a..a456851 100644
--- a/codegenerator/openapi/glance.py
+++ b/codegenerator/openapi/glance.py
@@ -423,8 +423,8 @@ class GlanceGenerator(OpenStackServerSourceBase):
         from glance.api.v2 import tasks
         from glance import schema as glance_schema
 
-        ref: str
-        mime_type: str = "application/json"
+        ref: str | None
+        mime_type: str | None = "application/json"
 
         if name == "TasksListResponse":
             openapi_spec.components.schemas.setdefault(
diff --git a/codegenerator/openapi/keystone.py b/codegenerator/openapi/keystone.py
index 2378df3..3636fc7 100644
--- a/codegenerator/openapi/keystone.py
+++ b/codegenerator/openapi/keystone.py
@@ -511,7 +511,7 @@ class KeystoneGenerator(OpenStackServerSourceBase):
         # Invoke modularized schema _get_schema_ref
         for resource_mod in self.RESOURCE_MODULES:
             hook = getattr(resource_mod, "_get_schema_ref", None)
-            if hook and schema_def is not UNSET:
+            if hook:
                 (ref, mime_type, matched) = hook(
                     openapi_spec, name, description, schema_def, action_name
                 )
diff --git a/codegenerator/openapi/nova.py b/codegenerator/openapi/nova.py
index 9049675..a4884c1 100644
--- a/codegenerator/openapi/nova.py
+++ b/codegenerator/openapi/nova.py
@@ -158,8 +158,9 @@ class NovaGenerator(OpenStackServerSourceBase):
     ):
         from nova.api.openstack.compute.schemas import flavors
 
-        schema = None
-        mime_type: str = "application/json"
+        schema: None = None
+        ref: str | None
+        mime_type: str | None = "application/json"
         # NOTE(gtema): This must go away once scemas are merged directly to
         # Nova
         # /servers