Add swagger-drogon generator
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user