"""Shared route utilities: symbol validation and error handling decorator.""" import functools import logging from collections.abc import Awaitable, Callable from typing import ParamSpec, TypeVar from fastapi import HTTPException from models import SYMBOL_PATTERN logger = logging.getLogger(__name__) P = ParamSpec("P") R = TypeVar("R") def validate_symbol(symbol: str) -> str: """Validate and normalize a stock symbol.""" if not SYMBOL_PATTERN.match(symbol): raise HTTPException(status_code=400, detail="Invalid symbol format") return symbol.upper() def safe(fn: Callable[P, Awaitable[R]]) -> Callable[P, Awaitable[R]]: """Decorator to catch upstream errors and return 502.""" @functools.wraps(fn) async def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: try: return await fn(*args, **kwargs) except HTTPException: raise except Exception: logger.exception("Upstream data error") raise HTTPException( status_code=502, detail="Data provider error. Check server logs.", ) return wrapper # type: ignore[return-value]