diff --git a/Sources/OpenFeature/OpenFeatureAPI.swift b/Sources/OpenFeature/OpenFeatureAPI.swift index dd91332..de5508c 100644 --- a/Sources/OpenFeature/OpenFeatureAPI.swift +++ b/Sources/OpenFeature/OpenFeatureAPI.swift @@ -86,6 +86,12 @@ public class OpenFeatureAPI { self.hooks.removeAll() } + public func getState() -> ( + provider: FeatureProvider?, evaluationContext: EvaluationContext?, providerStatus: ProviderStatus + ) { + return self.stateManager.getState() + } + public func observe() -> AnyPublisher { return providerSubject.map { provider in if let provider = provider { @@ -181,4 +187,13 @@ struct SafeStateManager { self.providerStatus = .notReady } } + + // Method to read all values atomically + func getState() -> ( + provider: FeatureProvider?, evaluationContext: EvaluationContext?, providerStatus: ProviderStatus + ) { + return queue.sync { + (provider: provider, evaluationContext: evaluationContext, providerStatus: providerStatus) + } + } } diff --git a/Sources/OpenFeature/OpenFeatureClient.swift b/Sources/OpenFeature/OpenFeatureClient.swift index 5a1fb45..feddd88 100644 --- a/Sources/OpenFeature/OpenFeatureClient.swift +++ b/Sources/OpenFeature/OpenFeatureClient.swift @@ -68,19 +68,46 @@ extension OpenFeatureClient { defaultValue: T, options: FlagEvaluationOptions? ) -> FlagEvaluationDetails { + let openFeatureApiState = openFeatureApi.getState() var details = FlagEvaluationDetails(flagKey: key, value: defaultValue) - - if openFeatureApi.getProviderStatus() == .fatal { + switch openFeatureApiState.providerStatus { + case .fatal: details.errorCode = .providerFatal - details.errorMessage = "Fatal error reported by the Provider" // TODO Improve this message with error details + details.errorMessage = OpenFeatureError.providerFatalError(message: "unknown").description // TODO Improve this message with error details + details.reason = Reason.error.rawValue + return details + case .notReady: + details.errorCode = .providerNotReady + details.errorMessage = OpenFeatureError.providerNotReadyError.description + details.reason = Reason.error.rawValue + return details + case .reconciling, .stale: + details.reason = Reason.stale.rawValue + return details + case .error: + details.errorCode = .general + details.errorMessage = OpenFeatureError.generalError(message: "unknown").description // TODO Improve this message with error details details.reason = Reason.error.rawValue return details + case .ready: + return evaluateFlagReady( + key: key, defaultValue: defaultValue, options: options, openFeatureApiState: openFeatureApiState) } + } + + private func evaluateFlagReady( + key: String, + defaultValue: T, + options: FlagEvaluationOptions?, + openFeatureApiState: ( + provider: FeatureProvider?, evaluationContext: EvaluationContext?, providerStatus: ProviderStatus + ) + ) -> FlagEvaluationDetails { + var details = FlagEvaluationDetails(flagKey: key, value: defaultValue) let options = options ?? FlagEvaluationOptions(hooks: [], hookHints: [:]) let hints = options.hookHints - let context = openFeatureApi.getEvaluationContext() - - let provider = openFeatureApi.getProvider() ?? NoOpProvider() + let context = openFeatureApiState.evaluationContext + let provider = openFeatureApiState.provider ?? NoOpProvider() let hookCtx = HookContext( flagKey: key, type: T.flagValueType, @@ -88,45 +115,34 @@ extension OpenFeatureClient { ctx: context, clientMetadata: self.metadata, providerMetadata: provider.metadata) - hookLock.lock() let mergedHooks = provider.hooks + options.hooks + hooks + openFeatureApi.hooks hookLock.unlock() - do { hookSupport.beforeHooks(flagValueType: T.flagValueType, hookCtx: hookCtx, hooks: mergedHooks, hints: hints) - let providerEval = try createProviderEvaluation( key: key, context: context, defaultValue: defaultValue, provider: provider) - - let evalDetails = FlagEvaluationDetails.from(providerEval: providerEval, flagKey: key) - details = evalDetails - + details = FlagEvaluationDetails.from(providerEval: providerEval, flagKey: key) try hookSupport.afterHooks( - flagValueType: T.flagValueType, hookCtx: hookCtx, details: evalDetails, hooks: mergedHooks, hints: hints + flagValueType: T.flagValueType, hookCtx: hookCtx, details: details, hooks: mergedHooks, hints: hints ) } catch { logger.error("Unable to correctly evaluate flag with key \(key) due to exception \(error)") - if let error = error as? OpenFeatureError { details.errorCode = error.errorCode() } else { details.errorCode = .general } - details.errorMessage = "\(error)" details.reason = Reason.error.rawValue - hookSupport.errorHooks( flagValueType: T.flagValueType, hookCtx: hookCtx, error: error, hooks: mergedHooks, hints: hints) } - hookSupport.afterAllHooks( flagValueType: T.flagValueType, hookCtx: hookCtx, hooks: mergedHooks, hints: hints) - return details } diff --git a/Tests/OpenFeatureTests/DeveloperExperienceTests.swift b/Tests/OpenFeatureTests/DeveloperExperienceTests.swift index c4fb15b..b0b20e6 100644 --- a/Tests/OpenFeatureTests/DeveloperExperienceTests.swift +++ b/Tests/OpenFeatureTests/DeveloperExperienceTests.swift @@ -166,7 +166,7 @@ final class DeveloperExperienceTests: XCTestCase { let details = client.getDetails(key: "test", defaultValue: false) XCTAssertEqual(details.errorCode, .providerFatal) - XCTAssertEqual(details.errorMessage, "Fatal error reported by the Provider") + XCTAssertEqual(details.errorMessage, "A fatal error occurred in the provider: unknown") XCTAssertEqual(details.reason, Reason.error.rawValue) } }