feat(api): attach request meta to error envelopes

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-01 17:43:37 +00:00
parent c48effb6b7
commit 1ec5bf4062
2 changed files with 52 additions and 3 deletions

View file

@ -73,6 +73,17 @@ func (g autoResponseMetaTestGroup) RegisterRoutes(rg *gin.RouterGroup) {
})
}
type autoErrorResponseMetaTestGroup struct{}
func (g autoErrorResponseMetaTestGroup) Name() string { return "auto-error-response-meta" }
func (g autoErrorResponseMetaTestGroup) BasePath() string { return "/v1" }
func (g autoErrorResponseMetaTestGroup) RegisterRoutes(rg *gin.RouterGroup) {
rg.GET("/error", func(c *gin.Context) {
time.Sleep(2 * time.Millisecond)
c.JSON(http.StatusBadRequest, api.Fail("bad_request", "request failed"))
})
}
// ── Bearer auth ─────────────────────────────────────────────────────────
func TestBearerAuth_Bad_MissingToken(t *testing.T) {
@ -310,6 +321,42 @@ func TestResponseMeta_Good_AttachesMetaAutomatically(t *testing.T) {
}
}
func TestResponseMeta_Good_AttachesMetaToErrorResponses(t *testing.T) {
gin.SetMode(gin.TestMode)
e, _ := api.New(
api.WithRequestID(),
api.WithResponseMeta(),
)
e.Register(autoErrorResponseMetaTestGroup{})
h := e.Handler()
w := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodGet, "/v1/error", nil)
req.Header.Set("X-Request-ID", "client-id-auto-error-meta")
h.ServeHTTP(w, req)
if w.Code != http.StatusBadRequest {
t.Fatalf("expected 400, got %d", w.Code)
}
var resp api.Response[string]
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
t.Fatalf("unmarshal error: %v", err)
}
if resp.Meta == nil {
t.Fatal("expected Meta to be present")
}
if resp.Meta.RequestID != "client-id-auto-error-meta" {
t.Fatalf("expected request_id=%q, got %q", "client-id-auto-error-meta", resp.Meta.RequestID)
}
if resp.Meta.Duration == "" {
t.Fatal("expected duration to be populated")
}
if resp.Error == nil || resp.Error.Code != "bad_request" {
t.Fatalf("expected bad_request error, got %+v", resp.Error)
}
}
// ── CORS ────────────────────────────────────────────────────────────────
func TestCORS_Good_PreflightAllOrigins(t *testing.T) {

View file

@ -104,8 +104,8 @@ func (w *responseMetaRecorder) commit() {
_, _ = w.ResponseWriter.Write(w.body.Bytes())
}
// responseMetaMiddleware injects request metadata into successful JSON
// envelope responses before they are written to the client.
// responseMetaMiddleware injects request metadata into JSON envelope
// responses before they are written to the client.
func responseMetaMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
if _, ok := c.Get(requestStartContextKey); !ok {
@ -156,7 +156,9 @@ func refreshResponseMetaBody(body []byte, meta *Meta) []byte {
}
if _, ok := obj["success"]; !ok {
return body
if _, ok := obj["error"]; !ok {
return body
}
}
current := map[string]any{}