Skip to content

Commit

Permalink
feat: Implement withContext (#89)
Browse files Browse the repository at this point in the history
* Implement withContext

* Add ConfidenceContextProvider protocol

* Remove context on child instance

* Remove clear from Contextual
  • Loading branch information
fabriziodemaria committed Apr 9, 2024
1 parent 56f8130 commit d0dddee
Show file tree
Hide file tree
Showing 5 changed files with 230 additions and 20 deletions.
39 changes: 29 additions & 10 deletions Sources/Confidence/Confidence.swift
Original file line number Diff line number Diff line change
@@ -1,45 +1,64 @@
import Foundation

public class Confidence: ConfidenceEventSender {
public var context: ConfidenceStruct
private let parent: ConfidenceContextProvider?
private var context: ConfidenceStruct
public let clientSecret: String
public var timeout: TimeInterval
public var region: ConfidenceRegion
public var initializationStrategy: InitializationStrategy
private var removedContextKeys: Set<String> = Set()

init(
required public init(
clientSecret: String,
timeout: TimeInterval,
region: ConfidenceRegion,
initializationStrategy: InitializationStrategy
initializationStrategy: InitializationStrategy,
context: ConfidenceStruct = [:],
parent: ConfidenceEventSender? = nil
) {
self.context = [:]
self.clientSecret = clientSecret
self.timeout = timeout
self.region = region
self.initializationStrategy = initializationStrategy
self.context = context
self.parent = parent
}

// TODO: Implement actual event uploading to the backend
public func send(definition: String, payload: ConfidenceStruct) {
print("Sending: \"\(definition)\".\nMessage: \(payload)\nContext: \(context)")
}


public func getContext() -> ConfidenceStruct {
let parentContext = parent?.getContext() ?? [:]
var reconciledCtx = parentContext.filter {
!removedContextKeys.contains($0.key)
}
self.context.forEach { entry in
reconciledCtx.updateValue(entry.value, forKey: entry.key)
}
return reconciledCtx
}

public func updateContextEntry(key: String, value: ConfidenceValue) {
context[key] = value
}

public func removeContextEntry(key: String) {
context.removeValue(forKey: key)
removedContextKeys.insert(key)
}

public func clearContext() {
context = [:]
}

// TODO: Implement creation of child instances
public func withContext(_ context: ConfidenceStruct) -> Self {
return self
return Self.init(
clientSecret: clientSecret,
timeout: timeout,
region: region,
initializationStrategy: initializationStrategy,
context: context,
parent: self)
}
}

Expand Down
6 changes: 6 additions & 0 deletions Sources/Confidence/ConfidenceContextProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Foundation

/// A Contextual implementer returns the current context
public protocol ConfidenceContextProvider {
func getContext() -> ConfidenceStruct
}
14 changes: 7 additions & 7 deletions Sources/Confidence/Contextual.swift
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import Foundation

/// A Contextual implementer maintains context data and can create child instances
/// A Contextual implementer maintains local context data and can create child instances
/// that can still access their parent's data
public protocol Contextual {
var context: ConfidenceStruct { get set }

/// Each ConfidenceContextProvider returns local data reconciled with parents' data. Local data has precedence
public protocol Contextual: ConfidenceContextProvider {
/// Adds/override entry to local data
func updateContextEntry(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)
func clearContext()
/// Creates a child Contextual instance that still has access
/// to its parent context
/// Creates a child Contextual instance that maintains access to its parent's data
func withContext(_ context: ConfidenceStruct) -> Self
}
Original file line number Diff line number Diff line change
Expand Up @@ -913,7 +913,7 @@ class ConfidenceFeatureProviderTest: XCTestCase {
{
provider.initialize(initialContext: MutableContext(targetingKey: "user1"))
wait(for: [readyExpectation], timeout: 5)
let context = confidence.context
let context = confidence.getContext()
let expected = [
"open_feature": ConfidenceValue(structure: ["targeting_key": ConfidenceValue(string: "user1")])
]
Expand All @@ -936,11 +936,13 @@ class ConfidenceFeatureProviderTest: XCTestCase {
})
{
let ctx1 = MutableContext(targetingKey: "user1")
let ctx2 = MutableContext(targetingKey: "user1", structure: MutableStructure(attributes: ["active": Value.boolean(true)]))
let ctx2 = MutableContext(
targetingKey: "user1",
structure: MutableStructure(attributes: ["active": Value.boolean(true)]))
provider.initialize(initialContext: ctx1)
provider.onContextSet(oldContext: ctx1, newContext: ctx2)
wait(for: [readyExpectation], timeout: 5)
let context = confidence.context
let context = confidence.getContext()
let expected = [
"open_feature": ConfidenceValue(structure: [
"targeting_key": ConfidenceValue(string: "user1"),
Expand Down
183 changes: 183 additions & 0 deletions Tests/ConfidenceTests/ConfidenceTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import Confidence
import XCTest

final class ConfidenceTests: XCTestCase {
func testWithContext() {
let confidenceParent = Confidence.init(
clientSecret: "",
timeout: TimeInterval(),
region: .europe,
initializationStrategy: .activateAndFetchAsync,
context: ["k1": ConfidenceValue(string: "v1")]
)
let confidenceChild: ConfidenceEventSender = confidenceParent.withContext(
["k2": ConfidenceValue(string: "v2")]
)
let expected = [
"k1": ConfidenceValue(string: "v1"),
"k2": ConfidenceValue(string: "v2")
]
XCTAssertEqual(confidenceChild.getContext(), expected)
}

func testWithContextUpdateParent() {
let confidenceParent = Confidence.init(
clientSecret: "",
timeout: TimeInterval(),
region: .europe,
initializationStrategy: .activateAndFetchAsync,
context: ["k1": ConfidenceValue(string: "v1")]
)
let confidenceChild: ConfidenceEventSender = confidenceParent.withContext(
["k2": ConfidenceValue(string: "v2")]
)
confidenceParent.updateContextEntry(
key: "k3",
value: ConfidenceValue(string: "v3"))
let expected = [
"k1": ConfidenceValue(string: "v1"),
"k2": ConfidenceValue(string: "v2"),
"k3": ConfidenceValue(string: "v3"),
]
XCTAssertEqual(confidenceChild.getContext(), expected)
}

func testUpdateLocalContext() {
let confidence = Confidence.init(
clientSecret: "",
timeout: TimeInterval(),
region: .europe,
initializationStrategy: .activateAndFetchAsync,
context: ["k1": ConfidenceValue(string: "v1")]
)
confidence.updateContextEntry(
key: "k1",
value: ConfidenceValue(string: "v3"))
let expected = [
"k1": ConfidenceValue(string: "v3"),
]
XCTAssertEqual(confidence.getContext(), expected)
}

func testUpdateLocalContextWithoutOverride() {
let confidenceParent = Confidence.init(
clientSecret: "",
timeout: TimeInterval(),
region: .europe,
initializationStrategy: .activateAndFetchAsync,
context: ["k1": ConfidenceValue(string: "v1")]
)
let confidenceChild: ConfidenceEventSender = confidenceParent.withContext(
["k2": ConfidenceValue(string: "v2")]
)
confidenceChild.updateContextEntry(
key: "k2",
value: ConfidenceValue(string: "v4"))
let expected = [
"k1": ConfidenceValue(string: "v1"),
"k2": ConfidenceValue(string: "v4"),
]
XCTAssertEqual(confidenceChild.getContext(), expected)
}

func testUpdateParentContextWithOverride() {
let confidenceParent = Confidence.init(
clientSecret: "",
timeout: TimeInterval(),
region: .europe,
initializationStrategy: .activateAndFetchAsync,
context: ["k1": ConfidenceValue(string: "v1")]
)
let confidenceChild: ConfidenceEventSender = confidenceParent.withContext(
["k2": ConfidenceValue(string: "v2")]
)
confidenceParent.updateContextEntry(
key: "k2",
value: ConfidenceValue(string: "v4"))
let expected = [
"k1": ConfidenceValue(string: "v1"),
"k2": ConfidenceValue(string: "v2"),
]
XCTAssertEqual(confidenceChild.getContext(), expected)
}

func testRemoveContextEntry() {
let confidence = Confidence.init(
clientSecret: "",
timeout: TimeInterval(),
region: .europe,
initializationStrategy: .activateAndFetchAsync,
context: [
"k1": ConfidenceValue(string: "v1"),
"k2": ConfidenceValue(string: "v2")
]
)
confidence.removeContextEntry(key: "k2")
let expected = [
"k1": ConfidenceValue(string: "v1")
]
XCTAssertEqual(confidence.getContext(), expected)
}

func testRemoveContextEntryFromParent() {
let confidenceParent = Confidence.init(
clientSecret: "",
timeout: TimeInterval(),
region: .europe,
initializationStrategy: .activateAndFetchAsync,
context: ["k1": ConfidenceValue(string: "v1")]
)
let confidenceChild: ConfidenceEventSender = confidenceParent.withContext(
["k2": ConfidenceValue(string: "v2")]
)
confidenceChild.removeContextEntry(key: "k1")
let expected = [
"k2": ConfidenceValue(string: "v2")
]
XCTAssertEqual(confidenceChild.getContext(), expected)
}

func testRemoveContextEntryFromParentAndChild() {
let confidenceParent = Confidence.init(
clientSecret: "",
timeout: TimeInterval(),
region: .europe,
initializationStrategy: .activateAndFetchAsync,
context: ["k1": ConfidenceValue(string: "v1")]
)
let confidenceChild: ConfidenceEventSender = confidenceParent.withContext(
[
"k2": ConfidenceValue(string: "v2"),
"k1": ConfidenceValue(string: "v3"),
]
)
confidenceChild.removeContextEntry(key: "k1")
let expected = [
"k2": ConfidenceValue(string: "v2")
]
XCTAssertEqual(confidenceChild.getContext(), expected)
}

func testRemoveContextEntryFromParentAndChildThenUpdate() {
let confidenceParent = Confidence.init(
clientSecret: "",
timeout: TimeInterval(),
region: .europe,
initializationStrategy: .activateAndFetchAsync,
context: ["k1": ConfidenceValue(string: "v1")]
)
let confidenceChild: ConfidenceEventSender = confidenceParent.withContext(
[
"k2": ConfidenceValue(string: "v2"),
"k1": ConfidenceValue(string: "v3"),
]
)
confidenceChild.removeContextEntry(key: "k1")
confidenceChild.updateContextEntry(key: "k1", value: ConfidenceValue(string: "v4"))
let expected = [
"k2": ConfidenceValue(string: "v2"),
"k1": ConfidenceValue(string: "v4"),
]
XCTAssertEqual(confidenceChild.getContext(), expected)
}
}

0 comments on commit d0dddee

Please sign in to comment.