From 53acde1efd236e54b629f2122b7dec6469945508 Mon Sep 17 00:00:00 2001 From: shaw Date: Mon, 25 May 2026 18:16:46 +0800 Subject: [PATCH] style: fix lint errors in response.failed SSE writer Errcheck flagged three unchecked strings.Builder.WriteString calls and gofmt rejected over-aligned trailing comment in the route table. Rewrite writeResponsesFailedSSE with json.Marshal on typed structs instead of Builder+strconv.Quote. Same wire format, but: - no unchecked Write returns to silence - strict JSON escaping (strconv.Quote emits \a and \v which are not valid JSON; Marshal handles all runes correctly) - omitempty model field via struct tag instead of conditional Builder - consistent with the json.Marshal style used elsewhere in handler/ Collapse trailing comment whitespace in stream_error_event_test.go to satisfy gofmt. All 30+ subtests in the package still pass. --- .../internal/handler/stream_error_event.go | 62 +++++++++++++------ .../handler/stream_error_event_test.go | 2 +- 2 files changed, 44 insertions(+), 20 deletions(-) diff --git a/backend/internal/handler/stream_error_event.go b/backend/internal/handler/stream_error_event.go index fc5bf61d..f3a33a8c 100644 --- a/backend/internal/handler/stream_error_event.go +++ b/backend/internal/handler/stream_error_event.go @@ -1,9 +1,9 @@ package handler import ( + "encoding/json" "fmt" "net/http" - "strconv" "strings" "github.com/Wei-Shaw/sub2api/internal/pkg/ctxkey" @@ -11,6 +11,30 @@ import ( "github.com/google/uuid" ) +// responsesFailedError 对齐 OpenAI Responses 协议 error 子对象。 +type responsesFailedError struct { + Code string `json:"code"` + Message string `json:"message"` +} + +// responsesFailedBody 对齐 apicompat.makeResponsesCompletedEvent 输出的 response 子对象字段集。 +// Output 用空 slice(不是 nil)确保 marshal 为 `[]` 而非 `null`。 +type responsesFailedBody struct { + ID string `json:"id"` + Object string `json:"object"` + Model string `json:"model,omitempty"` + Status string `json:"status"` + Output []any `json:"output"` + Error responsesFailedError `json:"error"` +} + +// responsesFailedEvent 是写入 SSE data 行的顶层结构。 +// 故意不带 sequence_number:spec 标记可选,且本函数被调用时无法可靠拿到 last seq。 +type responsesFailedEvent struct { + Type string `json:"type"` + Response responsesFailedBody `json:"response"` +} + // writeResponsesFailedSSE emits a `response.failed` SSE event in the OpenAI // Responses API protocol after the stream has already started. // @@ -33,27 +57,27 @@ func writeResponsesFailedSSE(c *gin.Context, errType, message string) bool { if !ok { return false } - rid := synthesizeResponseID(c) - model := requestModel(c) - code := mapResponsesErrorCode(errType) - var b strings.Builder - b.Grow(256 + len(message) + len(model)) - b.WriteString(`{"type":"response.failed","response":{`) - b.WriteString(`"id":`) - b.WriteString(strconv.Quote(rid)) - b.WriteString(`,"object":"response"`) - if model != "" { - b.WriteString(`,"model":`) - b.WriteString(strconv.Quote(model)) + payload, err := json.Marshal(responsesFailedEvent{ + Type: "response.failed", + Response: responsesFailedBody{ + ID: synthesizeResponseID(c), + Object: "response", + Model: requestModel(c), + Status: "failed", + Output: []any{}, + Error: responsesFailedError{ + Code: mapResponsesErrorCode(errType), + Message: message, + }, + }, + }) + if err != nil { + _ = c.Error(err) + return true } - b.WriteString(`,"status":"failed","output":[],"error":{"code":`) - b.WriteString(strconv.Quote(code)) - b.WriteString(`,"message":`) - b.WriteString(strconv.Quote(message)) - b.WriteString(`}}}`) - if _, err := fmt.Fprintf(c.Writer, "event: response.failed\ndata: %s\n\n", b.String()); err != nil { + if _, err := fmt.Fprintf(c.Writer, "event: response.failed\ndata: %s\n\n", payload); err != nil { _ = c.Error(err) return true } diff --git a/backend/internal/handler/stream_error_event_test.go b/backend/internal/handler/stream_error_event_test.go index 721b5856..f24cf97f 100644 --- a/backend/internal/handler/stream_error_event_test.go +++ b/backend/internal/handler/stream_error_event_test.go @@ -186,7 +186,7 @@ func TestInboundIsResponses_CoversAllRoutes(t *testing.T) { }{ {"/v1/responses", true}, {"/v1/responses/compact", true}, - {"/responses", true}, // <-- 用户 16 实际走这条 + {"/responses", true}, // <-- 用户 16 实际走这条 {"/responses/compact", true}, {"/backend-api/codex/responses", true}, {"/backend-api/codex/responses/compact", true},