159 lines
4.8 KiB
Python
159 lines
4.8 KiB
Python
from typing import Dict, Any, Type, get_type_hints, get_args, get_origin
|
|
from pydantic import BaseModel, Field, EmailStr
|
|
from enum import Enum
|
|
import inspect
|
|
from fastapi import APIRouter
|
|
from datetime import datetime
|
|
|
|
class SchemaConverter:
|
|
"""Converts Pydantic models to Zod schema definitions"""
|
|
|
|
TYPE_MAPPINGS = {
|
|
str: "string",
|
|
int: "number",
|
|
float: "number",
|
|
bool: "boolean",
|
|
list: "array",
|
|
dict: "object",
|
|
datetime: "date",
|
|
EmailStr: "string.email()",
|
|
}
|
|
|
|
def __init__(self):
|
|
self.processed_models = set()
|
|
|
|
def convert_model(self, model: Type[BaseModel]) -> Dict[str, Any]:
|
|
"""Convert a Pydantic model to a Zod schema definition"""
|
|
if model.__name__ in self.processed_models:
|
|
return {"$ref": model.__name__}
|
|
|
|
self.processed_models.add(model.__name__)
|
|
|
|
schema = {
|
|
"name": model.__name__,
|
|
"type": "object",
|
|
"fields": {},
|
|
"validations": {}
|
|
}
|
|
|
|
for field_name, field in model.__fields__.items():
|
|
field_info = self._convert_field(field)
|
|
schema["fields"][field_name] = field_info
|
|
|
|
# Get validations from field
|
|
validations = self._get_field_validations(field)
|
|
if validations:
|
|
schema["validations"][field_name] = validations
|
|
|
|
return schema
|
|
|
|
def _convert_field(self, field) -> Dict[str, Any]:
|
|
"""Convert a Pydantic field to Zod field definition"""
|
|
field_type = field.outer_type_
|
|
origin = get_origin(field_type)
|
|
|
|
if origin is not None:
|
|
# Handle generic types (List, Dict, etc)
|
|
args = get_args(field_type)
|
|
if origin == list:
|
|
return {
|
|
"type": "array",
|
|
"items": self._get_type_name(args[0])
|
|
}
|
|
elif origin == dict:
|
|
return {
|
|
"type": "object",
|
|
"additionalProperties": self._get_type_name(args[1])
|
|
}
|
|
|
|
if inspect.isclass(field_type) and issubclass(field_type, BaseModel):
|
|
# Nested model
|
|
return self.convert_model(field_type)
|
|
|
|
if inspect.isclass(field_type) and issubclass(field_type, Enum):
|
|
# Enum type
|
|
return {
|
|
"type": "enum",
|
|
"values": [e.value for e in field_type]
|
|
}
|
|
|
|
return {
|
|
"type": self._get_type_name(field_type)
|
|
}
|
|
|
|
def _get_field_validations(self, field) -> Dict[str, Any]:
|
|
"""Extract validations from field"""
|
|
validations = {}
|
|
|
|
if field.field_info.min_length is not None:
|
|
validations["min_length"] = field.field_info.min_length
|
|
if field.field_info.max_length is not None:
|
|
validations["max_length"] = field.field_info.max_length
|
|
if field.field_info.regex is not None:
|
|
validations["pattern"] = field.field_info.regex.pattern
|
|
if field.field_info.gt is not None:
|
|
validations["gt"] = field.field_info.gt
|
|
if field.field_info.lt is not None:
|
|
validations["lt"] = field.field_info.lt
|
|
|
|
return validations
|
|
|
|
def _get_type_name(self, type_: Type) -> str:
|
|
"""Get Zod type name for Python type"""
|
|
return self.TYPE_MAPPINGS.get(type_, "any")
|
|
|
|
# FastAPI router
|
|
router = APIRouter(prefix="/api/validation", tags=["Validation"])
|
|
converter = SchemaConverter()
|
|
|
|
@router.get("/schema/{model_name}")
|
|
async def get_schema(model_name: str) -> Dict[str, Any]:
|
|
"""Get Zod schema for a specific model"""
|
|
# This is just an example - you'd need to implement model lookup
|
|
models = {
|
|
"User": UserModel,
|
|
"Product": ProductModel,
|
|
# Add your models here
|
|
}
|
|
|
|
if model_name not in models:
|
|
raise ValueError(f"Model {model_name} not found")
|
|
|
|
return converter.convert_model(models[model_name])
|
|
|
|
# Example usage:
|
|
"""
|
|
class UserModel(BaseModel):
|
|
email: EmailStr
|
|
username: str = Field(min_length=3, max_length=50)
|
|
age: int = Field(gt=0, lt=150)
|
|
is_active: bool = True
|
|
roles: List[str] = []
|
|
|
|
# GET /api/validation/schema/User would return:
|
|
{
|
|
"name": "User",
|
|
"type": "object",
|
|
"fields": {
|
|
"email": {"type": "string.email()"},
|
|
"username": {"type": "string"},
|
|
"age": {"type": "number"},
|
|
"is_active": {"type": "boolean"},
|
|
"roles": {
|
|
"type": "array",
|
|
"items": "string"
|
|
}
|
|
},
|
|
"validations": {
|
|
"username": {
|
|
"min_length": 3,
|
|
"max_length": 50
|
|
},
|
|
"age": {
|
|
"gt": 0,
|
|
"lt": 150
|
|
}
|
|
}
|
|
}
|
|
"""
|