"""Shared legacy REST compat metering (routes and query params).""" from __future__ import annotations import logging from fastapi import Request, Response from sqlalchemy.ext.asyncio import AsyncSession from api.services.legacy_meter import identity_from_request, record_legacy_usage logger = logging.getLogger(__name__) _LEGACY_OWNER = "state-hub.api" # Planning horizon for legacy workstream interfaces; actual removal remains # legacy-meter-gated per docs/workplan-terminology-transition.md. _LEGACY_WORKSTREAM_SUNSET = "Wed, 30 Jun 2027 23:59:59 GMT" def legacy_response_headers(replacement_ref: str) -> dict[str, str]: return { "Deprecation": "true", "Sunset": _LEGACY_WORKSTREAM_SUNSET, "X-StateHub-Replacement": replacement_ref, "Link": f'<{replacement_ref}>; rel="successor-version"', } def mark_legacy_response(response: Response | None, replacement_ref: str) -> None: if response is None: return for key, value in legacy_response_headers(replacement_ref).items(): if key == "Link": response.headers.append(key, value) else: response.headers[key] = value def legacy_query_param_key(method: str, route: str, param: str = "workstream_id") -> str: return f"rest_api:{method} {route}?{param}" async def meter_legacy_query_param( *, session: AsyncSession, request: Request | None, response: Response | None, method: str, route: str, replacement_ref: str, param: str = "workstream_id", ) -> None: interface_key = legacy_query_param_key(method, route, param) mark_legacy_response(response, replacement_ref) try: await record_legacy_usage( session, interface_key=interface_key, interface_kind="rest_api", replacement_ref=replacement_ref, owner_component=_LEGACY_OWNER, replacement_verified=True, identity=identity_from_request(request), ) except Exception: await session.rollback() logger.warning("legacy-meter failed to record %s", interface_key, exc_info=True) def legacy_body_field_key(method: str, route: str, field: str = "workstream_id") -> str: return f"rest_api:{method} {route} {field}" async def meter_legacy_body_field( *, session: AsyncSession, request: Request | None, response: Response | None, method: str, route: str, replacement_ref: str, field: str = "workstream_id", ) -> None: interface_key = legacy_body_field_key(method, route, field) mark_legacy_response(response, replacement_ref) try: await record_legacy_usage( session, interface_key=interface_key, interface_kind="rest_api", replacement_ref=replacement_ref, owner_component=_LEGACY_OWNER, replacement_verified=True, identity=identity_from_request(request), ) except Exception: await session.rollback() logger.warning("legacy-meter failed to record %s", interface_key, exc_info=True) async def meter_legacy_body_from_model( body: object, *, session: AsyncSession, request: Request | None, response: Response | None, method: str, route: str, replacement_ref: str, field: str = "workstream_id", ) -> None: if not getattr(body, "used_legacy_workstream_id", False): return await meter_legacy_body_field( session=session, request=request, response=response, method=method, route=route, replacement_ref=replacement_ref, field=field, )