package provider import "context" // RequestRecorder receives one record per provider API call. Implemented // by internal/metrics; defined here so this package needs no metrics // dependency. type RequestRecorder interface { ProviderRequest(provider, op, result string) } // WithMetrics wraps a Provider so every call is recorded with its // classified result — zero-cost instrumentation for the next five // providers, and the one place Class is called purely for observability. func WithMetrics(name string, p Provider, rec RequestRecorder) Provider { return &instrumented{name: name, inner: p, rec: rec} } type instrumented struct { name string inner Provider rec RequestRecorder } func (i *instrumented) Create(ctx context.Context, req CreateRequest) (string, error) { id, err := i.inner.Create(ctx, req) i.record("create", err) return id, err } func (i *instrumented) Get(ctx context.Context, providerID string) (*Instance, error) { inst, err := i.inner.Get(ctx, providerID) i.record("get", err) return inst, err } func (i *instrumented) Delete(ctx context.Context, providerID string) error { err := i.inner.Delete(ctx, providerID) i.record("delete", err) return err } func (i *instrumented) ListByTag(ctx context.Context) ([]Instance, error) { instances, err := i.inner.ListByTag(ctx) i.record("list", err) return instances, err } func (i *instrumented) record(op string, err error) { i.rec.ProviderRequest(i.name, op, resultLabel(err)) } func resultLabel(err error) string { switch Class(err) { case nil: return "ok" case ErrNotFound: return "not_found" case ErrQuotaExceeded: return "quota_exceeded" case ErrPermanent: return "permanent" default: return "transient" } }