package session import ( "context" "errors" "fmt" ) // BackendLookup describes the slice of `backendclient.RESTClient` // SessionCache depends on. The narrow interface keeps this package free // of any backendclient import. type BackendLookup interface { LookupSession(ctx context.Context, deviceSessionID string) (Record, error) } // BackendCache resolves authenticated device sessions by issuing one // synchronous REST call to backend per request. The canonical implementation replaces the // previous Redis-backed projection with this thin wrapper; gateway no // longer keeps a process-local snapshot. See ARCHITECTURE.md §11 // «backend (sync REST), no Redis projection». type BackendCache struct { backend BackendLookup } // NewBackendCache constructs a Cache that delegates every Lookup to // backend over REST. backend must not be nil. func NewBackendCache(backend BackendLookup) (*BackendCache, error) { if backend == nil { return nil, errors.New("session.NewBackendCache: backend lookup must not be nil") } return &BackendCache{backend: backend}, nil } // Lookup resolves deviceSessionID via backend. ErrNotFound is forwarded // unchanged so callers can keep using the existing equality check. func (c *BackendCache) Lookup(ctx context.Context, deviceSessionID string) (Record, error) { if c == nil { return Record{}, errors.New("session backend cache: nil cache") } if c.backend == nil { return Record{}, errors.New("session backend cache: nil backend lookup") } rec, err := c.backend.LookupSession(ctx, deviceSessionID) if err != nil { return Record{}, fmt.Errorf("session backend cache: %w", err) } return rec, nil } var _ Cache = (*BackendCache)(nil)