From 15b4beb6e3d4e0146b3279d0c7fde7f48ec90776 Mon Sep 17 00:00:00 2001 From: Fabrizio Demaria Date: Wed, 27 Mar 2024 16:13:57 +0100 Subject: [PATCH 1/9] chore: Update ignore --- .gitignore | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index a6cc5249..ad8ce721 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ .DS_Store /.build /Packages -/*.xcodeproj +/**/*.xcodeproj xcuserdata/ DerivedData/ .swiftpm/config/registries.json @@ -10,4 +10,4 @@ DerivedData/ .build .mockingbird project.json -.swiftpm +.swiftpm \ No newline at end of file From 0a94cb85503be736cb0c28dd53b8ef7074de87d5 Mon Sep 17 00:00:00 2001 From: Fabrizio Demaria Date: Thu, 28 Mar 2024 15:42:42 +0100 Subject: [PATCH 2/9] Remove xcscheme file from repo --- .../xcschemes/ConfidenceDemoApp.xcscheme | 100 ------------------ 1 file changed, 100 deletions(-) delete mode 100644 ConfidenceDemoApp/ConfidenceDemoApp.xcodeproj/xcshareddata/xcschemes/ConfidenceDemoApp.xcscheme diff --git a/ConfidenceDemoApp/ConfidenceDemoApp.xcodeproj/xcshareddata/xcschemes/ConfidenceDemoApp.xcscheme b/ConfidenceDemoApp/ConfidenceDemoApp.xcodeproj/xcshareddata/xcschemes/ConfidenceDemoApp.xcscheme deleted file mode 100644 index 1514facf..00000000 --- a/ConfidenceDemoApp/ConfidenceDemoApp.xcodeproj/xcshareddata/xcschemes/ConfidenceDemoApp.xcscheme +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 67568a502d165af369b2cd4d80cb2b3f7f3c0867 Mon Sep 17 00:00:00 2001 From: Fabrizio Demaria Date: Thu, 28 Mar 2024 11:14:08 +0100 Subject: [PATCH 3/9] Fix test script used in CI --- scripts/run_tests.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/run_tests.sh b/scripts/run_tests.sh index 72fc485b..04385d5c 100755 --- a/scripts/run_tests.sh +++ b/scripts/run_tests.sh @@ -16,7 +16,6 @@ fi (cd $root_dir && TEST_RUNNER_CLIENT_TOKEN=$test_runner_client_token TEST_RUNNER_TEST_FLAG_NAME=$2 xcodebuild \ -quiet \ - -scheme ConfidenceProvider \ - -sdk "iphonesimulator" \ + -scheme Confidence-Package \ -destination 'platform=iOS Simulator,name=iPhone 14 Pro,OS=16.2' \ test) From ab5e368a0c1661cc2bb3d7c4b48a9fd6af8f9dea Mon Sep 17 00:00:00 2001 From: Fabrizio Demaria Date: Wed, 27 Mar 2024 16:15:24 +0100 Subject: [PATCH 4/9] feat: First Confidence Scaffolding --- .../ConfidenceDemoApp/ConfidenceDemoApp.swift | 10 ++- Package.swift | 13 +++- Sources/Confidence/Confidence.swift | 63 +++++++++++++++++++ .../Confidence/ConfidenceClientOptions.swift | 41 ++++++++++++ .../Confidence/ConfidenceEventSender.swift | 5 ++ Sources/Confidence/Contextual.swift | 11 ++++ .../Apply/FlagApplierWithRetries.swift | 1 + .../RemoteConfidenceClient.swift | 33 +--------- .../ConfidenceFeatureProvider.swift | 32 +++++++--- .../Http/NetworkClient.swift | 1 + .../InitializationStrategy.swift | 5 -- .../FlagApplierWithRetriesTest.swift | 1 + 12 files changed, 166 insertions(+), 50 deletions(-) create mode 100644 Sources/Confidence/Confidence.swift create mode 100644 Sources/Confidence/ConfidenceClientOptions.swift create mode 100644 Sources/Confidence/ConfidenceEventSender.swift create mode 100644 Sources/Confidence/Contextual.swift delete mode 100644 Sources/ConfidenceProvider/InitializationStrategy.swift diff --git a/ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift b/ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift index 3bb4737f..7b953607 100644 --- a/ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift +++ b/ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift @@ -1,4 +1,5 @@ import ConfidenceProvider +import Confidence import OpenFeature import SwiftUI @@ -26,10 +27,13 @@ extension ConfidenceDemoApp { initializationStratgey = .fetchAndActivate } - let provider = ConfidenceFeatureProvider - .Builder(credentials: .clientSecret(secret: secret)) - .with(initializationStrategy: initializationStratgey) + // TODO: Remove Builder pattern + let confidence = Confidence.Builder(clientSecret: secret) + .withOptions(options: ConfidenceClientOptions(initializationStrategy: initializationStratgey)) .build() + // TODO: Remove Builder pattern + let provider = ConfidenceFeatureProvider.Builder(confidence: confidence).build() + // NOTE: Using a random UUID for each app start is not advised and can result in getting stale values. let ctx = MutableContext(targetingKey: UUID.init().uuidString, structure: MutableStructure()) OpenFeatureAPI.shared.setProvider(provider: provider, initialContext: ctx) diff --git a/Package.swift b/Package.swift index d4a35f41..7e5811ae 100644 --- a/Package.swift +++ b/Package.swift @@ -4,7 +4,7 @@ import PackageDescription let package = Package( - name: "ConfidenceProvider", + name: "Confidence", platforms: [ .iOS(.v14), .macOS(.v12) @@ -12,7 +12,10 @@ let package = Package( products: [ .library( name: "ConfidenceProvider", - targets: ["ConfidenceProvider"]) + targets: ["ConfidenceProvider"]), + .library( + name: "Confidence", + targets: ["Confidence"]) ], dependencies: [ .package(url: "git@github.com:open-feature/swift-sdk.git", from: "0.1.0"), @@ -22,9 +25,15 @@ let package = Package( name: "ConfidenceProvider", dependencies: [ .product(name: "OpenFeature", package: "swift-sdk"), + "Confidence" ], plugins: [] ), + .target( + name: "Confidence", + dependencies: [], + plugins: [] + ), .testTarget( name: "ConfidenceProviderTests", dependencies: [ diff --git a/Sources/Confidence/Confidence.swift b/Sources/Confidence/Confidence.swift new file mode 100644 index 00000000..a701b5de --- /dev/null +++ b/Sources/Confidence/Confidence.swift @@ -0,0 +1,63 @@ +import Foundation + +public class Confidence: ConfidenceEventSender { + public var context: [String: String] + public let clientSecret: String + public let options: ConfidenceClientOptions + + init(clientSecret: String, options: ConfidenceClientOptions) { + self.clientSecret = clientSecret + self.options = options + self.context = [:] + } + + public func send(eventName: String) { + print("Sending \(eventName)") + } + + public func updateContextEntry(key: String, value: String) { + context[key] = value + } + + public func removeContextEntry(key: String) { + context.removeValue(forKey: key) + } + + public func clearContext() { + context = [:] + } + + public func withContext(_ context: [String: String]) -> Self { + // TODO + return self + } +} + +extension Confidence { + public struct Builder { + let clientSecret: String + var options: ConfidenceClientOptions + + public init(clientSecret: String) { + self.clientSecret = clientSecret + self.options = ConfidenceClientOptions( + credentials: ConfidenceClientCredentials.clientSecret(secret: (clientSecret))) + } + + init(clientSecret: String, options: ConfidenceClientOptions) { + self.clientSecret = clientSecret + self.options = options + } + + public func withOptions(options: ConfidenceClientOptions) -> Builder { + return Builder( + clientSecret: clientSecret, + options: options + ) + } + + public func build() -> Confidence { + return Confidence(clientSecret: clientSecret, options: ConfidenceClientOptions()) + } + } +} diff --git a/Sources/Confidence/ConfidenceClientOptions.swift b/Sources/Confidence/ConfidenceClientOptions.swift new file mode 100644 index 00000000..cc706cdc --- /dev/null +++ b/Sources/Confidence/ConfidenceClientOptions.swift @@ -0,0 +1,41 @@ +import Foundation + +public struct ConfidenceClientOptions { + public var credentials: ConfidenceClientCredentials // DEPRECATED + public var timeout: TimeInterval + public var region: ConfidenceRegion + public var initializationStrategy: InitializationStrategy + + public init( + credentials: ConfidenceClientCredentials? = nil, + timeout: TimeInterval? = nil, + region: ConfidenceRegion? = nil, + initializationStrategy: InitializationStrategy = .fetchAndActivate + ) { + self.credentials = credentials ?? ConfidenceClientCredentials.clientSecret(secret: "") + self.timeout = timeout ?? 10.0 + self.region = region ?? .global + self.initializationStrategy = initializationStrategy + } +} + +public enum ConfidenceClientCredentials { + case clientSecret(secret: String) + + public func getSecret() -> String { + switch self { + case .clientSecret(let secret): + return secret + } + } +} + +public enum ConfidenceRegion { + case global + case europe + case usa +} + +public enum InitializationStrategy { + case fetchAndActivate, activateAndFetchAsync +} diff --git a/Sources/Confidence/ConfidenceEventSender.swift b/Sources/Confidence/ConfidenceEventSender.swift new file mode 100644 index 00000000..24d30e80 --- /dev/null +++ b/Sources/Confidence/ConfidenceEventSender.swift @@ -0,0 +1,5 @@ +import Foundation + +public protocol ConfidenceEventSender: Contextual { + func send(eventName: String) +} diff --git a/Sources/Confidence/Contextual.swift b/Sources/Confidence/Contextual.swift new file mode 100644 index 00000000..1a3070ae --- /dev/null +++ b/Sources/Confidence/Contextual.swift @@ -0,0 +1,11 @@ +import Foundation + +public protocol Contextual { + var context: [String: String] { get set } // TODO Introdue complex types + + func updateContextEntry(key: String, value: String) + func removeContextEntry(key: String) + func clearContext() + + func withContext(_ context: [String: String]) -> Self +} diff --git a/Sources/ConfidenceProvider/Apply/FlagApplierWithRetries.swift b/Sources/ConfidenceProvider/Apply/FlagApplierWithRetries.swift index 4eb2e182..cf03c2ce 100644 --- a/Sources/ConfidenceProvider/Apply/FlagApplierWithRetries.swift +++ b/Sources/ConfidenceProvider/Apply/FlagApplierWithRetries.swift @@ -1,4 +1,5 @@ import Foundation +import Confidence import OpenFeature import os diff --git a/Sources/ConfidenceProvider/ConfidenceClient/RemoteConfidenceClient.swift b/Sources/ConfidenceProvider/ConfidenceClient/RemoteConfidenceClient.swift index 3ceba804..71b93fa8 100644 --- a/Sources/ConfidenceProvider/ConfidenceClient/RemoteConfidenceClient.swift +++ b/Sources/ConfidenceProvider/ConfidenceClient/RemoteConfidenceClient.swift @@ -1,6 +1,8 @@ import Foundation +import Confidence import OpenFeature + public class RemoteConfidenceClient: ConfidenceClient { private let targetingKey = "targeting_key" private let flagApplier: FlagApplier @@ -161,37 +163,6 @@ struct ApplyFlagsRequest: Codable { struct ApplyFlagsResponse: Codable { } -public struct ConfidenceClientOptions { - public var credentials: ConfidenceClientCredentials - public var timeout: TimeInterval - public var region: ConfidenceRegion - - public init( - credentials: ConfidenceClientCredentials, timeout: TimeInterval? = nil, region: ConfidenceRegion? = nil - ) { - self.credentials = credentials - self.timeout = timeout ?? 10.0 - self.region = region ?? .global - } -} - -public enum ConfidenceClientCredentials { - case clientSecret(secret: String) - - public func getSecret() -> String { - switch self { - case .clientSecret(let secret): - return secret - } - } -} - -public enum ConfidenceRegion { - case global - case europe - case usa -} - struct Sdk: Codable { init(id: String?, version: String?) { self.id = id ?? "SDK_ID_SWIFT_PROVIDER" diff --git a/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift b/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift index 8b41fe16..07f3128f 100644 --- a/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift +++ b/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift @@ -1,4 +1,5 @@ import Foundation +import Confidence import OpenFeature import Combine import os @@ -21,6 +22,7 @@ public class ConfidenceFeatureProvider: FeatureProvider { private let initializationStrategy: InitializationStrategy private let storage: Storage private let eventHandler = EventHandler(ProviderEvent.notReady) + private let confidence: Confidence? /// Should not be called externally, use `ConfidenceFeatureProvider.Builder` instead. init( @@ -31,7 +33,8 @@ public class ConfidenceFeatureProvider: FeatureProvider { overrides: [String: LocalOverride] = [:], flagApplier: FlagApplier, applyStorage: Storage, - initializationStrategy: InitializationStrategy + initializationStrategy: InitializationStrategy, + confidence: Confidence? ) { self.client = client self.metadata = metadata @@ -40,6 +43,7 @@ public class ConfidenceFeatureProvider: FeatureProvider { self.flagApplier = flagApplier self.initializationStrategy = initializationStrategy self.storage = storage + self.confidence = confidence resolver = LocalStorageResolver(cache: cache) } @@ -251,12 +255,12 @@ public class ConfidenceFeatureProvider: FeatureProvider { ) return evaluationResult } catch ConfidenceError.cachedValueExpired { - return ProviderEvaluation(value: defaultValue, - variant: nil, - reason: Reason.error.rawValue, - errorCode: ErrorCode.providerNotReady - )} - catch { + return ProviderEvaluation( + value: defaultValue, + variant: nil, + reason: Reason.error.rawValue, + errorCode: ErrorCode.providerNotReady) + } catch { throw error } } @@ -410,8 +414,9 @@ extension ConfidenceFeatureProvider { var flagApplier: (any FlagApplier)? var initializationStrategy: InitializationStrategy = .fetchAndActivate var applyStorage: Storage = DefaultStorage.resolverApplyCache() + var confidence: Confidence? - /// Initializes the builder with the given credentails. + /// Initializes the builder with the given credentails. DEPRECATED /// /// OpenFeatureAPI.shared.setProvider(provider: /// ConfidenceFeatureProvider.Builder(credentials: .clientSecret(secret: "mysecret")) @@ -420,6 +425,14 @@ extension ConfidenceFeatureProvider { self.options = ConfidenceClientOptions(credentials: credentials) } + /// TODO + public init(confidence: Confidence) { + self.options = ConfidenceClientOptions(credentials: ConfidenceClientCredentials + .clientSecret(secret: confidence.clientSecret)) + self.initializationStrategy = confidence.options.initializationStrategy + self.confidence = confidence + } + init( options: ConfidenceClientOptions, session: URLSession? = nil, @@ -606,7 +619,8 @@ extension ConfidenceFeatureProvider { overrides: localOverrides, flagApplier: flagApplier, applyStorage: applyStorage, - initializationStrategy: initializationStrategy + initializationStrategy: initializationStrategy, + confidence: confidence ) } } diff --git a/Sources/ConfidenceProvider/Http/NetworkClient.swift b/Sources/ConfidenceProvider/Http/NetworkClient.swift index d6a4e47f..ff43a61f 100644 --- a/Sources/ConfidenceProvider/Http/NetworkClient.swift +++ b/Sources/ConfidenceProvider/Http/NetworkClient.swift @@ -1,4 +1,5 @@ import Foundation +import Confidence final class NetworkClient: HttpClient { private let headers: [String: String] diff --git a/Sources/ConfidenceProvider/InitializationStrategy.swift b/Sources/ConfidenceProvider/InitializationStrategy.swift deleted file mode 100644 index 0669c528..00000000 --- a/Sources/ConfidenceProvider/InitializationStrategy.swift +++ /dev/null @@ -1,5 +0,0 @@ -import Foundation - -public enum InitializationStrategy { - case fetchAndActivate, activateAndFetchAsync -} diff --git a/Tests/ConfidenceProviderTests/FlagApplierWithRetriesTest.swift b/Tests/ConfidenceProviderTests/FlagApplierWithRetriesTest.swift index a95c0b4c..42b71f40 100644 --- a/Tests/ConfidenceProviderTests/FlagApplierWithRetriesTest.swift +++ b/Tests/ConfidenceProviderTests/FlagApplierWithRetriesTest.swift @@ -2,6 +2,7 @@ // swiftlint:disable file_length import Foundation import OpenFeature +import Confidence import XCTest @testable import ConfidenceProvider From d0dfe599678695e19eead6bebb091b783c6f4e53 Mon Sep 17 00:00:00 2001 From: Fabrizio Demaria Date: Wed, 27 Mar 2024 16:48:03 +0100 Subject: [PATCH 5/9] Basic EvaluationContext wiring in events --- .../ConfidenceDemoApp/ConfidenceDemoApp.swift | 5 +- Package.swift | 10 +-- Sources/Confidence/Confidence.swift | 67 +++++++++++++------ .../Confidence/ConfidenceEventSender.swift | 2 + Sources/Confidence/Contextual.swift | 8 ++- .../ConfidenceClientOptions.swift | 15 +---- .../ConfidenceFeatureProvider.swift | 17 +++-- 7 files changed, 81 insertions(+), 43 deletions(-) rename Sources/{Confidence => ConfidenceProvider/ConfidenceClient}/ConfidenceClientOptions.swift (72%) diff --git a/ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift b/ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift index 7b953607..309fef04 100644 --- a/ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift +++ b/ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift @@ -36,6 +36,9 @@ extension ConfidenceDemoApp { // NOTE: Using a random UUID for each app start is not advised and can result in getting stale values. let ctx = MutableContext(targetingKey: UUID.init().uuidString, structure: MutableStructure()) - OpenFeatureAPI.shared.setProvider(provider: provider, initialContext: ctx) + Task { + await OpenFeatureAPI.shared.setProviderAndWait(provider: provider, initialContext: ctx) + confidence.send(eventName: "my_event") + } } } diff --git a/Package.swift b/Package.swift index 7e5811ae..74afe395 100644 --- a/Package.swift +++ b/Package.swift @@ -21,6 +21,11 @@ let package = Package( .package(url: "git@github.com:open-feature/swift-sdk.git", from: "0.1.0"), ], targets: [ + .target( + name: "Confidence", + dependencies: [], + plugins: [] + ), .target( name: "ConfidenceProvider", dependencies: [ @@ -29,11 +34,6 @@ let package = Package( ], plugins: [] ), - .target( - name: "Confidence", - dependencies: [], - plugins: [] - ), .testTarget( name: "ConfidenceProviderTests", dependencies: [ diff --git a/Sources/Confidence/Confidence.swift b/Sources/Confidence/Confidence.swift index a701b5de..ccb13d1a 100644 --- a/Sources/Confidence/Confidence.swift +++ b/Sources/Confidence/Confidence.swift @@ -3,16 +3,24 @@ import Foundation public class Confidence: ConfidenceEventSender { public var context: [String: String] public let clientSecret: String - public let options: ConfidenceClientOptions + public var timeout: TimeInterval + public var region: ConfidenceRegion + public var initializationStrategy: InitializationStrategy - init(clientSecret: String, options: ConfidenceClientOptions) { - self.clientSecret = clientSecret - self.options = options + init(clientSecret: String, + timeout: TimeInterval, + region: ConfidenceRegion, + initializationStrategy: InitializationStrategy) { self.context = [:] + self.clientSecret = clientSecret + self.timeout = timeout + self.region = region + self.initializationStrategy = initializationStrategy } + // TODO: Implement actual event uploading to the backend public func send(eventName: String) { - print("Sending \(eventName)") + print("Sending \(eventName) - Targeting key: \(context["targeting_key"] ?? "UNKNOWN")") } public func updateContextEntry(key: String, value: String) { @@ -27,37 +35,58 @@ public class Confidence: ConfidenceEventSender { context = [:] } + // TODO: Implement creation of child instances public func withContext(_ context: [String: String]) -> Self { - // TODO return self } } extension Confidence { - public struct Builder { + public class Builder { let clientSecret: String - var options: ConfidenceClientOptions + var timeout: TimeInterval = 10.0 + var region: ConfidenceRegion = .global + var initializationStrategy: InitializationStrategy = .fetchAndActivate public init(clientSecret: String) { self.clientSecret = clientSecret - self.options = ConfidenceClientOptions( - credentials: ConfidenceClientCredentials.clientSecret(secret: (clientSecret))) } - init(clientSecret: String, options: ConfidenceClientOptions) { - self.clientSecret = clientSecret - self.options = options + public func withTimeout(timeout: TimeInterval) -> Builder { + self.timeout = timeout + return self } - public func withOptions(options: ConfidenceClientOptions) -> Builder { - return Builder( - clientSecret: clientSecret, - options: options - ) + + public func withRegion(region: ConfidenceRegion) -> Builder { + self.region = region + return self + } + + public func withInitializationstrategy(initializationStrategy: InitializationStrategy) -> Builder { + self.initializationStrategy = initializationStrategy + return self } public func build() -> Confidence { - return Confidence(clientSecret: clientSecret, options: ConfidenceClientOptions()) + return Confidence( + clientSecret: clientSecret, + timeout: timeout, + region: region, + initializationStrategy: initializationStrategy + ) } } } + +public enum InitializationStrategy { + case fetchAndActivate, activateAndFetchAsync +} + +public enum ConfidenceRegion { + case global + case europe + case usa +} + + diff --git a/Sources/Confidence/ConfidenceEventSender.swift b/Sources/Confidence/ConfidenceEventSender.swift index 24d30e80..4747a543 100644 --- a/Sources/Confidence/ConfidenceEventSender.swift +++ b/Sources/Confidence/ConfidenceEventSender.swift @@ -1,5 +1,7 @@ import Foundation +/// Sends events to Confidence. Contextual data is appended to each event +// TODO: Add functions for sending events with payload public protocol ConfidenceEventSender: Contextual { func send(eventName: String) } diff --git a/Sources/Confidence/Contextual.swift b/Sources/Confidence/Contextual.swift index 1a3070ae..9b6859ce 100644 --- a/Sources/Confidence/Contextual.swift +++ b/Sources/Confidence/Contextual.swift @@ -1,11 +1,15 @@ import Foundation +/// A Contextual implementer maintains context data and can create child instances +/// that can still access their parent's data public protocol Contextual { - var context: [String: String] { get set } // TODO Introdue complex types + // TODO: Add complex type to the context Dictionary + var context: [String: String] { get set } func updateContextEntry(key: String, value: String) func removeContextEntry(key: String) func clearContext() - + /// Creates a child Contextual instance that still has access + /// to its parent context func withContext(_ context: [String: String]) -> Self } diff --git a/Sources/Confidence/ConfidenceClientOptions.swift b/Sources/ConfidenceProvider/ConfidenceClient/ConfidenceClientOptions.swift similarity index 72% rename from Sources/Confidence/ConfidenceClientOptions.swift rename to Sources/ConfidenceProvider/ConfidenceClient/ConfidenceClientOptions.swift index cc706cdc..f2ae97e8 100644 --- a/Sources/Confidence/ConfidenceClientOptions.swift +++ b/Sources/ConfidenceProvider/ConfidenceClient/ConfidenceClientOptions.swift @@ -1,4 +1,5 @@ import Foundation +import Confidence public struct ConfidenceClientOptions { public var credentials: ConfidenceClientCredentials // DEPRECATED @@ -7,12 +8,12 @@ public struct ConfidenceClientOptions { public var initializationStrategy: InitializationStrategy public init( - credentials: ConfidenceClientCredentials? = nil, + credentials: ConfidenceClientCredentials, timeout: TimeInterval? = nil, region: ConfidenceRegion? = nil, initializationStrategy: InitializationStrategy = .fetchAndActivate ) { - self.credentials = credentials ?? ConfidenceClientCredentials.clientSecret(secret: "") + self.credentials = credentials self.timeout = timeout ?? 10.0 self.region = region ?? .global self.initializationStrategy = initializationStrategy @@ -29,13 +30,3 @@ public enum ConfidenceClientCredentials { } } } - -public enum ConfidenceRegion { - case global - case europe - case usa -} - -public enum InitializationStrategy { - case fetchAndActivate, activateAndFetchAsync -} diff --git a/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift b/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift index 07f3128f..4101e556 100644 --- a/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift +++ b/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift @@ -54,6 +54,8 @@ public class ConfidenceFeatureProvider: FeatureProvider { } if self.initializationStrategy == .activateAndFetchAsync { + // TODO: Set the entire context + confidence?.context = ["targeting_key": "CACHED"] eventHandler.send(.ready) } @@ -70,6 +72,8 @@ public class ConfidenceFeatureProvider: FeatureProvider { // signal the provider is ready after the network request is done if self.initializationStrategy == .fetchAndActivate { + // TODO: Set the entire context + confidence?.context = ["targeting_key": initialContext.getTargetingKey()] eventHandler.send(.ready) } } catch { @@ -111,6 +115,8 @@ public class ConfidenceFeatureProvider: FeatureProvider { // update the storage try await store(with: newContext, resolveResult: resolveResult, refreshCache: true) eventHandler.send(ProviderEvent.ready) + // TODO: Set the entire context + confidence?.context = ["targeting_key": newContext.getTargetingKey()] } catch { eventHandler.send(ProviderEvent.ready) // do nothing @@ -416,7 +422,8 @@ extension ConfidenceFeatureProvider { var applyStorage: Storage = DefaultStorage.resolverApplyCache() var confidence: Confidence? - /// Initializes the builder with the given credentails. DEPRECATED + /// DEPRECATED + /// Initializes the builder with the given credentails. /// /// OpenFeatureAPI.shared.setProvider(provider: /// ConfidenceFeatureProvider.Builder(credentials: .clientSecret(secret: "mysecret")) @@ -427,9 +434,11 @@ extension ConfidenceFeatureProvider { /// TODO public init(confidence: Confidence) { - self.options = ConfidenceClientOptions(credentials: ConfidenceClientCredentials - .clientSecret(secret: confidence.clientSecret)) - self.initializationStrategy = confidence.options.initializationStrategy + self.options = ConfidenceClientOptions( + credentials: ConfidenceClientCredentials.clientSecret(secret: confidence.clientSecret), + timeout: confidence.timeout, + region: confidence.region) + self.initializationStrategy = confidence.initializationStrategy self.confidence = confidence } From e59b4cbc8394a881787e625d9626dd20b3624542 Mon Sep 17 00:00:00 2001 From: Fabrizio Demaria Date: Thu, 28 Mar 2024 15:43:00 +0100 Subject: [PATCH 6/9] More Builder and constructor refactoring --- .../ConfidenceDemoApp/ConfidenceDemoApp.swift | 10 +- Sources/Confidence/Confidence.swift | 12 +-- .../Apply/FlagApplierWithRetries.swift | 1 - .../ConfidenceClientOptions.swift | 2 +- .../ConfidenceFeatureProvider.swift | 95 ++++++++++--------- .../FlagApplierWithRetriesTest.swift | 1 - 6 files changed, 60 insertions(+), 61 deletions(-) diff --git a/ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift b/ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift index 309fef04..e84a5d2b 100644 --- a/ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift +++ b/ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift @@ -22,17 +22,15 @@ extension ConfidenceDemoApp { } // If we have no cache, then do a fetch first. - var initializationStratgey: InitializationStrategy = .activateAndFetchAsync + var initializationStrategy: InitializationStrategy = .activateAndFetchAsync if ConfidenceFeatureProvider.isStorageEmpty() { - initializationStratgey = .fetchAndActivate + initializationStrategy = .fetchAndActivate } - // TODO: Remove Builder pattern let confidence = Confidence.Builder(clientSecret: secret) - .withOptions(options: ConfidenceClientOptions(initializationStrategy: initializationStratgey)) + .withInitializationstrategy(initializationStrategy: initializationStrategy) .build() - // TODO: Remove Builder pattern - let provider = ConfidenceFeatureProvider.Builder(confidence: confidence).build() + let provider = ConfidenceFeatureProvider(confidence: confidence) // NOTE: Using a random UUID for each app start is not advised and can result in getting stale values. let ctx = MutableContext(targetingKey: UUID.init().uuidString, structure: MutableStructure()) diff --git a/Sources/Confidence/Confidence.swift b/Sources/Confidence/Confidence.swift index ccb13d1a..174cc314 100644 --- a/Sources/Confidence/Confidence.swift +++ b/Sources/Confidence/Confidence.swift @@ -7,10 +7,12 @@ public class Confidence: ConfidenceEventSender { public var region: ConfidenceRegion public var initializationStrategy: InitializationStrategy - init(clientSecret: String, - timeout: TimeInterval, - region: ConfidenceRegion, - initializationStrategy: InitializationStrategy) { + init( + clientSecret: String, + timeout: TimeInterval, + region: ConfidenceRegion, + initializationStrategy: InitializationStrategy + ) { self.context = [:] self.clientSecret = clientSecret self.timeout = timeout @@ -88,5 +90,3 @@ public enum ConfidenceRegion { case europe case usa } - - diff --git a/Sources/ConfidenceProvider/Apply/FlagApplierWithRetries.swift b/Sources/ConfidenceProvider/Apply/FlagApplierWithRetries.swift index cf03c2ce..4eb2e182 100644 --- a/Sources/ConfidenceProvider/Apply/FlagApplierWithRetries.swift +++ b/Sources/ConfidenceProvider/Apply/FlagApplierWithRetries.swift @@ -1,5 +1,4 @@ import Foundation -import Confidence import OpenFeature import os diff --git a/Sources/ConfidenceProvider/ConfidenceClient/ConfidenceClientOptions.swift b/Sources/ConfidenceProvider/ConfidenceClient/ConfidenceClientOptions.swift index f2ae97e8..4c8de133 100644 --- a/Sources/ConfidenceProvider/ConfidenceClient/ConfidenceClientOptions.swift +++ b/Sources/ConfidenceProvider/ConfidenceClient/ConfidenceClientOptions.swift @@ -2,7 +2,7 @@ import Foundation import Confidence public struct ConfidenceClientOptions { - public var credentials: ConfidenceClientCredentials // DEPRECATED + public var credentials: ConfidenceClientCredentials public var timeout: TimeInterval public var region: ConfidenceRegion public var initializationStrategy: InitializationStrategy diff --git a/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift b/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift index 4101e556..768b1902 100644 --- a/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift +++ b/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift @@ -24,7 +24,7 @@ public class ConfidenceFeatureProvider: FeatureProvider { private let eventHandler = EventHandler(ProviderEvent.notReady) private let confidence: Confidence? - /// Should not be called externally, use `ConfidenceFeatureProvider.Builder` instead. + /// Should not be called externally, use `ConfidenceFeatureProvider.Builder`or init with `Confidence` instead. init( metadata: ProviderMetadata, client: RemoteConfidenceClient, @@ -32,7 +32,6 @@ public class ConfidenceFeatureProvider: FeatureProvider { storage: Storage, overrides: [String: LocalOverride] = [:], flagApplier: FlagApplier, - applyStorage: Storage, initializationStrategy: InitializationStrategy, confidence: Confidence? ) { @@ -44,8 +43,33 @@ public class ConfidenceFeatureProvider: FeatureProvider { self.initializationStrategy = initializationStrategy self.storage = storage self.confidence = confidence + self.resolver = LocalStorageResolver(cache: cache) + } - resolver = LocalStorageResolver(cache: cache) + /// Initialize the Provider via a `Confidence` object. + public init(confidence: Confidence) { + let metadata = ConfidenceMetadata(version: "0.1.4") // x-release-please-version + let options = ConfidenceClientOptions( + credentials: ConfidenceClientCredentials.clientSecret(secret: confidence.clientSecret), + timeout: confidence.timeout, + region: confidence.region) + self.metadata = metadata + self.cache = InMemoryProviderCache.from(storage: DefaultStorage.resolverFlagsCache()) + self.storage = DefaultStorage.resolverFlagsCache() + self.resolver = LocalStorageResolver(cache: cache) + self.flagApplier = FlagApplierWithRetries( + httpClient: NetworkClient(region: options.region), + storage: DefaultStorage.applierFlagsCache(), + options: options, + metadata: metadata) + self.client = RemoteConfidenceClient( + options: options, + applyOnResolve: false, + flagApplier: flagApplier, + metadata: metadata) + self.initializationStrategy = confidence.initializationStrategy + self.overrides = [:] + self.confidence = confidence } public func initialize(initialContext: OpenFeature.EvaluationContext?) { @@ -125,7 +149,7 @@ public class ConfidenceFeatureProvider: FeatureProvider { } public func getBooleanEvaluation(key: String, defaultValue: Bool, context: EvaluationContext?) throws - -> OpenFeature.ProviderEvaluation + -> OpenFeature.ProviderEvaluation { return try errorWrappedResolveFlag( flag: key, @@ -135,7 +159,7 @@ public class ConfidenceFeatureProvider: FeatureProvider { } public func getStringEvaluation(key: String, defaultValue: String, context: EvaluationContext?) throws - -> OpenFeature.ProviderEvaluation + -> OpenFeature.ProviderEvaluation { return try errorWrappedResolveFlag( flag: key, @@ -145,7 +169,7 @@ public class ConfidenceFeatureProvider: FeatureProvider { } public func getIntegerEvaluation(key: String, defaultValue: Int64, context: EvaluationContext?) throws - -> OpenFeature.ProviderEvaluation + -> OpenFeature.ProviderEvaluation { return try errorWrappedResolveFlag( flag: key, @@ -155,7 +179,7 @@ public class ConfidenceFeatureProvider: FeatureProvider { } public func getDoubleEvaluation(key: String, defaultValue: Double, context: EvaluationContext?) throws - -> OpenFeature.ProviderEvaluation + -> OpenFeature.ProviderEvaluation { return try errorWrappedResolveFlag( flag: key, @@ -165,7 +189,7 @@ public class ConfidenceFeatureProvider: FeatureProvider { } public func getObjectEvaluation(key: String, defaultValue: OpenFeature.Value, context: EvaluationContext?) - throws -> OpenFeature.ProviderEvaluation + throws -> OpenFeature.ProviderEvaluation { return try errorWrappedResolveFlag( flag: key, @@ -204,7 +228,7 @@ public class ConfidenceFeatureProvider: FeatureProvider { } public func errorWrappedResolveFlag(flag: String, defaultValue: T, ctx: EvaluationContext?, errorPrefix: String) - throws -> ProviderEvaluation + throws -> ProviderEvaluation { do { let path = try FlagPath.getPath(for: flag) @@ -272,7 +296,7 @@ public class ConfidenceFeatureProvider: FeatureProvider { } private func resolveFlagNoValue(defaultValue: T, resolverResult: ResolveResult, ctx: EvaluationContext) - -> ProviderEvaluation + -> ProviderEvaluation { switch resolverResult.resolvedValue.resolveReason { case .noMatch: @@ -419,7 +443,6 @@ extension ConfidenceFeatureProvider { var cache: ProviderCache? var flagApplier: (any FlagApplier)? var initializationStrategy: InitializationStrategy = .fetchAndActivate - var applyStorage: Storage = DefaultStorage.resolverApplyCache() var confidence: Confidence? /// DEPRECATED @@ -432,16 +455,6 @@ extension ConfidenceFeatureProvider { self.options = ConfidenceClientOptions(credentials: credentials) } - /// TODO - public init(confidence: Confidence) { - self.options = ConfidenceClientOptions( - credentials: ConfidenceClientCredentials.clientSecret(secret: confidence.clientSecret), - timeout: confidence.timeout, - region: confidence.region) - self.initializationStrategy = confidence.initializationStrategy - self.confidence = confidence - } - init( options: ConfidenceClientOptions, session: URLSession? = nil, @@ -449,8 +462,7 @@ extension ConfidenceFeatureProvider { flagApplier: FlagApplier?, storage: Storage, cache: ProviderCache?, - initializationStrategy: InitializationStrategy, - applyStorage: Storage + initializationStrategy: InitializationStrategy ) { self.options = options self.session = session @@ -459,7 +471,6 @@ extension ConfidenceFeatureProvider { self.storage = storage self.cache = cache self.initializationStrategy = initializationStrategy - self.applyStorage = applyStorage } /// Allows the `ConfidenceClient` to be configured with a custom URLSession, useful for @@ -475,8 +486,7 @@ extension ConfidenceFeatureProvider { flagApplier: flagApplier, storage: storage, cache: cache, - initializationStrategy: initializationStrategy, - applyStorage: applyStorage + initializationStrategy: initializationStrategy ) } @@ -492,8 +502,7 @@ extension ConfidenceFeatureProvider { flagApplier: flagApplier, storage: storage, cache: cache, - initializationStrategy: initializationStrategy, - applyStorage: applyStorage + initializationStrategy: initializationStrategy ) } @@ -509,8 +518,7 @@ extension ConfidenceFeatureProvider { flagApplier: flagApplier, storage: storage, cache: cache, - initializationStrategy: initializationStrategy, - applyStorage: applyStorage + initializationStrategy: initializationStrategy ) } @@ -526,8 +534,7 @@ extension ConfidenceFeatureProvider { flagApplier: flagApplier, storage: storage, cache: cache, - initializationStrategy: initializationStrategy, - applyStorage: applyStorage + initializationStrategy: initializationStrategy ) } @@ -543,8 +550,7 @@ extension ConfidenceFeatureProvider { flagApplier: flagApplier, storage: storage, cache: cache, - initializationStrategy: initializationStrategy, - applyStorage: applyStorage + initializationStrategy: initializationStrategy ) } @@ -560,8 +566,7 @@ extension ConfidenceFeatureProvider { flagApplier: flagApplier, storage: storage, cache: cache, - initializationStrategy: initializationStrategy, - applyStorage: applyStorage + initializationStrategy: initializationStrategy ) } @@ -594,21 +599,20 @@ extension ConfidenceFeatureProvider { flagApplier: flagApplier, storage: storage, cache: cache, - initializationStrategy: initializationStrategy, - applyStorage: applyStorage + initializationStrategy: initializationStrategy ) } /// Creates the `ConfidenceFeatureProvider` according to the settings specified in the builder. public func build() -> ConfidenceFeatureProvider { let flagApplier = - flagApplier - ?? FlagApplierWithRetries( - httpClient: NetworkClient(region: options.region), - storage: DefaultStorage.applierFlagsCache(), - options: options, - metadata: metadata - ) + flagApplier + ?? FlagApplierWithRetries( + httpClient: NetworkClient(region: options.region), + storage: DefaultStorage.applierFlagsCache(), + options: options, + metadata: metadata + ) let cache = cache ?? InMemoryProviderCache.from(storage: storage) @@ -627,7 +631,6 @@ extension ConfidenceFeatureProvider { storage: storage, overrides: localOverrides, flagApplier: flagApplier, - applyStorage: applyStorage, initializationStrategy: initializationStrategy, confidence: confidence ) diff --git a/Tests/ConfidenceProviderTests/FlagApplierWithRetriesTest.swift b/Tests/ConfidenceProviderTests/FlagApplierWithRetriesTest.swift index 42b71f40..a95c0b4c 100644 --- a/Tests/ConfidenceProviderTests/FlagApplierWithRetriesTest.swift +++ b/Tests/ConfidenceProviderTests/FlagApplierWithRetriesTest.swift @@ -2,7 +2,6 @@ // swiftlint:disable file_length import Foundation import OpenFeature -import Confidence import XCTest @testable import ConfidenceProvider From cf8d9abfbe56537dbbf5087deb82ac37c93126e6 Mon Sep 17 00:00:00 2001 From: Fabrizio Demaria Date: Thu, 28 Mar 2024 16:06:33 +0100 Subject: [PATCH 7/9] Update README --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 969ba75e..aaa25f5d 100644 --- a/README.md +++ b/README.md @@ -37,16 +37,18 @@ In the dependencies section of Package.swift add: and in the target dependencies section add: ```swift .product(name: "ConfidenceProvider", package: "confidence-openfeature-provider-swift"), +.product(name: "Confidence", package: "confidence-openfeature-provider-swift"), ``` ## Usage ### Import Modules -Import the `ConfidenceProvider` and `OpenFeature` modules +Import the `ConfidenceProvider`, the `Confidence` and the `OpenFeature` modules ```swift import ConfidenceProvider +import Confidence import OpenFeature ``` From ca447cd2664f46dadfb152695a9417232ca135f8 Mon Sep 17 00:00:00 2001 From: Fabrizio Demaria Date: Tue, 2 Apr 2024 14:29:51 +0200 Subject: [PATCH 8/9] Move enum to standalone files --- Sources/Confidence/Confidence.swift | 10 ---------- Sources/Confidence/ConfidenceRegion.swift | 9 +++++++++ Sources/Confidence/InitializationStrategy.swift | 6 ++++++ 3 files changed, 15 insertions(+), 10 deletions(-) create mode 100644 Sources/Confidence/ConfidenceRegion.swift create mode 100644 Sources/Confidence/InitializationStrategy.swift diff --git a/Sources/Confidence/Confidence.swift b/Sources/Confidence/Confidence.swift index 174cc314..99d1e282 100644 --- a/Sources/Confidence/Confidence.swift +++ b/Sources/Confidence/Confidence.swift @@ -80,13 +80,3 @@ extension Confidence { } } } - -public enum InitializationStrategy { - case fetchAndActivate, activateAndFetchAsync -} - -public enum ConfidenceRegion { - case global - case europe - case usa -} diff --git a/Sources/Confidence/ConfidenceRegion.swift b/Sources/Confidence/ConfidenceRegion.swift new file mode 100644 index 00000000..231b90a2 --- /dev/null +++ b/Sources/Confidence/ConfidenceRegion.swift @@ -0,0 +1,9 @@ +import Foundation + +/// Sets the region for the network request to the Confidence backend. +/// This is applied for both sending events as well as fetching flag's data. +public enum ConfidenceRegion { + case global + case europe + case usa +} diff --git a/Sources/Confidence/InitializationStrategy.swift b/Sources/Confidence/InitializationStrategy.swift new file mode 100644 index 00000000..8dfbc0c0 --- /dev/null +++ b/Sources/Confidence/InitializationStrategy.swift @@ -0,0 +1,6 @@ +import Foundation + +/// Flag resolve configuration related to how to refresh flags at startup +public enum InitializationStrategy { + case fetchAndActivate, activateAndFetchAsync +} From 67ecbac1a6e92a1f743045ac3a4ff51e6b1204bf Mon Sep 17 00:00:00 2001 From: Fabrizio Demaria Date: Tue, 2 Apr 2024 14:32:21 +0200 Subject: [PATCH 9/9] Remove temporary code --- Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift b/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift index 768b1902..e224e623 100644 --- a/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift +++ b/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift @@ -78,8 +78,6 @@ public class ConfidenceFeatureProvider: FeatureProvider { } if self.initializationStrategy == .activateAndFetchAsync { - // TODO: Set the entire context - confidence?.context = ["targeting_key": "CACHED"] eventHandler.send(.ready) } @@ -96,8 +94,6 @@ public class ConfidenceFeatureProvider: FeatureProvider { // signal the provider is ready after the network request is done if self.initializationStrategy == .fetchAndActivate { - // TODO: Set the entire context - confidence?.context = ["targeting_key": initialContext.getTargetingKey()] eventHandler.send(.ready) } } catch { @@ -139,8 +135,6 @@ public class ConfidenceFeatureProvider: FeatureProvider { // update the storage try await store(with: newContext, resolveResult: resolveResult, refreshCache: true) eventHandler.send(ProviderEvent.ready) - // TODO: Set the entire context - confidence?.context = ["targeting_key": newContext.getTargetingKey()] } catch { eventHandler.send(ProviderEvent.ready) // do nothing