Skip to content

Commit 2b3534c

Browse files
feat(core): add WebSocket response factory and DI dependency
- add WSResponseFactory for standardized WebSocket success and error payloads - WSResponseFactory generates responses consistent with HTTP response structure, including metadata - map WebSocketException into structured ErrorContent - add get_ws_response_factory provider and WSResponseFactoryDep dependency alias for DI
1 parent 74867a3 commit 2b3534c

2 files changed

Lines changed: 48 additions & 2 deletions

File tree

app/core/dependencies.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22

33
from fastapi import Depends
44

5-
from .response import ResponseFactory, get_response_factory
5+
from .response import (
6+
ResponseFactory,
7+
WSResponseFactory,
8+
get_response_factory,
9+
get_ws_response_factory,
10+
)
611
from .security import JWTService, PasswordSecurity
712

813

@@ -17,3 +22,5 @@ def get_password_security() -> PasswordSecurity:
1722
ResponseFactoryDep = Annotated[ResponseFactory, Depends(get_response_factory)]
1823
JWTServiceDep = Annotated[JWTService, Depends(get_jwt_service)]
1924
PasswordSecurityDep = Annotated[PasswordSecurity, Depends(get_password_security)]
25+
26+
WSResponseFactoryDep = Annotated[WSResponseFactory, Depends(get_ws_response_factory)]

app/core/response.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from collections.abc import Mapping
22
from typing import Any
33

4-
from fastapi import HTTPException, Request, status
4+
from fastapi import HTTPException, Request, WebSocket, WebSocketException, status
55
from fastapi.responses import JSONResponse
66

77
from app.schemas.response import ErrorContent, Meta, SuccessContent
@@ -61,3 +61,42 @@ def error(self, exc: HTTPException) -> JSONResponse:
6161

6262
def get_response_factory(request: Request) -> ResponseFactory:
6363
return ResponseFactory(request)
64+
65+
66+
class WSResponseFactory:
67+
def __init__(self, request_id: str | None = None, instance: str | None = None) -> None:
68+
self.request_id = request_id
69+
self.instance = instance
70+
71+
def success(self, data: Any, meta_extensions: dict[str, Any] | None = None) -> dict[str, Any]:
72+
meta: dict[str, Any] = {"request_id": self.request_id}
73+
if meta_extensions:
74+
meta.update(meta_extensions)
75+
76+
content = SuccessContent(data=data, meta=Meta(**meta))
77+
return content.model_dump(mode="json", exclude_none=True)
78+
79+
def error(self, exc: WebSocketException) -> dict[str, Any]:
80+
type_url = getattr(exc, "type", "https://websocket.org/reference/close-codes/")
81+
meta_extensions = getattr(exc, "meta_extensions", None)
82+
83+
meta: dict[str, Any] = {"request_id": self.request_id}
84+
if meta_extensions:
85+
meta.update(meta_extensions)
86+
87+
meta["success"] = False
88+
89+
content = ErrorContent(
90+
type=type_url,
91+
title="Websocket Error",
92+
status=exc.code or 1011,
93+
detail=getattr(exc, "reason", None) or "Websocket connection closed",
94+
instance=str(self.instance) or None,
95+
errors=getattr(exc, "errors", None),
96+
meta=Meta(**meta),
97+
)
98+
return content.model_dump(exclude_none=True)
99+
100+
101+
def get_ws_response_factory(ws: WebSocket) -> WSResponseFactory:
102+
return WSResponseFactory(getattr(ws.state, "request_id", None), ws.url.path or None)

0 commit comments

Comments
 (0)