Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift (135 lines of code) (raw):

import Foundation import Combine import Confidence import OpenFeature import os struct Metadata: ProviderMetadata { var name: String? } /// The implementation of the Confidence Feature Provider. This implementation allows to pre-cache evaluations. public class ConfidenceFeatureProvider: FeatureProvider { public static let providerId: String = "SDK_ID_SWIFT_PROVIDER" public var metadata: ProviderMetadata public var hooks: [any Hook] = [] private let lock = UnfairLock() private let initializationStrategy: InitializationStrategy private let eventHandler = EventHandler(ProviderEvent.notReady) private let confidence: Confidence private let confidenceFeatureProviderQueue = DispatchQueue(label: "com.provider.queue") private var cancellables = Set<AnyCancellable>() private var currentResolveTask: Task<Void, Never>? /** Initialize the Provider via a `Confidence` object. The `initializationStrategy` defines when the Provider is ready to read flags, before or after a refresh of the flag evaluation fata. */ public convenience init(confidence: Confidence, initializationStrategy: InitializationStrategy = .fetchAndActivate) { self.init(confidence: confidence, session: nil) } internal init( confidence: Confidence, initializationStrategy: InitializationStrategy = .fetchAndActivate, session: URLSession? ) { self.metadata = Metadata(name: ConfidenceFeatureProvider.providerId) self.initializationStrategy = initializationStrategy self.confidence = confidence } public func initialize(initialContext: OpenFeature.EvaluationContext?) { let context = ConfidenceTypeMapper.from(ctx: initialContext ?? MutableContext(attributes: [:])) confidence.putContextLocal(context: context) do { if initializationStrategy == .activateAndFetchAsync { try confidence.activate() eventHandler.send(.ready) Task { await confidence.asyncFetch() } } else { Task { do { try await confidence.fetchAndActivate() eventHandler.send(.ready) } catch { eventHandler.send(.error) } } } } catch { eventHandler.send(.error) } } func shutdown() { for cancellable in cancellables { cancellable.cancel() } cancellables.removeAll() currentResolveTask?.cancel() } public func onContextSet( oldContext: OpenFeature.EvaluationContext?, newContext: OpenFeature.EvaluationContext ) { let removedKeys: [String] = oldContext.map { Array($0.asMap().filter { key, _ in !newContext.asMap().keys.contains(key) }.keys) } ?? [] Task { confidence.putContext(context: ConfidenceTypeMapper.from(ctx: newContext), removedKeys: removedKeys) } } public func getBooleanEvaluation(key: String, defaultValue: Bool, context: EvaluationContext?) throws -> OpenFeature.ProviderEvaluation<Bool> { try confidence.getEvaluation(key: key, defaultValue: defaultValue).toProviderEvaluation() } public func getStringEvaluation(key: String, defaultValue: String, context: EvaluationContext?) throws -> OpenFeature.ProviderEvaluation<String> { try confidence.getEvaluation(key: key, defaultValue: defaultValue).toProviderEvaluation() } public func getIntegerEvaluation(key: String, defaultValue: Int64, context: EvaluationContext?) throws -> OpenFeature.ProviderEvaluation<Int64> { try confidence.getEvaluation(key: key, defaultValue: defaultValue).toProviderEvaluation() } public func getDoubleEvaluation(key: String, defaultValue: Double, context: EvaluationContext?) throws -> OpenFeature.ProviderEvaluation<Double> { try confidence.getEvaluation(key: key, defaultValue: defaultValue).toProviderEvaluation() } public func getObjectEvaluation(key: String, defaultValue: OpenFeature.Value, context: EvaluationContext?) throws -> OpenFeature.ProviderEvaluation<OpenFeature.Value> { try confidence.getEvaluation(key: key, defaultValue: defaultValue).toProviderEvaluation() } public func observe() -> AnyPublisher<OpenFeature.ProviderEvent, Never> { return eventHandler.observe() } private func withLock(callback: @escaping (ConfidenceFeatureProvider) -> Void) { confidenceFeatureProviderQueue.sync { [weak self] in guard let self = self else { return } callback(self) } } } extension Evaluation { func toProviderEvaluation() throws -> ProviderEvaluation<T> { if let errorCode = self.errorCode { switch errorCode { case .providerNotReady: throw OpenFeatureError.providerNotReadyError case .invalidContext: throw OpenFeatureError.invalidContextError case .flagNotFound: throw OpenFeatureError.flagNotFoundError(key: self.errorMessage ?? "unknown key") case .evaluationError: throw OpenFeatureError.generalError(message: self.errorMessage ?? "unknown error") case .typeMismatch: throw OpenFeatureError.typeMismatchError } } return ProviderEvaluation( value: self.value, variant: self.variant, reason: self.reason.rawValue, errorCode: nil, errorMessage: nil ) } }