"""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