Update generate_godot_openapi.py to take file input and generate one file per API tag
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
from collections import defaultdict
|
||||
|
||||
def load_openapi_spec(path: str) -> dict:
|
||||
"""Load OpenAPI spec from YAML or JSON file."""
|
||||
try:
|
||||
import yaml
|
||||
with open(path, "r") as f:
|
||||
return yaml.safe_load(f)
|
||||
except ImportError:
|
||||
# Fallback to JSON if PyYAML is not installed
|
||||
with open(path, "r") as f:
|
||||
return json.load(f)
|
||||
|
||||
def map_type(openapi_type: str) -> str:
|
||||
"""Map OpenAPI types to GDScript types."""
|
||||
type_mapping = {
|
||||
"integer": "int",
|
||||
"number": "float",
|
||||
"boolean": "bool",
|
||||
"array": "Array",
|
||||
"object": "Dictionary",
|
||||
}
|
||||
return type_mapping.get(openapi_type, "String")
|
||||
|
||||
def default_value(openapi_type: str):
|
||||
"""Return default values for GDScript types."""
|
||||
default_mapping = {
|
||||
"integer": "0",
|
||||
"number": "0.0",
|
||||
"boolean": "false",
|
||||
"array": "[]",
|
||||
"object": "{}",
|
||||
}
|
||||
return default_mapping.get(openapi_type, '""')
|
||||
|
||||
def sanitize_class_name(name: str) -> str:
|
||||
"""Convert a name to a valid GDScript class name."""
|
||||
# Replace invalid characters with underscores
|
||||
name = re.sub(r"[^a-zA-Z0-9_]", "_", name)
|
||||
# Capitalize first letter
|
||||
return name[0].upper() + name[1:] if name else "Model"
|
||||
|
||||
def generate_model_class(class_name: str, schema: dict) -> str:
|
||||
"""Generate a GDScript class for a model."""
|
||||
lines = [
|
||||
f"class_name {class_name}",
|
||||
"extends RefCounted",
|
||||
"",
|
||||
]
|
||||
|
||||
# Add properties
|
||||
properties = schema.get("properties", {})
|
||||
for prop_name, prop_schema in properties.items():
|
||||
prop_type = map_type(prop_schema.get("type", "string"))
|
||||
lines.append(f"var {prop_name}: {prop_type}")
|
||||
|
||||
# Add _init method
|
||||
lines.extend([
|
||||
"",
|
||||
"func _init(data: Dictionary):",
|
||||
])
|
||||
for prop_name in properties:
|
||||
prop_type = map_type(properties[prop_name].get("type", "string"))
|
||||
default = default_value(properties[prop_name].get("type", "string"))
|
||||
lines.append(f' {prop_name} = data.get("{prop_name}", {default})')
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
def generate_api_client(path: str, method: str, endpoint: dict) -> str:
|
||||
"""Generate a GDScript API client for an endpoint."""
|
||||
# Sanitize path for class name
|
||||
class_name = sanitize_class_name(path.replace("/", "_").replace("{", "").replace("}", "")) + method.capitalize()
|
||||
# Format URL (replace {param} with %s for Godot's string formatting)
|
||||
url = path.replace("{", "%").replace("}", "s")
|
||||
full_url = f'"{BASE_URL}{url}"'
|
||||
|
||||
lines = [
|
||||
f"class_name {class_name}",
|
||||
"extends RefCounted",
|
||||
"",
|
||||
"var http_request: HTTPRequest",
|
||||
"",
|
||||
"func _init(node: Node):",
|
||||
" http_request = HTTPRequest.new()",
|
||||
" node.add_child(http_request)",
|
||||
' http_request.connect("request_completed", self, "_on_request_completed")',
|
||||
"",
|
||||
f"func call(params: Dictionary, callback: Callable):",
|
||||
f" var url := {full_url}",
|
||||
' var headers = ["User-Agent: MyGodotApp"]',
|
||||
" var error := http_request.request(url, headers)",
|
||||
" if error != OK:",
|
||||
' push_error("HTTP request failed.")',
|
||||
" return",
|
||||
" http_request.set_meta(\"callback\", callback)",
|
||||
"",
|
||||
"func _on_request_completed(result: int, response_code: int, headers: PoolStringArray, body: PoolByteArray):",
|
||||
" var callback := http_request.get_meta(\"callback\")",
|
||||
" if callback:",
|
||||
" var response_body = body.get_string_from_utf8()",
|
||||
" var json = JSON.new()",
|
||||
" if json.parse(response_body) == OK:",
|
||||
" callback.call(json.get_data())",
|
||||
" else:",
|
||||
" callback.call(null)",
|
||||
]
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
def generate_tag_client(tag: str, endpoints: list, base_url: str) -> str:
|
||||
"""Generate a GDScript API client for all endpoints with a given tag."""
|
||||
class_name = sanitize_class_name(tag) + "API"
|
||||
|
||||
lines = [
|
||||
f"class_name {class_name}",
|
||||
"extends RefCounted",
|
||||
"",
|
||||
"var http_request: HTTPRequest",
|
||||
"",
|
||||
"func _init(node: Node):",
|
||||
" http_request = HTTPRequest.new()",
|
||||
" node.add_child(http_request)",
|
||||
' http_request.connect("request_completed", self, "_on_request_completed")',
|
||||
"",
|
||||
]
|
||||
|
||||
# Generate a method for each endpoint
|
||||
for path, method, endpoint in endpoints:
|
||||
# Sanitize method name for GDScript
|
||||
method_name = method.lower()
|
||||
# Create a valid function name from the path
|
||||
func_name = "call_" + path.replace("/", "_").replace("{", "").replace("}", "").replace("-", "_")
|
||||
|
||||
# Format URL (replace {param} with %s for Godot's string formatting)
|
||||
url = path.replace("{", "%").replace("}", "s")
|
||||
full_url = f'"{base_url}{url}"'
|
||||
|
||||
lines.extend([
|
||||
f"func {func_name}(params: Dictionary = {{}}, callback: Callable):",
|
||||
f" var url := {full_url}",
|
||||
' var headers = ["User-Agent: MyGodotApp"]',
|
||||
f" var error := http_request.request(url, headers, false, HTTPClient.METHOD_{method.upper()})",
|
||||
" if error != OK:",
|
||||
' push_error("HTTP request failed.")',
|
||||
" return",
|
||||
" http_request.set_meta(\"callback\", callback)",
|
||||
"",
|
||||
])
|
||||
|
||||
# Add the completion handler
|
||||
lines.extend([
|
||||
"func _on_request_completed(result: int, response_code: int, headers: PoolStringArray, body: PoolByteArray):",
|
||||
" var callback := http_request.get_meta(\"callback\")",
|
||||
" if callback:",
|
||||
" var response_body = body.get_string_from_utf8()",
|
||||
" var json = JSON.new()",
|
||||
" if json.parse(response_body) == OK:",
|
||||
" callback.call(json.get_data())",
|
||||
" else:",
|
||||
" callback.call(null)",
|
||||
])
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def generate_code(spec: dict, output_dir: str, base_url: str):
|
||||
"""Generate all GDScript files from the OpenAPI spec."""
|
||||
# Create output directory
|
||||
Path(output_dir).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Generate models
|
||||
schemas = spec.get("definitions", {}) # Swagger 2.0 uses "definitions"
|
||||
if not schemas:
|
||||
schemas = spec.get("components", {}).get("schemas", {})
|
||||
|
||||
for schema_name, schema in schemas.items():
|
||||
class_name = sanitize_class_name(schema_name)
|
||||
code = generate_model_class(class_name, schema)
|
||||
output_path = Path(output_dir) / f"{class_name}.gd"
|
||||
with open(output_path, "w") as f:
|
||||
f.write(code)
|
||||
print(f"Generated model: {output_path}")
|
||||
|
||||
# Group endpoints by tag
|
||||
paths = spec.get("paths", {})
|
||||
tag_endpoints = defaultdict(list)
|
||||
|
||||
for path, methods in paths.items():
|
||||
for method, endpoint in methods.items():
|
||||
tags = endpoint.get("tags", ["default"])
|
||||
for tag in tags:
|
||||
tag_endpoints[tag].append((path, method, endpoint))
|
||||
|
||||
# Generate one file per tag
|
||||
for tag, endpoints in tag_endpoints.items():
|
||||
code = generate_tag_client(tag, endpoints, base_url)
|
||||
class_name = sanitize_class_name(tag) + "API"
|
||||
output_path = Path(output_dir) / f"{class_name}.gd"
|
||||
with open(output_path, "w") as f:
|
||||
f.write(code)
|
||||
print(f"Generated API client for tag '{tag}': {output_path}")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Generate Godot API clients from OpenAPI spec")
|
||||
parser.add_argument("input", help="Path to the OpenAPI JSON/YAML file")
|
||||
parser.add_argument("-o", "--output", default="godot_generated", help="Output directory for GDScript files")
|
||||
parser.add_argument("-b", "--base-url", default="https://api.example.com", help="Base URL for API requests")
|
||||
args = parser.parse_args()
|
||||
|
||||
spec = load_openapi_spec(args.input)
|
||||
generate_code(spec, args.output, args.base_url)
|
||||
print("Done!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user