This commit is contained in:
2026-09-16 11:42:06 +05:30
parent bf5a9026fe
commit 25bc33975c
7 changed files with 482 additions and 3 deletions

View File

@@ -70,10 +70,27 @@ func Idempotency() fiber.Handler {
return err
}
// Cache only deterministic outcomes (2xx/4xx). A 5xx is transient — the
// retry should get a genuine second attempt, not a cached failure.
// Cache SUCCESS only.
//
// This used to store any status below 500, on the reasoning that a 4xx
// is deterministic. A 4xx is not deterministic — it is a refusal made
// against state that moves. POST /customer/auth/otp/verify returns 401
// when the submitted code does not match the one in Redis, and the whole
// point of that screen is that the customer then gets the code right.
// With the refusal cached for 24 hours, the retry that should have
// worked replayed the old 401 instead — confirmed live against
// api.doormile.com, where the second attempt came back carrying
// Idempotent-Replay: true. One typo locked a customer out for a day.
// The same shape applies to 403 after a permission is granted, 404 after
// a record is created, and 429 after a window rolls over.
//
// Nothing is lost by narrowing it. This middleware exists to stop a retry
// repeating a SIDE EFFECT — a second pickup, a second COD collection, a
// second session. A request that ended 4xx performed no side effect, so
// re-executing it is exactly as safe as the first attempt was, and
// strictly more correct than replaying a stale no.
status := c.Response().StatusCode()
if status < 500 {
if isCacheableStatus(status) {
body := string(c.Response().Body())
db.Rdb.Set(context.Background(), base, strconv.Itoa(status)+sep+body, ttl)
}
@@ -82,6 +99,12 @@ func Idempotency() fiber.Handler {
}
}
// isCacheableStatus reports whether a response may be stored and replayed to
// a later request carrying the same key. Only a 2xx may — see above.
func isCacheableStatus(status int) bool {
return status >= 200 && status < 300
}
// idempotencyScope namespaces a key so one caller's stored response can never
// be replayed to another.
//