feat: complete phase 3 -- OpenAPI auto-discovery, SSRF protection, tool generation
- 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
This commit is contained in:
51
backend/app/openapi/validator.py
Normal file
51
backend/app/openapi/validator.py
Normal file
@@ -0,0 +1,51 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user