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
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 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift b/ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift
index 3bb4737f..e84a5d2b 100644
--- a/ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift
+++ b/ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift
@@ -1,4 +1,5 @@
import ConfidenceProvider
+import Confidence
import OpenFeature
import SwiftUI
@@ -21,17 +22,21 @@ 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
}
- let provider = ConfidenceFeatureProvider
- .Builder(credentials: .clientSecret(secret: secret))
- .with(initializationStrategy: initializationStratgey)
+ let confidence = Confidence.Builder(clientSecret: secret)
+ .withInitializationstrategy(initializationStrategy: initializationStrategy)
.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())
- 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 d4a35f41..74afe395 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,16 +12,25 @@ 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"),
],
targets: [
+ .target(
+ name: "Confidence",
+ dependencies: [],
+ plugins: []
+ ),
.target(
name: "ConfidenceProvider",
dependencies: [
.product(name: "OpenFeature", package: "swift-sdk"),
+ "Confidence"
],
plugins: []
),
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
```
diff --git a/Sources/Confidence/Confidence.swift b/Sources/Confidence/Confidence.swift
new file mode 100644
index 00000000..99d1e282
--- /dev/null
+++ b/Sources/Confidence/Confidence.swift
@@ -0,0 +1,82 @@
+import Foundation
+
+public class Confidence: ConfidenceEventSender {
+ public var context: [String: String]
+ public let clientSecret: String
+ public var timeout: TimeInterval
+ public var region: ConfidenceRegion
+ public var initializationStrategy: InitializationStrategy
+
+ 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) - Targeting key: \(context["targeting_key"] ?? "UNKNOWN")")
+ }
+
+ public func updateContextEntry(key: String, value: String) {
+ context[key] = value
+ }
+
+ public func removeContextEntry(key: String) {
+ context.removeValue(forKey: key)
+ }
+
+ public func clearContext() {
+ context = [:]
+ }
+
+ // TODO: Implement creation of child instances
+ public func withContext(_ context: [String: String]) -> Self {
+ return self
+ }
+}
+
+extension Confidence {
+ public class Builder {
+ let clientSecret: String
+ var timeout: TimeInterval = 10.0
+ var region: ConfidenceRegion = .global
+ var initializationStrategy: InitializationStrategy = .fetchAndActivate
+
+ public init(clientSecret: String) {
+ self.clientSecret = clientSecret
+ }
+
+ public func withTimeout(timeout: TimeInterval) -> Builder {
+ self.timeout = timeout
+ return self
+ }
+
+
+ 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,
+ timeout: timeout,
+ region: region,
+ initializationStrategy: initializationStrategy
+ )
+ }
+ }
+}
diff --git a/Sources/Confidence/ConfidenceEventSender.swift b/Sources/Confidence/ConfidenceEventSender.swift
new file mode 100644
index 00000000..4747a543
--- /dev/null
+++ b/Sources/Confidence/ConfidenceEventSender.swift
@@ -0,0 +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/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/Contextual.swift b/Sources/Confidence/Contextual.swift
new file mode 100644
index 00000000..9b6859ce
--- /dev/null
+++ b/Sources/Confidence/Contextual.swift
@@ -0,0 +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 {
+ // 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/ConfidenceProvider/InitializationStrategy.swift b/Sources/Confidence/InitializationStrategy.swift
similarity index 59%
rename from Sources/ConfidenceProvider/InitializationStrategy.swift
rename to Sources/Confidence/InitializationStrategy.swift
index 0669c528..8dfbc0c0 100644
--- a/Sources/ConfidenceProvider/InitializationStrategy.swift
+++ b/Sources/Confidence/InitializationStrategy.swift
@@ -1,5 +1,6 @@
import Foundation
+/// Flag resolve configuration related to how to refresh flags at startup
public enum InitializationStrategy {
case fetchAndActivate, activateAndFetchAsync
}
diff --git a/Sources/ConfidenceProvider/ConfidenceClient/ConfidenceClientOptions.swift b/Sources/ConfidenceProvider/ConfidenceClient/ConfidenceClientOptions.swift
new file mode 100644
index 00000000..4c8de133
--- /dev/null
+++ b/Sources/ConfidenceProvider/ConfidenceClient/ConfidenceClientOptions.swift
@@ -0,0 +1,32 @@
+import Foundation
+import Confidence
+
+public struct ConfidenceClientOptions {
+ public var credentials: ConfidenceClientCredentials
+ public var timeout: TimeInterval
+ public var region: ConfidenceRegion
+ public var initializationStrategy: InitializationStrategy
+
+ public init(
+ credentials: ConfidenceClientCredentials,
+ timeout: TimeInterval? = nil,
+ region: ConfidenceRegion? = nil,
+ initializationStrategy: InitializationStrategy = .fetchAndActivate
+ ) {
+ self.credentials = credentials
+ 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
+ }
+ }
+}
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..e224e623 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,8 +22,9 @@ 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.
+ /// Should not be called externally, use `ConfidenceFeatureProvider.Builder`or init with `Confidence` instead.
init(
metadata: ProviderMetadata,
client: RemoteConfidenceClient,
@@ -30,8 +32,8 @@ public class ConfidenceFeatureProvider: FeatureProvider {
storage: Storage,
overrides: [String: LocalOverride] = [:],
flagApplier: FlagApplier,
- applyStorage: Storage,
- initializationStrategy: InitializationStrategy
+ initializationStrategy: InitializationStrategy,
+ confidence: Confidence?
) {
self.client = client
self.metadata = metadata
@@ -40,8 +42,34 @@ public class ConfidenceFeatureProvider: FeatureProvider {
self.flagApplier = flagApplier
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?) {
@@ -115,7 +143,7 @@ public class ConfidenceFeatureProvider: FeatureProvider {
}
public func getBooleanEvaluation(key: String, defaultValue: Bool, context: EvaluationContext?) throws
- -> OpenFeature.ProviderEvaluation
+ -> OpenFeature.ProviderEvaluation
{
return try errorWrappedResolveFlag(
flag: key,
@@ -125,7 +153,7 @@ public class ConfidenceFeatureProvider: FeatureProvider {
}
public func getStringEvaluation(key: String, defaultValue: String, context: EvaluationContext?) throws
- -> OpenFeature.ProviderEvaluation
+ -> OpenFeature.ProviderEvaluation
{
return try errorWrappedResolveFlag(
flag: key,
@@ -135,7 +163,7 @@ public class ConfidenceFeatureProvider: FeatureProvider {
}
public func getIntegerEvaluation(key: String, defaultValue: Int64, context: EvaluationContext?) throws
- -> OpenFeature.ProviderEvaluation
+ -> OpenFeature.ProviderEvaluation
{
return try errorWrappedResolveFlag(
flag: key,
@@ -145,7 +173,7 @@ public class ConfidenceFeatureProvider: FeatureProvider {
}
public func getDoubleEvaluation(key: String, defaultValue: Double, context: EvaluationContext?) throws
- -> OpenFeature.ProviderEvaluation
+ -> OpenFeature.ProviderEvaluation
{
return try errorWrappedResolveFlag(
flag: key,
@@ -155,7 +183,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,
@@ -194,7 +222,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)
@@ -251,18 +279,18 @@ 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
}
}
private func resolveFlagNoValue(defaultValue: T, resolverResult: ResolveResult, ctx: EvaluationContext)
- -> ProviderEvaluation
+ -> ProviderEvaluation
{
switch resolverResult.resolvedValue.resolveReason {
case .noMatch:
@@ -409,8 +437,9 @@ extension ConfidenceFeatureProvider {
var cache: ProviderCache?
var flagApplier: (any FlagApplier)?
var initializationStrategy: InitializationStrategy = .fetchAndActivate
- var applyStorage: Storage = DefaultStorage.resolverApplyCache()
+ var confidence: Confidence?
+ /// DEPRECATED
/// Initializes the builder with the given credentails.
///
/// OpenFeatureAPI.shared.setProvider(provider:
@@ -427,8 +456,7 @@ extension ConfidenceFeatureProvider {
flagApplier: FlagApplier?,
storage: Storage,
cache: ProviderCache?,
- initializationStrategy: InitializationStrategy,
- applyStorage: Storage
+ initializationStrategy: InitializationStrategy
) {
self.options = options
self.session = session
@@ -437,7 +465,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
@@ -453,8 +480,7 @@ extension ConfidenceFeatureProvider {
flagApplier: flagApplier,
storage: storage,
cache: cache,
- initializationStrategy: initializationStrategy,
- applyStorage: applyStorage
+ initializationStrategy: initializationStrategy
)
}
@@ -470,8 +496,7 @@ extension ConfidenceFeatureProvider {
flagApplier: flagApplier,
storage: storage,
cache: cache,
- initializationStrategy: initializationStrategy,
- applyStorage: applyStorage
+ initializationStrategy: initializationStrategy
)
}
@@ -487,8 +512,7 @@ extension ConfidenceFeatureProvider {
flagApplier: flagApplier,
storage: storage,
cache: cache,
- initializationStrategy: initializationStrategy,
- applyStorage: applyStorage
+ initializationStrategy: initializationStrategy
)
}
@@ -504,8 +528,7 @@ extension ConfidenceFeatureProvider {
flagApplier: flagApplier,
storage: storage,
cache: cache,
- initializationStrategy: initializationStrategy,
- applyStorage: applyStorage
+ initializationStrategy: initializationStrategy
)
}
@@ -521,8 +544,7 @@ extension ConfidenceFeatureProvider {
flagApplier: flagApplier,
storage: storage,
cache: cache,
- initializationStrategy: initializationStrategy,
- applyStorage: applyStorage
+ initializationStrategy: initializationStrategy
)
}
@@ -538,8 +560,7 @@ extension ConfidenceFeatureProvider {
flagApplier: flagApplier,
storage: storage,
cache: cache,
- initializationStrategy: initializationStrategy,
- applyStorage: applyStorage
+ initializationStrategy: initializationStrategy
)
}
@@ -572,21 +593,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)
@@ -605,8 +625,8 @@ extension ConfidenceFeatureProvider {
storage: storage,
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/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)