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 } } } """