232 lines
9.4 KiB
Python
232 lines
9.4 KiB
Python
from openapi_python_client.schema import OpenAPI, Schema, Reference
|
|
from jinja2 import Environment, FileSystemLoader
|
|
from typing import Dict, Any
|
|
|
|
import os
|
|
|
|
|
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
env = Environment(loader=FileSystemLoader(script_dir))
|
|
|
|
class VlangCodeGenerator:
|
|
def __init__(self, python_code: OpenAPI, output_dir: str) -> None:
|
|
self.python_code = python_code
|
|
self.output_dir = output_dir
|
|
self.struct_template = env.get_template("templates/struct.jinja")
|
|
|
|
def generate(self):
|
|
"""
|
|
Main generation method to create V code.
|
|
"""
|
|
# Ensure the output directory exists
|
|
os.makedirs(self.output_dir, exist_ok=True)
|
|
|
|
structs = self._generate_structs()
|
|
print('structs: ', structs)
|
|
# methods = self._generate_methods()
|
|
|
|
# # Combine structs and methods into one file
|
|
vlang_code = structs
|
|
output_file = f"{self.output_dir}/generated.v"
|
|
|
|
# Write to file
|
|
with open(output_file, "w") as file:
|
|
file.write(vlang_code)
|
|
print(f"Vlang code generated at {output_file}")
|
|
|
|
def _generate_struct(self, struct_name: str, scheme: Schema | Reference) -> str:
|
|
properties = {}
|
|
code = ""
|
|
|
|
for field_name, field in scheme.properties.items(): # type: ignore
|
|
v_type = self._convert_type(field.type) # type: ignore
|
|
|
|
if field.type == 'object': # type: ignore
|
|
# Capitalize each part of the field name and create a nested struct name
|
|
nested_struct_name = ''.join(part.capitalize() for part in field_name.split("_"))
|
|
|
|
# Generate the struct for the nested object
|
|
code += self._generate_struct(struct_name=nested_struct_name, scheme=field)
|
|
|
|
# Update v_type to the newly generated nested struct name
|
|
v_type = nested_struct_name
|
|
|
|
# Update the properties dictionary with type name and description
|
|
properties[field_name] = {
|
|
'type_name': v_type,
|
|
'description': field.description # type: ignore
|
|
}
|
|
|
|
code += "\n"
|
|
code += self.struct_template.render(
|
|
struct_name=struct_name,
|
|
properties= properties # type: ignore
|
|
)
|
|
code += "\n"
|
|
|
|
return code
|
|
|
|
def _generate_structs(self) -> str:
|
|
"""
|
|
Generate V structs from OpenAPI components with support for nested objects and arrays.
|
|
"""
|
|
if not self.python_code.components:
|
|
raise ValueError("No components found in spec")
|
|
|
|
if not self.python_code.components.schemas:
|
|
raise ValueError("No schemas found in components")
|
|
|
|
code = ""
|
|
|
|
for struct_name, schema in self.python_code.components.schemas.items():
|
|
code += self._generate_struct(struct_name=struct_name, scheme=schema)
|
|
|
|
return code
|
|
|
|
# structs_code = []
|
|
# for schema_name, schema in self.python_code.components.schemas.items():
|
|
# fields = []
|
|
# for field_name, field in schema.properties.items(): # type: ignore
|
|
# if field.type == "object": # type: ignore
|
|
# # Generate a nested struct
|
|
# parts = field_name.split("_")
|
|
# nested_struct_name = ""
|
|
# for part in parts:
|
|
# nested_struct_name += part.capitalize()
|
|
# nested_struct = self._generate_struct_from_object(nested_struct_name, field) # type: ignore
|
|
# structs_code.append(nested_struct)
|
|
# fields.append(f"\t{field_name} {nested_struct_name}")
|
|
# print(f"Generated struct for {nested_struct_name}")
|
|
# elif field.type == "array": # type: ignore
|
|
# # Handle arrays with proper type conversion for items
|
|
# item_type = self._convert_type(field.items.type) # type: ignore
|
|
# fields.append(f"\t{field_name} []{item_type}")
|
|
# else:
|
|
# # Convert JSON schema type to V type
|
|
# v_type = self._convert_type(field.type) # type: ignore
|
|
# fields.append(f"\t{field_name} {v_type}")
|
|
|
|
# # Construct struct
|
|
# struct_code = f"pub struct {schema_name} {{\n" + "\n".join(fields) + "\n}"
|
|
# structs_code.append(struct_code)
|
|
# print(f"Generated struct for {schema_name}")
|
|
|
|
# return "\n\n".join(structs_code)
|
|
|
|
# def _generate_struct_from_object(self, struct_name: str, schema: dict) -> str:
|
|
# """
|
|
# Generate a nested struct from an object schema.
|
|
# """
|
|
# fields = []
|
|
# for field_name, field in schema.properties.items(): # type: ignore
|
|
# v_type = self._convert_type(field.type) # type: ignore
|
|
# fields.append(f"\t{field_name} {v_type}")
|
|
|
|
# return f"struct {struct_name} {{\n" + "\n".join(fields) + "\n}"
|
|
|
|
# def _generate_methods(self) -> str:
|
|
# """
|
|
# Generate V methods based on OpenAPI paths and operations.
|
|
# """
|
|
# if not self.python_code.paths:
|
|
# raise ValueError("No paths found in spec")
|
|
|
|
# methods_code = []
|
|
# for path, path_item in self.python_code.paths.items():
|
|
# # Explicitly check for HTTP method attributes in PathItem
|
|
# for http_method in ["get", "post", "put", "delete", "patch", "options", "head"]:
|
|
# operation = getattr(path_item, http_method, None)
|
|
# if operation:
|
|
# # Generate method name and parameters
|
|
# method_name = self._generate_method_name(http_method, path)
|
|
# parameters = self._generate_method_parameters(operation.parameters)
|
|
# request_body = self._generate_request_body(operation.request_body)
|
|
# response_type = self._generate_response_type(operation.responses)
|
|
|
|
# # Combine method arguments
|
|
# method_arguments = parameters
|
|
# if request_body:
|
|
# method_arguments += f", {request_body}" if parameters else request_body
|
|
|
|
# # Generate the method code
|
|
# method_code = f"fn {method_name}({method_arguments}) {response_type} {{\n"
|
|
# method_code += f"\t// TODO: Implement the {http_method.upper()} request to {path}\n"
|
|
# method_code += "\t// Use the generated structs for request/response bodies\n"
|
|
# method_code += "}\n"
|
|
# methods_code.append(method_code)
|
|
|
|
# print(f"Generated method for {http_method.upper()} {path}")
|
|
|
|
# return "\n\n".join(methods_code)
|
|
|
|
# def _generate_method_name(self, http_method: str, path: str) -> str:
|
|
# """
|
|
# Generate a method name from the HTTP method and path.
|
|
# """
|
|
# # Remove leading/trailing slashes and replace `/` with `_`
|
|
# sanitized_path = path.strip("/").replace("/", "_").replace("{", "").replace("}", "")
|
|
# return f"{http_method.lower()}_{sanitized_path}"
|
|
|
|
# def _generate_method_parameters(self, parameters) -> str:
|
|
# if not parameters:
|
|
# return ""
|
|
|
|
# param_list = []
|
|
# for param in parameters:
|
|
# param_name = param.name
|
|
# param_schema = getattr(param, "schema", None)
|
|
# print('param_name: ', param_name)
|
|
# print('param_schema: ', param_schema)
|
|
# # if param_schema and param_schema.type:
|
|
# # param_type = self._convert_type(param_schema.type)
|
|
# # param_list.append(f"{param_name} {param_type}")
|
|
|
|
# return ", ".join(param_list)
|
|
|
|
|
|
# def _generate_request_body(self, request_body) -> str:
|
|
# """
|
|
# Generate a function parameter for the request body if present.
|
|
# """
|
|
# if not request_body or not request_body.content:
|
|
# return ""
|
|
|
|
# # Assume application/json content type
|
|
# json_schema = request_body.content.get("application/json")
|
|
# if not json_schema or not json_schema.schema:
|
|
# return ""
|
|
|
|
# print('body_type: ', json_schema)
|
|
# # body_type = json_schema.schema.ref.split("/")[-1] # Extract the schema name
|
|
# return f"body {json_schema}"
|
|
|
|
# def _generate_response_type(self, responses) -> str:
|
|
# """
|
|
# Determine the return type of the method based on responses.
|
|
# """
|
|
# if not responses:
|
|
# return "void"
|
|
|
|
# for status_code, response in responses.items():
|
|
# if response.content and "application/json" in response.content:
|
|
# json_schema = response.content["application/json"].schema
|
|
# print('json_schema: ', json_schema)
|
|
# # if json_schema and json_schema.ref:
|
|
# # return json_schema.ref.split("/")[-1] # Extract schema name
|
|
|
|
# return "void"
|
|
|
|
def _convert_type(self, json_type: str) -> str:
|
|
"""
|
|
Map JSON schema types to Vlang types.
|
|
"""
|
|
type_mapping = {
|
|
"string": "string",
|
|
"integer": "int",
|
|
"number": "f64",
|
|
"boolean": "bool",
|
|
"array": "[]",
|
|
}
|
|
return type_mapping.get(json_type, "string") # Default to `string`
|
|
|