Documentation
¶
Overview ¶
Package idemkit provides tenant-scoped HTTP idempotency middleware.
The middleware suppresses duplicate HTTP responses only after the configured Store confirms persistence. It cannot make a handler's business side effects exactly once by itself. Stronger guarantees require service-owned transactional storage or an outbox that atomically couples the business mutation to the idempotency record.
Index ¶
- Constants
- Variables
- func Fingerprint(method, target string, body []byte) string
- func Middleware(store Store, opts ...Option) func(http.Handler) http.Handler
- type BeginResult
- type Claim
- type LeaseClaim
- type LeaseStore
- type LeaseToken
- type MemoryStore
- func (s *MemoryStore) Begin(ctx context.Context, tenantID, key, fingerprint string) (Claim, error)
- func (s *MemoryStore) BeginLease(ctx context.Context, tenantID, key, fingerprint string) (LeaseClaim, error)
- func (s *MemoryStore) Complete(ctx context.Context, tenantID, key, fingerprint string, ...) error
- func (s *MemoryStore) CompleteLease(ctx context.Context, tenantID, key, fingerprint string, token LeaseToken, ...) error
- func (s *MemoryStore) Release(ctx context.Context, tenantID, key string) error
- func (s *MemoryStore) ReleaseLease(ctx context.Context, tenantID, key string, token LeaseToken) error
- type Option
- type Store
- type StoredResponse
- type TenantResolver
Constants ¶
const ( DefaultHeader = "Idempotency-Key" ReplayHeader = "Idempotency-Replayed" DefaultMemoryTTL = 24 * time.Hour DefaultMemoryCapacity = 10_000 DefaultMaxRequestBytes = int64(1 << 20) DefaultMaxResponseBytes = int64(1 << 20) )
Variables ¶
var ( // ErrLeaseLost means a token-aware operation no longer owns the claim. ErrLeaseLost = stderrors.New("idemkit: lease ownership lost") // ErrCapacity means the store cannot admit another live claim. ErrCapacity = stderrors.New("idemkit: store capacity reached") )
Functions ¶
func Fingerprint ¶
Fingerprint returns a stable hash over method, target, and body bytes.
func Middleware ¶
Middleware returns HTTP middleware that provides idempotency replay for mutating requests carrying an idempotency key. A successful keyed handler response is buffered and becomes visible only after Store persistence.
Types ¶
type BeginResult ¶
type BeginResult int
BeginResult describes the state returned by Store.Begin.
const ( Started BeginResult = iota Replay InFlight Mismatch )
type Claim ¶
type Claim struct {
Result BeginResult
Response *StoredResponse
}
Claim is returned by Store.Begin.
type LeaseClaim ¶
type LeaseClaim struct {
Claim
Token LeaseToken
}
LeaseClaim augments a Claim with ownership for a newly started request.
type LeaseStore ¶
type LeaseStore interface {
Store
BeginLease(context.Context, string, string, string) (LeaseClaim, error)
CompleteLease(context.Context, string, string, string, LeaseToken, StoredResponse) error
ReleaseLease(context.Context, string, string, LeaseToken) error
}
LeaseStore is an optional token-aware extension to Store. It lets middleware release or complete only the claim it owns, preventing stale-owner ABA mutations after expiry and replacement.
type MemoryStore ¶
type MemoryStore struct {
// contains filtered or unexported fields
}
MemoryStore is an in-memory tenant-aware LeaseStore implementation. Its capacity is global across tenant/key pairs and it never evicts live records.
func NewMemoryStore ¶
func NewMemoryStore(ttl time.Duration) *MemoryStore
NewMemoryStore creates a bounded tenant-scoped in-memory idempotency store.
func NewMemoryStoreWithCapacity ¶
func NewMemoryStoreWithCapacity(ttl time.Duration, capacity int) *MemoryStore
NewMemoryStoreWithCapacity creates a tenant-scoped store with a live-record bound. Non-positive capacity restores DefaultMemoryCapacity.
func (*MemoryStore) Begin ¶
Begin implements the legacy Store contract. Middleware uses BeginLease when the dynamic store implements LeaseStore.
func (*MemoryStore) BeginLease ¶
func (s *MemoryStore) BeginLease(ctx context.Context, tenantID, key, fingerprint string) (LeaseClaim, error)
BeginLease starts or observes a token-owned claim.
func (*MemoryStore) Complete ¶
func (s *MemoryStore) Complete(ctx context.Context, tenantID, key, fingerprint string, response StoredResponse) error
Complete implements the legacy Store contract. It preserves key-only compatibility; token-aware callers should use CompleteLease.
func (*MemoryStore) CompleteLease ¶
func (s *MemoryStore) CompleteLease(ctx context.Context, tenantID, key, fingerprint string, token LeaseToken, response StoredResponse) error
CompleteLease persists only if token still owns the live in-flight claim.
func (*MemoryStore) Release ¶
func (s *MemoryStore) Release(ctx context.Context, tenantID, key string) error
Release implements the legacy key-only Store contract.
func (*MemoryStore) ReleaseLease ¶
func (s *MemoryStore) ReleaseLease(ctx context.Context, tenantID, key string, token LeaseToken) error
ReleaseLease removes only the live claim owned by token.
type Option ¶
type Option func(*options)
Option configures middleware.
func WithHeader ¶
WithHeader changes the request header used for the idempotency key.
func WithRequestBodyLimit ¶
WithRequestBodyLimit bounds keyed request bodies retained for fingerprinting. Non-positive values restore DefaultMaxRequestBytes.
func WithResponseBodyLimit ¶
WithResponseBodyLimit bounds keyed handler responses buffered before Store persistence. Keyed endpoints are therefore non-streaming. Non-positive values restore DefaultMaxResponseBytes.
func WithTenantResolver ¶
func WithTenantResolver(resolver TenantResolver) Option
WithTenantResolver configures tenant extraction. Nil restores single-tenant mode.
type Store ¶
type Store interface {
Begin(ctx context.Context, tenantID, key, fingerprint string) (Claim, error)
Complete(ctx context.Context, tenantID, key, fingerprint string, response StoredResponse) error
Release(ctx context.Context, tenantID, key string) error
}
Store is the source-compatible, tenant-aware idempotency storage contract.
If Complete returns an error, the result is ambiguous. Implementations that require duplicate suppression must retain the claim until store-owned expiry or operator reconciliation; middleware deliberately does not call Release.
type StoredResponse ¶
type StoredResponse struct {
StatusCode int
Header http.Header
Body []byte
Fingerprint string
CreatedAt time.Time
}
StoredResponse is replayed for matching completed idempotency claims.
type TenantResolver ¶
TenantResolver maps a request to its tenant namespace. The default resolver returns an empty string for single-tenant services.