Files
smart-support/backend/app/openapi/validator.py
Yaojia Wang a54eb224e0 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
2026-03-31 00:10:44 +02:00

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