Intercept HTTP traffic with a custom route handler that preserves response classes, injects Vary headers on context-pruned payloads, and exposes dynamic JSON-Schema introspection.
The Challenge
Standard FastAPI routes are stateless with respect to security context. As a result, developers often encounter:
- Response Class Overwriting: Middleware or custom response classes are silently replaced by
JSONResponse, breaking specialized serialization logic. - Cache Invisibility: When the response payload is dynamically pruned based on user permissions, intermediate CDNs or proxies may cache a pruned response and serve it to a privileged user, leaking restricted data.
- Schema Blindness: Frontend teams have no standardized way to introspect the current schema—especially which fields are restricted for the active user—without manual API documentation or hard-coded logic.
The ZCore Elegance
ZCoreAPIRoute is a drop-in replacement for FastAPI's APIRoute. It intercepts every request, conditionally generates context-aware JSON-Schema responses when ?schema=true is appended, and appends Vary: Authorization, Cookie headers to any response that has been security-pruned.
from zcore.web.api_router import ZCoreAPIRoute
from fastapi import APIRouter
router = APIRouter(route_class=ZCoreAPIRoute)
@router.post("/products", openapi_extra={"expose_schema": True})
async def create(data: ProductCreate, service: ProductService = Inject(ProductService)):
return await service.create(data)
# GET /products?schema=true → Returns JSON-Schema with restricted fields pruned
# POST /products → Response includes Vary: Authorization, Cookie if pruning occurred
from fastapi import APIRouter
router = APIRouter()
@router.post("/products")
async def create(data: ProductCreate, service: ProductService = Depends(get_service)):
# No schema introspection
# No Vary header management
# Plain JSONResponse always used
return await service.create(data)
Execution Flow
Boundaries & Integration
ZCoreAPIRoute works transparently with the rest of the ZCore ecosystem.
- BaseRouter Compatible: The
BaseRouterclass inweb/base_router.pyusesZCoreAPIRouteas its defaultroute_class. Any scaffolded endpoint automatically inherits schema exposure and Vary header management. - Response Class Preservation: When initializing,
ZCoreAPIRouteinspects the currentresponse_class. If it is the defaultJSONResponse, it swaps inZCoreJSONResponse(which uses ZCore's unified JSON encoder). Custom response classes are left untouched. - Manual Endpoints: Functional endpoints registered on a standard
APIRouterare unaffected. Only routes usingZCoreAPIRoute(or aBaseRoutersubclass) participate in the security-aware lifecycle.
Under-the-Hood Spec
1. Response Class Preservation Logic
Inside ZCoreAPIRoute.__init__, the constructor checks the inherited response_class [web/api_router.py]. If the class resolves to the standard FastAPI JSONResponse, it is replaced with ZCoreJSONResponse. Any custom subclass (e.g., ORJSONResponse) is preserved, ensuring third-party serializers are never silently overridden.
2. Vary Header Append on Context-Pruned Responses
After the route handler completes, custom_route_handler inspects get_restricted_fields() [web/api_router.py]. If the set is non-empty and the response is JSON, the method appends Authorization and Cookie to the existing Vary header. Duplicates are eliminated by lower-casing existing values, preventing header bloat on repeated requests.
3. Schema Extraction via Dependency Introspection
find_input_schema iterates over dependant.body_params to locate the body Pydantic model [web/api_router.py]. find_output_schema recursively unpacks generic origin types (e.g., ResponseWrapper[ProductOut]) to extract the inner model. This allows ZCoreAPIRoute to resolve the effective schema even when wrapped in generic containers.
4. Request Body Caching
ZCoreRequest overrides body() to cache the byte stream on first read [web/api_router.py]. This prevents downstream middlewares or validators from consuming the stream and leaving subsequent readers with an empty payload.
Schema Exposure Restriction
Schema introspection (?schema=true) is only enabled on routes with openapi_extra={"expose_schema": True}. This prevents accidental leakage of internal model structures on endpoints that do not opt in.