103 lines
2.9 KiB
Python
103 lines
2.9 KiB
Python
"""
|
|
OpenAPI specification loading and validation utilities.
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
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, or 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
|