Add swagger-drogon generator

This commit is contained in:
gabdevele
2026-06-03 01:52:43 +02:00 Unverified
parent 1043768da7
commit 21883a4a8f
19 changed files with 1672 additions and 203 deletions
@@ -0,0 +1,77 @@
from __future__ import annotations
import re
from pathlib import Path
from typing import Any
from .infer import infer_operation, load_sources
from .models import Route
from .openapi import build_operation, build_path, merge_components
from .parser import parse_routes
from .validate import validate_spec
def generate(
controllers_dir: Path,
source_dirs: list[Path],
contract: dict[str, Any],
title: str,
version: str,
server: str,
) -> dict[str, Any]:
sources = load_sources(source_dirs)
spec: dict[str, Any] = {
"openapi": "3.0.3",
"info": {
"title": title,
"version": version,
"description": contract.get(
"description",
"Generated from Drogon route macros, C++ metadata and schemas.",
),
},
"servers": [{"url": server}],
"components": merge_components(contract),
"paths": {},
}
operations_contract = contract.get("operations", {})
if not isinstance(operations_contract, dict):
raise ValueError("contract operations must be an object")
for route in parse_routes(controllers_dir):
inferred = infer_operation(route, sources)
path, path_params = build_path(route.raw_path, _handler_path_param_names(route))
inferred.path_parameters = path_params
operation_contract = operations_contract.get(route.operation_id, {})
if not isinstance(operation_contract, dict):
raise ValueError(f"{route.operation_id}: operation contract must be an object")
operation = build_operation(route, inferred, operation_contract)
spec["paths"].setdefault(path, {})[route.http_method] = operation
validate_spec(spec)
return spec
def _handler_path_param_names(route: Route) -> list[str]:
text = route.header.read_text(encoding="utf-8")
match = re.search(
rf"void\s+{route.handler}\s*\((.*?)\)\s*;",
text,
re.MULTILINE | re.DOTALL,
)
if not match:
return []
params = match.group(1)
names: list[str] = []
for type_name, name in re.findall(r"\b(long long|int|size_t)\s+([A-Za-z_]\w*)", params):
del type_name
if name not in {"req"}:
names.append(_openapi_param_name(name))
return names
def _openapi_param_name(name: str) -> str:
if name.endswith("Id") and len(name) > 2:
return "id" if name == "reportId" else name[:-2] + "_id"
return name