mirror of
https://github.com/tiennm99/miti99bot.git
synced 2026-09-16 16:24:49 +00:00
- prefix.go: return errors.ErrUnsupported when inner store lacks CompareAndSwap, enabling fail-fast instead of infinite retries - Adds comprehensive CAS semantics test coverage for all KV backends: * memory_kv_test.go: new tests for basic operations and CAS failures * prefix_test.go: tests for wrapped CAS errors and unsupported operations * dynamodb_kv_test.go, firestore_kv_test.go: CAS failure scenarios - portfolio_test.go: test retry exhaustion, business-error short-circuit, concurrent updates, and fail-fast on unsupported CAS
68 lines
1.9 KiB
Go
68 lines
1.9 KiB
Go
package storage
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// Prefixed returns a KVStore that transparently prepends prefix+":" to every
|
|
// key. List() strips the prefix from returned keys so callers see their own
|
|
// flat namespace. The prefix must be non-empty.
|
|
func Prefixed(inner KVStore, prefix string) KVStore {
|
|
if prefix == "" {
|
|
panic("storage: Prefixed requires non-empty prefix")
|
|
}
|
|
return &prefixedStore{inner: inner, prefix: prefix + ":"}
|
|
}
|
|
|
|
type prefixedStore struct {
|
|
inner KVStore
|
|
prefix string
|
|
}
|
|
|
|
func (p *prefixedStore) k(key string) string { return p.prefix + key }
|
|
|
|
func (p *prefixedStore) Get(ctx context.Context, key string) ([]byte, error) {
|
|
return p.inner.Get(ctx, p.k(key))
|
|
}
|
|
|
|
func (p *prefixedStore) GetJSON(ctx context.Context, key string, dst any) error {
|
|
return p.inner.GetJSON(ctx, p.k(key), dst)
|
|
}
|
|
|
|
func (p *prefixedStore) Put(ctx context.Context, key string, val []byte) error {
|
|
return p.inner.Put(ctx, p.k(key), val)
|
|
}
|
|
|
|
func (p *prefixedStore) CompareAndSwap(ctx context.Context, key string, expected []byte, val []byte) error {
|
|
cas, ok := p.inner.(CompareAndSwapStore)
|
|
if !ok {
|
|
// A missing capability is permanent: report it as unsupported so
|
|
// callers fail fast instead of retrying it as a write conflict.
|
|
return fmt.Errorf("storage: inner store does not support compare-and-swap: %w", errors.ErrUnsupported)
|
|
}
|
|
return cas.CompareAndSwap(ctx, p.k(key), expected, val)
|
|
}
|
|
|
|
func (p *prefixedStore) PutJSON(ctx context.Context, key string, val any) error {
|
|
return p.inner.PutJSON(ctx, p.k(key), val)
|
|
}
|
|
|
|
func (p *prefixedStore) Delete(ctx context.Context, key string) error {
|
|
return p.inner.Delete(ctx, p.k(key))
|
|
}
|
|
|
|
func (p *prefixedStore) List(ctx context.Context, prefix string) ([]string, error) {
|
|
keys, err := p.inner.List(ctx, p.k(prefix))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out := make([]string, len(keys))
|
|
for i, k := range keys {
|
|
out[i] = strings.TrimPrefix(k, p.prefix)
|
|
}
|
|
return out, nil
|
|
}
|