This commit is contained in:
95
src/docgen/generators/openapi.py
Normal file
95
src/docgen/generators/openapi.py
Normal file
@@ -0,0 +1,95 @@
|
||||
"""OpenAPI documentation generator."""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from docgen.models import DocConfig, Endpoint, HTTPMethod
|
||||
from docgen.generators import BaseGenerator
|
||||
|
||||
|
||||
class OpenAPIGenerator(BaseGenerator):
|
||||
"""Generator for OpenAPI 3.0 specification."""
|
||||
|
||||
def generate(self, endpoints: list[Endpoint], output_dir: Path) -> Path:
|
||||
"""Generate OpenAPI specification."""
|
||||
output_dir = self._ensure_output_dir(output_dir)
|
||||
|
||||
spec = self._build_openapi_spec(endpoints)
|
||||
spec_path = output_dir / "openapi.json"
|
||||
spec_path.write_text(json.dumps(spec, indent=2))
|
||||
|
||||
return spec_path
|
||||
|
||||
def _build_openapi_spec(self, endpoints: list[Endpoint]) -> dict[str, Any]:
|
||||
"""Build the OpenAPI specification dictionary."""
|
||||
spec = {
|
||||
"openapi": "3.0.3",
|
||||
"info": {
|
||||
"title": self.config.title,
|
||||
"description": self.config.description,
|
||||
"version": self.config.version,
|
||||
},
|
||||
"paths": {},
|
||||
"servers": [{"url": "/"}],
|
||||
}
|
||||
|
||||
for endpoint in endpoints:
|
||||
path_item = self._endpoint_to_path_item(endpoint)
|
||||
|
||||
if endpoint.path not in spec["paths"]:
|
||||
spec["paths"][endpoint.path] = path_item
|
||||
else:
|
||||
existing = spec["paths"][endpoint.path]
|
||||
for method in ["get", "post", "put", "patch", "delete", "options", "head"]:
|
||||
if method in path_item:
|
||||
existing[method] = path_item[method]
|
||||
|
||||
return spec
|
||||
|
||||
def _endpoint_to_path_item(self, endpoint: Endpoint) -> dict[str, Any]:
|
||||
"""Convert an Endpoint to OpenAPI path item."""
|
||||
method = endpoint.method.value.lower()
|
||||
|
||||
operation = {
|
||||
"summary": endpoint.summary or f"{endpoint.method.value} {endpoint.path}",
|
||||
"description": endpoint.description,
|
||||
"operationId": endpoint.operation_id or f"{method}_{endpoint.path.replace('/', '_').strip('_')}",
|
||||
"deprecated": endpoint.deprecated,
|
||||
"parameters": [self._param_to_openapi(p) for p in endpoint.parameters],
|
||||
"responses": self._build_responses(endpoint.responses),
|
||||
}
|
||||
|
||||
if endpoint.security:
|
||||
operation["security"] = [{"bearerAuth": []}]
|
||||
|
||||
return {method: operation}
|
||||
|
||||
def _param_to_openapi(self, param) -> dict[str, Any]:
|
||||
"""Convert Parameter to OpenAPI parameter."""
|
||||
return {
|
||||
"name": param.name,
|
||||
"in": param.location.value,
|
||||
"description": param.description,
|
||||
"required": param.required,
|
||||
"schema": {"type": param.type, "default": param.default},
|
||||
"example": param.example,
|
||||
}
|
||||
|
||||
def _build_responses(self, responses) -> dict[str, Any]:
|
||||
"""Build OpenAPI responses object."""
|
||||
if not responses:
|
||||
return {
|
||||
"200": {"description": "Successful response"},
|
||||
"default": {"description": "Error response"},
|
||||
}
|
||||
|
||||
result = {}
|
||||
for resp in responses:
|
||||
resp_obj = {"description": resp.description}
|
||||
if resp.example:
|
||||
resp_obj["content"] = {
|
||||
resp.content_type: {"schema": resp.example}
|
||||
}
|
||||
result[str(resp.status_code)] = resp_obj
|
||||
|
||||
return result
|
||||
Reference in New Issue
Block a user