@@ -146,7 +146,7 @@ public struct SandboxTokenServer: TokenServer {
146146
147147// MARK: - Cache
148148
149- /// `CachingCredentialsProvider` handles in-memory caching of credentials from any other `CredentialsProvider`.
149+ /// `CachingCredentialsProvider` handles caching of credentials from any other `CredentialsProvider` using configurable storage .
150150public actor CachingCredentialsProvider : CredentialsProvider , Loggable {
151151 /// A tuple containing the request and response that were cached.
152152 public typealias Cached = ( ConnectionCredentials . Request , ConnectionCredentials . Response )
@@ -155,31 +155,74 @@ public actor CachingCredentialsProvider: CredentialsProvider, Loggable {
155155
156156 private let provider : CredentialsProvider
157157 private let validator : Validator
158-
159- private var cached : Cached ?
158+ private let storage : CredentialsStorage
160159
161160 /// Initialize a caching wrapper around any credentials provider.
162161 /// - Parameters:
163162 /// - provider: The underlying credentials provider to wrap
163+ /// - storage: The storage implementation to use for caching (defaults to in-memory storage)
164164 /// - validator: A closure to determine if cached credentials are still valid (defaults to JWT expiration check)
165- public init ( _ provider: CredentialsProvider , validator: @escaping Validator = { _, res in res. hasValidToken ( ) } ) {
165+ public init (
166+ _ provider: CredentialsProvider ,
167+ storage: CredentialsStorage = InMemoryCredentialsStorage ( ) ,
168+ validator: @escaping Validator = { _, res in res. hasValidToken ( ) }
169+ ) {
166170 self . provider = provider
171+ self . storage = storage
167172 self . validator = validator
168173 }
169174
170175 public func fetch( _ request: ConnectionCredentials . Request ) async throws -> ConnectionCredentials . Response {
171- if let ( cachedRequest, cachedResponse) = cached, cachedRequest == request, validator ( cachedRequest, cachedResponse) {
176+ if let ( cachedRequest, cachedResponse) = await storage. retrieve ( ) ,
177+ cachedRequest == request,
178+ validator ( cachedRequest, cachedResponse)
179+ {
172180 log ( " Using cached credentials " , . debug)
173181 return cachedResponse
174182 }
175183
176184 let response = try await provider. fetch ( request)
177- cached = ( request, response)
185+ try await storage . store ( ( request, response) )
178186 return response
179187 }
180188
181189 /// Invalidate the cached credentials, forcing a fresh fetch on the next request.
182- public func invalidate( ) {
190+ public func invalidate( ) async {
191+ await storage. clear ( )
192+ }
193+ }
194+
195+ // MARK: - Storage
196+
197+ /// Protocol for abstract storage that can persist and retrieve a single cached credential pair.
198+ /// Implement this protocol to create custom storage implementations e.g. for Keychain.
199+ public protocol CredentialsStorage : Sendable {
200+ /// Store credentials in the storage (replaces any existing credentials)
201+ func store( _ credentials: CachingCredentialsProvider . Cached ) async throws
202+
203+ /// Retrieve the cached credentials
204+ /// - Returns: The cached credentials if found, nil otherwise
205+ func retrieve( ) async -> CachingCredentialsProvider . Cached ?
206+
207+ /// Clear the stored credentials
208+ func clear( ) async
209+ }
210+
211+ /// Simple in-memory storage implementation
212+ public actor InMemoryCredentialsStorage : CredentialsStorage {
213+ private var cached : CachingCredentialsProvider . Cached ?
214+
215+ public init ( ) { }
216+
217+ public func store( _ credentials: CachingCredentialsProvider . Cached ) async throws {
218+ cached = credentials
219+ }
220+
221+ public func retrieve( ) async -> CachingCredentialsProvider . Cached ? {
222+ cached
223+ }
224+
225+ public func clear( ) async {
183226 cached = nil
184227 }
185228}
0 commit comments