- SSRF protection: private IP blocking, DNS rebinding defense, redirect validation - OpenAPI fetcher with SSRF guard, JSON/YAML auto-detection, 10MB limit - Structural spec validator (3.0.x/3.1.x) - Endpoint parser with $ref resolution, auto-generated operation IDs - Heuristic + LLM endpoint classifier with Protocol interface - Review API at /api/openapi (import, job status, classification CRUD, approve) - @tool code generator + Agent YAML generator - Import orchestrator (fetch -> validate -> parse -> classify pipeline) - 125 new tests, 322 total passing, 93.23% coverage
52 lines
1.8 KiB
Python
52 lines
1.8 KiB
Python
"""OpenAPI spec validator.
|
|
|
|
Validates an OpenAPI spec dict for required fields and basic structural
|
|
correctness. Returns a list of human-readable error strings.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
_SUPPORTED_VERSIONS = ("3.0.", "3.1.")
|
|
_REQUIRED_FIELDS = ("openapi", "info", "paths")
|
|
|
|
|
|
def validate_spec(spec_dict: object) -> list[str]:
|
|
"""Validate an OpenAPI spec dict.
|
|
|
|
Returns a list of error strings. An empty list means the spec is valid.
|
|
Does not raise; all errors are captured and returned.
|
|
"""
|
|
if not isinstance(spec_dict, dict):
|
|
return [f"Spec must be a dict, got {type(spec_dict).__name__}"]
|
|
|
|
errors: list[str] = []
|
|
|
|
# Check required top-level fields
|
|
for field in _REQUIRED_FIELDS:
|
|
if field not in spec_dict:
|
|
errors.append(f"Missing required field: '{field}'")
|
|
|
|
# Validate openapi version if present
|
|
if "openapi" in spec_dict:
|
|
version = spec_dict["openapi"]
|
|
if not isinstance(version, str):
|
|
errors.append(f"'openapi' must be a string, got {type(version).__name__}")
|
|
elif not any(version.startswith(prefix) for prefix in _SUPPORTED_VERSIONS):
|
|
errors.append(
|
|
f"Unsupported OpenAPI version '{version}'. "
|
|
f"Supported versions start with: {', '.join(_SUPPORTED_VERSIONS)}"
|
|
)
|
|
|
|
# Validate info object if present
|
|
if "info" in spec_dict and isinstance(spec_dict["info"], dict):
|
|
info = spec_dict["info"]
|
|
for sub_field in ("title", "version"):
|
|
if sub_field not in info:
|
|
errors.append(f"Missing required field in 'info': '{sub_field}'")
|
|
|
|
# Validate paths object if present
|
|
if "paths" in spec_dict and not isinstance(spec_dict["paths"], dict):
|
|
errors.append(f"'paths' must be a dict, got {type(spec_dict['paths']).__name__}")
|
|
|
|
return errors
|