110 lines
3.0 KiB
Python
110 lines
3.0 KiB
Python
"""
|
|
# Summary
|
|
|
|
OpenAPI specification loading and validation utilities.
|
|
|
|
This module is responsible for loading an OpenAPI 3.x specification
|
|
from disk and validating it before it is used by the application.
|
|
|
|
It enforces the principle that an invalid or malformed OpenAPI document
|
|
must never reach the routing or runtime layers.
|
|
|
|
Notes:
|
|
**Design Principles:**
|
|
|
|
- OpenAPI is treated as an authoritative contract.
|
|
- Invalid specifications fail fast at application startup.
|
|
- Supported formats are JSON and YAML.
|
|
- Validation errors are surfaced clearly and early.
|
|
|
|
**Constraints:**
|
|
|
|
- This module intentionally does NOT:
|
|
- Modify the OpenAPI document.
|
|
- Infer missing fields.
|
|
- Generate models or code.
|
|
- Perform request/response validation at runtime.
|
|
"""
|
|
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import yaml
|
|
from openapi_spec_validator import validate_spec
|
|
|
|
from .errors import OpenAPIFirstError
|
|
|
|
|
|
class OpenAPISpecLoadError(OpenAPIFirstError):
|
|
"""
|
|
Raised when an OpenAPI specification cannot be loaded or validated.
|
|
|
|
Notes:
|
|
**Guarantees:**
|
|
|
|
- This error indicates that the OpenAPI document is unreadable,
|
|
malformed, or violates the OpenAPI 3.x specification.
|
|
"""
|
|
pass
|
|
|
|
|
|
def load_openapi(path: str | Path) -> dict[str, Any]:
|
|
"""
|
|
Load and validate an OpenAPI 3.x specification from disk.
|
|
|
|
Args:
|
|
path (str | Path):
|
|
Filesystem path to an OpenAPI specification file. Supported
|
|
extensions: `.json`, `.yaml`, `.yml`.
|
|
|
|
Returns:
|
|
dict[str, Any]:
|
|
Parsed and validated OpenAPI specification.
|
|
|
|
Raises:
|
|
OpenAPISpecLoadError:
|
|
If the file does not exist, cannot be parsed, or fails OpenAPI
|
|
schema validation.
|
|
|
|
Notes:
|
|
**Guarantees:**
|
|
|
|
- The specification is parsed based on file extension and validated
|
|
using a strict OpenAPI schema validator.
|
|
- Any error results in an immediate exception, preventing
|
|
application startup.
|
|
"""
|
|
spec_path = Path(path)
|
|
|
|
if not spec_path.exists():
|
|
raise OpenAPISpecLoadError(f"OpenAPI spec not found: {spec_path}")
|
|
|
|
try:
|
|
if spec_path.suffix == ".json":
|
|
with spec_path.open("r", encoding="utf-8") as f:
|
|
spec = json.load(f)
|
|
|
|
elif spec_path.suffix in {".yaml", ".yml"}:
|
|
with spec_path.open("r", encoding="utf-8") as f:
|
|
spec = yaml.safe_load(f)
|
|
|
|
else:
|
|
raise OpenAPISpecLoadError(
|
|
f"Unsupported OpenAPI file format: {spec_path.suffix}"
|
|
)
|
|
|
|
except Exception as exc:
|
|
raise OpenAPISpecLoadError(
|
|
f"Failed to parse OpenAPI spec: {spec_path}"
|
|
) from exc
|
|
|
|
try:
|
|
validate_spec(spec)
|
|
except Exception as exc:
|
|
raise OpenAPISpecLoadError(
|
|
f"OpenAPI spec validation failed: {spec_path}"
|
|
) from exc
|
|
|
|
return spec
|