Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: CreateConfidence from Provider for Metadata #141

Merged
merged 2 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 33 additions & 12 deletions Sources/Confidence/Confidence.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,25 @@ import Combine
import os

public class Confidence: ConfidenceEventSender {
public let clientSecret: String
public var region: ConfidenceRegion
private let clientSecret: String
private var region: ConfidenceRegion
private let parent: ConfidenceContextProvider?
private let eventSenderEngine: EventSenderEngine
private let contextSubject = CurrentValueSubject<ConfidenceStruct, Never>([:])
private var removedContextKeys: Set<String> = Set()
private let confidenceQueue = DispatchQueue(label: "com.confidence.queue")
private let remoteFlagResolver: ConfidenceResolveClient
private let flagApplier: FlagApplier
private var cache = FlagResolution.EMPTY
private var storage: Storage
internal let contextReconciliatedChanges = PassthroughSubject<String, Never>()
private var cancellables = Set<AnyCancellable>()
private var currentFetchTask: Task<(), Never>?

// Internal for testing
internal let remoteFlagResolver: ConfidenceResolveClient
internal let contextReconciliatedChanges = PassthroughSubject<String, Never>()

public static let sdkId: String = "SDK_ID_SWIFT_CONFIDENCE"

required init(
clientSecret: String,
region: ConfidenceRegion,
Expand Down Expand Up @@ -255,15 +259,20 @@ public class Confidence: ConfidenceEventSender {

extension Confidence {
public class Builder {
let clientSecret: String
// Must be configured or configured automatically
internal let clientSecret: String
internal let eventStorage: EventStorage
internal let visitorId = VisitorUtil().getId()

// Can be configured
internal var region: ConfidenceRegion = .global
internal var metadata: ConfidenceMetadata?
internal var initialContext: ConfidenceStruct = [:]

// Injectable for testing
internal var flagApplier: FlagApplier?
internal var storage: Storage?
internal let eventStorage: EventStorage
internal var flagResolver: ConfidenceResolveClient?
var region: ConfidenceRegion = .global

var visitorId = VisitorUtil().getId()
var initialContext: ConfidenceStruct = [:]

/**
Initializes the builder with the given credentails.
Expand Down Expand Up @@ -293,6 +302,9 @@ extension Confidence {
return self
}

/**
Sets the initial Context.
*/
public func withContext(initialContext: ConfidenceStruct) -> Builder {
self.initialContext = initialContext
return self
Expand All @@ -307,12 +319,21 @@ extension Confidence {
return self
}

/**
Overrides the Metadata for the Confidence instance. In normal production scenarios, Metadata is
handled automatically by the SDK and this overrides should not be applied.
*/
public func withMetadata(metadata: ConfidenceMetadata) -> Builder {
self.metadata = metadata
return self
}

public func build() -> Confidence {
let options = ConfidenceClientOptions(
credentials: ConfidenceClientCredentials.clientSecret(secret: clientSecret),
region: region)
let metadata = ConfidenceMetadata(
name: "SDK_ID_SWIFT_CONFIDENCE",
let metadata = metadata ?? ConfidenceMetadata(
name: Confidence.sdkId,
version: "0.1.4") // x-release-please-version
let uploader = RemoteConfidenceClient(
options: options,
Expand Down
5 changes: 3 additions & 2 deletions Sources/Confidence/RemoteResolveConfidenceClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import Foundation
public class RemoteConfidenceResolveClient: ConfidenceResolveClient {
private let targetingKey = "targeting_key"
private var options: ConfidenceClientOptions
private let metadata: ConfidenceMetadata

private var httpClient: HttpClient
private var applyOnResolve: Bool

// Internal for testing
internal let metadata: ConfidenceMetadata

init(
options: ConfidenceClientOptions,
session: URLSession? = nil,
Expand Down
53 changes: 45 additions & 8 deletions Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import OpenFeature
import os

struct Metadata: ProviderMetadata {
var name: String?
var name: String? = ConfidenceFeatureProvider.providerId
}

/// The implementation of the Confidence Feature Provider. This implementation allows to pre-cache evaluations.
public class ConfidenceFeatureProvider: FeatureProvider {
public var metadata: ProviderMetadata
public static let providerId: String = "SDK_ID_SWIFT_PROVIDER"

public var metadata: ProviderMetadata = Metadata()
public var hooks: [any Hook] = []
private let lock = UnfairLock()
private let initializationStrategy: InitializationStrategy
Expand All @@ -20,8 +22,47 @@ public class ConfidenceFeatureProvider: FeatureProvider {
private var currentResolveTask: Task<Void, Never>?
private let confidenceFeatureProviderQueue = DispatchQueue(label: "com.provider.queue")

/// Initialize the Provider via a `Confidence` object.
public convenience init(confidence: Confidence, initializationStrategy: InitializationStrategy = .fetchAndActivate) {
/**
Creates the `Confidence` object to be used as init parameter for this Provider.
*/
public static func createConfidence(clientSecret: String) -> ConfidenceForOpenFeature {
return ConfidenceForOpenFeature.init(confidence: Confidence.Builder.init(clientSecret: clientSecret)
.withRegion(region: .global)
.withMetadata(metadata: ConfidenceMetadata.init(
name: providerId,
version: "0.2.1") // x-release-please-version
)
.build())
}

/**
Proxy holder to ensure correct Confidence configuration passed into the Provider's init.
Do not instantiate directly.
*/
public class ConfidenceForOpenFeature {
internal init(confidence: Confidence) {
self.confidence = confidence
}
let confidence: Confidence
}

/**
Initialize the Provider via a `Confidence` object.
The `Confidence` object must be creted via the `createConfidence` function available from this same class,
rather then be instantiated directly via `Confidence.init(...)` as you would if not using the OpenFeature integration.
*/
public convenience init(
confidenceForOF: ConfidenceForOpenFeature,
initializationStrategy: InitializationStrategy = .fetchAndActivate
) {
self.init(confidence: confidenceForOF.confidence, session: nil)
}

// Allows to pass a confidence object with injected configurations for testing
internal convenience init(
confidence: Confidence,
initializationStrategy: InitializationStrategy = .fetchAndActivate
) {
self.init(confidence: confidence, session: nil)
}

Expand All @@ -30,10 +71,6 @@ public class ConfidenceFeatureProvider: FeatureProvider {
initializationStrategy: InitializationStrategy = .fetchAndActivate,
session: URLSession?
) {
let metadata = ConfidenceMetadata(
name: "SDK_ID_SWIFT_PROVIDER",
version: "0.2.1") // x-release-please-version
self.metadata = Metadata(name: metadata.name)
self.initializationStrategy = initializationStrategy
self.confidence = confidence
}
Expand Down
20 changes: 19 additions & 1 deletion Tests/ConfidenceProviderTests/ConfidenceProviderTest.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import Foundation
import ConfidenceProvider
import Combine
import OpenFeature
import XCTest

@testable import Confidence
@testable import ConfidenceProvider

class ConfidenceProviderTest: XCTestCase {
private var readyExpectation = XCTestExpectation(description: "Ready")
Expand Down Expand Up @@ -74,4 +74,22 @@ class ConfidenceProviderTest: XCTestCase {
await fulfillment(of: [errorExpectation], timeout: 5.0)
cancellable.cancel()
}

func testMetadata() async throws {
class FakeClient: ConfidenceResolveClient {
func resolve(ctx: ConfidenceStruct) async throws -> ResolvesResult {
throw ConfidenceError.internalError(message: "test")
}
}

let confidenceForOpenFeature = ConfidenceFeatureProvider.createConfidence(clientSecret: "testSecret")
let provider = ConfidenceFeatureProvider(
confidenceForOF: confidenceForOpenFeature,
initializationStrategy: .activateAndFetchAsync
)
XCTAssertEqual("SDK_ID_SWIFT_PROVIDER", provider.metadata.name)
let remoteClient = confidenceForOpenFeature.confidence.remoteFlagResolver as? RemoteConfidenceResolveClient
XCTAssertEqual("SDK_ID_SWIFT_PROVIDER", remoteClient?.metadata.name)
XCTAssertNotEqual("", remoteClient?.metadata.version)
}
}
7 changes: 7 additions & 0 deletions Tests/ConfidenceTests/ConfidenceTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,13 @@ class ConfidenceTest: XCTestCase {
XCTAssertEqual(error as? ConfidenceError, ConfidenceError.invalidContextInMessage)
}
}

func testConfidenceMetadata() {
let confidence = Confidence.Builder(clientSecret: "").build()
let remoteClient = confidence.remoteFlagResolver as? RemoteConfidenceResolveClient
XCTAssertEqual("SDK_ID_SWIFT_CONFIDENCE", remoteClient?.metadata.name)
XCTAssertNotEqual("", remoteClient?.metadata.version)
}
}

final class DispatchQueueFake: DispatchQueueType {
Expand Down
Loading