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: Implement withContext #89

Merged
merged 4 commits into from
Apr 9, 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
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(
fabriziodemaria marked this conversation as resolved.
Show resolved Hide resolved
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
@@ -1,4 +1,4 @@
// swiftlint:disable type_body_length

Check warning on line 1 in Tests/ConfidenceProviderTests/ConfidenceFeatureProviderTest.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Blanket Disable Command Violation: The disabled 'type_body_length' rule should be re-enabled before the end of the file (blanket_disable_command)
// swiftlint:disable file_length
import Foundation
import Confidence
Expand Down Expand Up @@ -913,7 +913,7 @@
{
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 @@
})
{
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)
}
}
Loading