Files
miti99bot/internal/storage/prefix.go
T
tiennm99 9d680c58ad fix(storage): report missing CAS support as unsupported instead of conflict
- 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
2026-06-11 21:40:16 +07:00

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
}