From 01dda0868abba7ae456914c1b7a1e4c1117834e1 Mon Sep 17 00:00:00 2001 From: Fabrizio Demaria Date: Thu, 2 May 2024 14:18:07 +0200 Subject: [PATCH] docs: Documentation for public protocols/constructors (#111) * Remove unused timeout parameter * docs: In-code docs for public protocols/constructors --- Sources/Confidence/Confidence.swift | 46 ++++++++++--------- .../ConfidenceContextProvider.swift | 4 +- .../Confidence/ConfidenceEventSender.swift | 8 +++- Sources/Confidence/Contextual.swift | 28 +++++++---- .../Confidence/InitializationStrategy.swift | 14 +++++- .../ConfidenceFeatureProvider.swift | 18 +++----- Tests/ConfidenceTests/ConfidenceTests.swift | 41 +++++++++++------ .../RemoteConfidenceClientTests.swift | 4 +- 8 files changed, 103 insertions(+), 60 deletions(-) diff --git a/Sources/Confidence/Confidence.swift b/Sources/Confidence/Confidence.swift index 2b66b028..93ca47f5 100644 --- a/Sources/Confidence/Confidence.swift +++ b/Sources/Confidence/Confidence.swift @@ -3,7 +3,6 @@ import Combine public class Confidence: ConfidenceEventSender { public let clientSecret: String - public var timeout: TimeInterval public var region: ConfidenceRegion public var initializationStrategy: InitializationStrategy private let parent: ConfidenceContextProvider? @@ -12,9 +11,9 @@ public class Confidence: ConfidenceEventSender { private var removedContextKeys: Set = Set() private let confidenceQueue = DispatchQueue(label: "com.confidence.queue") + /// Internal, the hosting app should use Confidence.Builder instead required init( clientSecret: String, - timeout: TimeInterval, region: ConfidenceRegion, eventSenderEngine: EventSenderEngine, initializationStrategy: InitializationStrategy, @@ -24,7 +23,6 @@ public class Confidence: ConfidenceEventSender { ) { self.eventSenderEngine = eventSenderEngine self.clientSecret = clientSecret - self.timeout = timeout self.region = region self.initializationStrategy = initializationStrategy self.contextFlow.value = context @@ -34,6 +32,11 @@ public class Confidence: ConfidenceEventSender { } } + public func track(eventName: String, message: ConfidenceStruct) { + eventSenderEngine.emit(eventName: eventName, message: message, context: getContext()) + } + + /// Allows to observe changes in the Context, not meant to be used directly by the hosting app public func contextChanges() -> AnyPublisher { return contextFlow .dropFirst() @@ -41,10 +44,6 @@ public class Confidence: ConfidenceEventSender { .eraseToAnyPublisher() } - public func track(eventName: String, message: ConfidenceStruct) { - eventSenderEngine.emit(eventName: eventName, message: message, context: getContext()) - } - private func withLock(callback: @escaping (Confidence) -> Void) { confidenceQueue.sync { [weak self] in guard let self = self else { @@ -73,7 +72,7 @@ public class Confidence: ConfidenceEventSender { } } - public func putContext(context: ConfidenceStruct) { + private func putContext(context: ConfidenceStruct) { withLock { confidence in var map = confidence.contextFlow.value for entry in context { @@ -83,11 +82,12 @@ public class Confidence: ConfidenceEventSender { } } - public func putContext(context: ConfidenceStruct, removedKeys: [String] = []) { + public func putContext(context: ConfidenceStruct, removeKeys: [String] = []) { withLock { confidence in var map = confidence.contextFlow.value - for removedKey in removedKeys { + for removedKey in removeKeys { map.removeValue(forKey: removedKey) + confidence.removedContextKeys.insert(removedKey) } for entry in context { map.updateValue(entry.value, forKey: entry.key) @@ -96,7 +96,7 @@ public class Confidence: ConfidenceEventSender { } } - public func removeContextEntry(key: String) { + public func removeKey(key: String) { withLock { confidence in var map = confidence.contextFlow.value map.removeValue(forKey: key) @@ -108,7 +108,6 @@ public class Confidence: ConfidenceEventSender { public func withContext(_ context: ConfidenceStruct) -> Self { return Self.init( clientSecret: clientSecret, - timeout: timeout, region: region, eventSenderEngine: eventSenderEngine, initializationStrategy: initializationStrategy, @@ -117,15 +116,17 @@ public class Confidence: ConfidenceEventSender { } } +// MARK: Builder + extension Confidence { public class Builder { let clientSecret: String - var timeout: TimeInterval = 10.0 var region: ConfidenceRegion = .global var initializationStrategy: InitializationStrategy = .fetchAndActivate let eventStorage: EventStorage var visitorId: String? + /// Initializes the builder with the given credentails. public init(clientSecret: String) { self.clientSecret = clientSecret do { @@ -135,22 +136,27 @@ extension Confidence { } } - public func withTimeout(timeout: TimeInterval) -> Builder { - self.timeout = timeout - return self - } - - + /** + Sets the region for the network request to the Confidence backend. + The default is `global` and the requests are automatically routed to the closest server. + */ public func withRegion(region: ConfidenceRegion) -> Builder { self.region = region return self } + /** + Flag resolve configuration related to how to refresh flags at startup + */ public func withInitializationstrategy(initializationStrategy: InitializationStrategy) -> Builder { self.initializationStrategy = initializationStrategy return self } + /** + The SDK attaches a unique identifier to the Context, which is persisted across + restarts of the App but re-generated on every new install + */ public func withVisitorId() -> Builder { self.visitorId = VisitorUtil().getId() return self @@ -160,7 +166,6 @@ extension Confidence { let uploader = RemoteConfidenceClient( options: ConfidenceClientOptions( credentials: ConfidenceClientCredentials.clientSecret(secret: clientSecret), - timeout: timeout, region: region), metadata: ConfidenceMetadata( name: "SDK_ID_SWIFT_CONFIDENCE", @@ -173,7 +178,6 @@ extension Confidence { flushPolicies: [SizeFlushPolicy(batchSize: 1)]) return Confidence( clientSecret: clientSecret, - timeout: timeout, region: region, eventSenderEngine: eventSenderEngine, initializationStrategy: initializationStrategy, diff --git a/Sources/Confidence/ConfidenceContextProvider.swift b/Sources/Confidence/ConfidenceContextProvider.swift index b9e5424a..33366dcc 100644 --- a/Sources/Confidence/ConfidenceContextProvider.swift +++ b/Sources/Confidence/ConfidenceContextProvider.swift @@ -1,6 +1,8 @@ import Foundation -/// A Contextual implementer returns the current context +/** +A Contextual implementer returns the current context +*/ public protocol ConfidenceContextProvider { func getContext() -> ConfidenceStruct } diff --git a/Sources/Confidence/ConfidenceEventSender.swift b/Sources/Confidence/ConfidenceEventSender.swift index aedc8797..2bb1c5e1 100644 --- a/Sources/Confidence/ConfidenceEventSender.swift +++ b/Sources/Confidence/ConfidenceEventSender.swift @@ -1,6 +1,12 @@ import Foundation -/// Sends events to Confidence. Contextual data is appended to each event +/** +Sends events to Confidence. Contextual data is appended to each event +*/ public protocol ConfidenceEventSender: Contextual { + /** + Upon return, the event has been correctly stored and will be emitted to the backend + according to the configured flushing logic + */ func track(eventName: String, message: ConfidenceStruct) } diff --git a/Sources/Confidence/Contextual.swift b/Sources/Confidence/Contextual.swift index 52fe6b73..4bfb9781 100644 --- a/Sources/Confidence/Contextual.swift +++ b/Sources/Confidence/Contextual.swift @@ -1,14 +1,26 @@ import Foundation -/// A Contextual implementer maintains local context data and can create child instances -/// that can still access their parent's data -/// Each ConfidenceContextProvider returns local data reconciled with parents' data. Local data has precedence +/** +A Contextual implementer maintains local context data and can create child instances +that can still access their parent's data +Each ConfidenceContextProvider returns local data reconciled with parents' data. Local data has precedence +*/ public protocol Contextual: ConfidenceContextProvider { - /// Adds/override entry to local data + /** + Adds/override entry to local data + */ func putContext(key: String, value: ConfidenceValue) - /// Removes entry from local data - /// It hides entries with this key from parents' data (without modifying parents' data) - func removeContextEntry(key: String) - /// Creates a child Contextual instance that maintains access to its parent's data + /** + Removes entry from local data + It hides entries with this key from parents' data (without modifying parents' data) + */ + func removeKey(key: String) + /** + Perform `putContext` and multiple `removeKey` at once + */ + func putContext(context: ConfidenceStruct, removeKeys: [String]) + /** + Creates a child Contextual instance that maintains access to its parent's data + */ func withContext(_ context: ConfidenceStruct) -> Self } diff --git a/Sources/Confidence/InitializationStrategy.swift b/Sources/Confidence/InitializationStrategy.swift index 8dfbc0c0..154812b1 100644 --- a/Sources/Confidence/InitializationStrategy.swift +++ b/Sources/Confidence/InitializationStrategy.swift @@ -1,6 +1,16 @@ import Foundation -/// Flag resolve configuration related to how to refresh flags at startup +/** +Flag resolve configuration related to how to refresh flags at startup +*/ public enum InitializationStrategy { - case fetchAndActivate, activateAndFetchAsync + /** + Flags are resolved before the values are accessible by the application + */ + case fetchAndActivate + /** + Values in the cache are accessible right away, an asynchronous resolve + updates the cache for a future session + */ + case activateAndFetchAsync } diff --git a/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift b/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift index fc1ea53e..e644f70b 100644 --- a/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift +++ b/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift @@ -59,7 +59,6 @@ public class ConfidenceFeatureProvider: FeatureProvider { 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()) @@ -86,7 +85,7 @@ public class ConfidenceFeatureProvider: FeatureProvider { return } - self.updateConfidenceContext(context: initialContext) + confidence?.putContext(context: ConfidenceTypeMapper.from(ctx: initialContext)) if self.initializationStrategy == .activateAndFetchAsync { eventHandler.send(.ready) } @@ -158,12 +157,13 @@ public class ConfidenceFeatureProvider: FeatureProvider { return } - var removedKeys: [String] = [] + var removeKeys: [String] = [] if let oldContext = oldContext { - removedKeys = Array(oldContext.asMap().filter { key, _ in !newContext.asMap().keys.contains(key) }.keys) + removeKeys = Array(oldContext.asMap().filter { key, _ in !newContext.asMap().keys.contains(key) }.keys) } - - self.updateConfidenceContext(context: newContext, removedKeys: removedKeys) + confidence?.putContext( + context: ConfidenceTypeMapper.from(ctx: newContext), + removeKeys: removeKeys) } private func startListentingForContextChanges() { @@ -185,10 +185,6 @@ public class ConfidenceFeatureProvider: FeatureProvider { .store(in: &cancellables) } - private func updateConfidenceContext(context: EvaluationContext, removedKeys: [String] = []) { - confidence?.putContext(context: ConfidenceTypeMapper.from(ctx: context), removedKeys: removedKeys) - } - public func getBooleanEvaluation(key: String, defaultValue: Bool, context: EvaluationContext?) throws -> OpenFeature.ProviderEvaluation { @@ -484,7 +480,7 @@ extension ConfidenceFeatureProvider { var initializationStrategy: InitializationStrategy = .fetchAndActivate var confidence: Confidence? - /// DEPRECATED + /// DEPRECATED: initialise with a `Confidence` object instead. /// Initializes the builder with the given credentails. /// /// OpenFeatureAPI.shared.setProvider(provider: diff --git a/Tests/ConfidenceTests/ConfidenceTests.swift b/Tests/ConfidenceTests/ConfidenceTests.swift index 9281a3ce..844f9064 100644 --- a/Tests/ConfidenceTests/ConfidenceTests.swift +++ b/Tests/ConfidenceTests/ConfidenceTests.swift @@ -5,7 +5,6 @@ final class ConfidenceTests: XCTestCase { func testWithContext() { let confidenceParent = Confidence.init( clientSecret: "", - timeout: TimeInterval(), region: .europe, eventSenderEngine: EventSenderEngineMock(), initializationStrategy: .activateAndFetchAsync, @@ -24,7 +23,6 @@ final class ConfidenceTests: XCTestCase { func testWithContextUpdateParent() { let confidenceParent = Confidence.init( clientSecret: "", - timeout: TimeInterval(), region: .europe, eventSenderEngine: EventSenderEngineMock(), initializationStrategy: .activateAndFetchAsync, @@ -45,10 +43,32 @@ final class ConfidenceTests: XCTestCase { XCTAssertEqual(confidenceChild.getContext(), expected) } + func testWithContextUpdateParentRemoveKeys() { + let confidenceParent = Confidence.init( + clientSecret: "", + region: .europe, + eventSenderEngine: EventSenderEngineMock(), + initializationStrategy: .activateAndFetchAsync, + context: ["k1": ConfidenceValue(string: "v1")], + parent: nil + ) + let confidenceChild: ConfidenceEventSender = confidenceParent.withContext( + ["k2": ConfidenceValue(string: "v2")] + ) + confidenceChild.putContext( + context: ["k3": ConfidenceValue(string: "v3")], + removeKeys: ["k1"] + ) + let expected = [ + "k2": ConfidenceValue(string: "v2"), + "k3": ConfidenceValue(string: "v3"), + ] + XCTAssertEqual(confidenceChild.getContext(), expected) + } + func testUpdateLocalContext() { let confidence = Confidence.init( clientSecret: "", - timeout: TimeInterval(), region: .europe, eventSenderEngine: EventSenderEngineMock(), initializationStrategy: .activateAndFetchAsync, @@ -67,7 +87,6 @@ final class ConfidenceTests: XCTestCase { func testUpdateLocalContextWithoutOverride() { let confidenceParent = Confidence.init( clientSecret: "", - timeout: TimeInterval(), region: .europe, eventSenderEngine: EventSenderEngineMock(), initializationStrategy: .activateAndFetchAsync, @@ -90,7 +109,6 @@ final class ConfidenceTests: XCTestCase { func testUpdateParentContextWithOverride() { let confidenceParent = Confidence.init( clientSecret: "", - timeout: TimeInterval(), region: .europe, eventSenderEngine: EventSenderEngineMock(), initializationStrategy: .activateAndFetchAsync, @@ -113,7 +131,6 @@ final class ConfidenceTests: XCTestCase { func testRemoveContextEntry() { let confidence = Confidence.init( clientSecret: "", - timeout: TimeInterval(), region: .europe, eventSenderEngine: EventSenderEngineMock(), initializationStrategy: .activateAndFetchAsync, @@ -123,7 +140,7 @@ final class ConfidenceTests: XCTestCase { ], parent: nil ) - confidence.removeContextEntry(key: "k2") + confidence.removeKey(key: "k2") let expected = [ "k1": ConfidenceValue(string: "v1") ] @@ -133,7 +150,6 @@ final class ConfidenceTests: XCTestCase { func testRemoveContextEntryFromParent() { let confidenceParent = Confidence.init( clientSecret: "", - timeout: TimeInterval(), region: .europe, eventSenderEngine: EventSenderEngineMock(), initializationStrategy: .activateAndFetchAsync, @@ -143,7 +159,7 @@ final class ConfidenceTests: XCTestCase { let confidenceChild: ConfidenceEventSender = confidenceParent.withContext( ["k2": ConfidenceValue(string: "v2")] ) - confidenceChild.removeContextEntry(key: "k1") + confidenceChild.removeKey(key: "k1") let expected = [ "k2": ConfidenceValue(string: "v2") ] @@ -153,7 +169,6 @@ final class ConfidenceTests: XCTestCase { func testRemoveContextEntryFromParentAndChild() { let confidenceParent = Confidence.init( clientSecret: "", - timeout: TimeInterval(), region: .europe, eventSenderEngine: EventSenderEngineMock(), initializationStrategy: .activateAndFetchAsync, @@ -166,7 +181,7 @@ final class ConfidenceTests: XCTestCase { "k1": ConfidenceValue(string: "v3"), ] ) - confidenceChild.removeContextEntry(key: "k1") + confidenceChild.removeKey(key: "k1") let expected = [ "k2": ConfidenceValue(string: "v2") ] @@ -176,7 +191,6 @@ final class ConfidenceTests: XCTestCase { func testRemoveContextEntryFromParentAndChildThenUpdate() { let confidenceParent = Confidence.init( clientSecret: "", - timeout: TimeInterval(), region: .europe, eventSenderEngine: EventSenderEngineMock(), initializationStrategy: .activateAndFetchAsync, @@ -189,7 +203,7 @@ final class ConfidenceTests: XCTestCase { "k1": ConfidenceValue(string: "v3"), ] ) - confidenceChild.removeContextEntry(key: "k1") + confidenceChild.removeKey(key: "k1") confidenceChild.putContext(key: "k1", value: ConfidenceValue(string: "v4")) let expected = [ "k2": ConfidenceValue(string: "v2"), @@ -201,7 +215,6 @@ final class ConfidenceTests: XCTestCase { func testVisitorId() { let confidence = Confidence.init( clientSecret: "", - timeout: TimeInterval(), region: .europe, eventSenderEngine: EventSenderEngineMock(), initializationStrategy: .activateAndFetchAsync, diff --git a/Tests/ConfidenceTests/RemoteConfidenceClientTests.swift b/Tests/ConfidenceTests/RemoteConfidenceClientTests.swift index c17aace4..c50e1202 100644 --- a/Tests/ConfidenceTests/RemoteConfidenceClientTests.swift +++ b/Tests/ConfidenceTests/RemoteConfidenceClientTests.swift @@ -27,7 +27,7 @@ class RemoteConfidenceClientTest: XCTestCase { XCTAssertTrue(processed) } - func testUploadEmptyeventsDoesntThrow() async throws { + func testUploadEmptyEventsDoesntThrow() async throws { let client = RemoteConfidenceClient( options: ConfidenceClientOptions( credentials: ConfidenceClientCredentials.clientSecret(secret: "")), @@ -56,7 +56,7 @@ class RemoteConfidenceClientTest: XCTestCase { XCTAssertTrue(processed) } - func testNMalformedResponseThrows() async throws { + func testMalformedResponseThrows() async throws { MockedClientURLProtocol.mockedOperation = .malformedResponse let client = RemoteConfidenceClient( options: ConfidenceClientOptions(