feat: add project-owned error catalogs and HTTP 200 failure contracts

This commit is contained in:
2026-09-24 12:40:05 +08:00
parent bb2caefbb4
commit ebfd279f9a
42 changed files with 1897 additions and 120 deletions

View File

@@ -14,9 +14,10 @@ import (
)
type StoredResponse struct {
Status int `json:"status"`
Headers http.Header `json:"headers"`
Body []byte `json:"body"`
Status int `json:"status"`
ErrorCode string `json:"errorCode,omitempty"`
Headers http.Header `json:"headers"`
Body []byte `json:"body"`
}
type IdempotencyRecord struct {
Fingerprint string `json:"fingerprint"`
@@ -54,6 +55,9 @@ func (i *Idempotency) shared(c *httpx.Context, next httpx.Handler, id, fingerpri
response := record.Response
headers := response.Headers.Clone()
headers.Set("Idempotency-Replayed", "true")
if response.ErrorCode != "" {
c.RecordError(apierrors.New(response.Status, response.ErrorCode, ""))
}
return c.WriteResponse(response.Status, headers, response.Body)
}
completed := false
@@ -84,9 +88,15 @@ func (i *Idempotency) shared(c *httpx.Context, next httpx.Handler, id, fingerpri
if buffer.status == 0 {
buffer.status = 200
}
if failure := child.Failure(); failure != nil {
c.RecordError(failure)
}
var response *StoredResponse
if buffer.status < 500 && buffer.headers.Get("Set-Cookie") == "" && !strings.Contains(strings.ToLower(buffer.headers.Get("Cache-Control")), "no-store") {
response = &StoredResponse{Status: buffer.status, Headers: buffer.headers.Clone(), Body: append([]byte(nil), buffer.body.Bytes()...)}
if failure := child.Failure(); failure != nil {
response.ErrorCode = failure.Code
}
}
if err = finish(response); err != nil {
return apierrors.New(503, "IDEMPOTENCY_UNAVAILABLE", "outcome requires reconciliation").WithCause(err)