Skip to content
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
1 change: 0 additions & 1 deletion Sources/OpenFeature/EvaluationContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,4 @@ import Foundation
public protocol EvaluationContext: Structure {
func getTargetingKey() -> String
func deepCopy() -> EvaluationContext
func setTargetingKey(targetingKey: String)
}
67 changes: 67 additions & 0 deletions Sources/OpenFeature/ImmutableContext.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import Foundation

/// The ``ImmutableContext`` is an ``EvaluationContext`` implementation which is immutable and thread-safe.
/// It provides read-only access to context data and cannot be modified after creation.
public struct ImmutableContext: EvaluationContext {
private let targetingKey: String
private let structure: ImmutableStructure

public init(targetingKey: String = "", structure: ImmutableStructure = ImmutableStructure()) {
self.targetingKey = targetingKey
self.structure = structure
}

public init(attributes: [String: Value]) {
self.init(structure: ImmutableStructure(attributes: attributes))
}

public func deepCopy() -> EvaluationContext {
return ImmutableContext(targetingKey: targetingKey, structure: structure.deepCopy())
}

public func getTargetingKey() -> String {
return targetingKey
}

public func keySet() -> Set<String> {
return structure.keySet()
}

public func getValue(key: String) -> Value? {
return structure.getValue(key: key)
}

public func asMap() -> [String: Value] {
return structure.asMap()
}

public func asObjectMap() -> [String: AnyHashable?] {
return structure.asObjectMap()
}
}

extension ImmutableContext {
public func withTargetingKey(_ targetingKey: String) -> ImmutableContext {
return ImmutableContext(targetingKey: targetingKey, structure: structure)
}

public func withAttribute(key: String, value: Value) -> ImmutableContext {
var newAttributes = structure.asMap()
newAttributes[key] = value
return ImmutableContext(targetingKey: targetingKey, structure: ImmutableStructure(attributes: newAttributes))
}

public func withAttributes(_ attributes: [String: Value]) -> ImmutableContext {
var newAttributes = structure.asMap()
for (key, value) in attributes {
newAttributes[key] = value
}
return ImmutableContext(targetingKey: targetingKey, structure: ImmutableStructure(attributes: newAttributes))
}

public func withoutAttribute(key: String) -> ImmutableContext {
var newAttributes = structure.asMap()
newAttributes.removeValue(forKey: key)
return ImmutableContext(targetingKey: targetingKey, structure: ImmutableStructure(attributes: newAttributes))
}
}
54 changes: 54 additions & 0 deletions Sources/OpenFeature/ImmutableStructure.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import Foundation

/// The ``ImmutableStructure`` is a ``Structure`` implementation which is immutable and thread-safe.
/// It provides read-only access to structured data and cannot be modified after creation.
public class ImmutableStructure: Structure {
private let attributes: [String: Value]

public init(attributes: [String: Value] = [:]) {
self.attributes = attributes
}

public func keySet() -> Set<String> {
return Set(attributes.keys)
}

public func getValue(key: String) -> Value? {
return attributes[key]
}

public func asMap() -> [String: Value] {
return attributes
}

public func asObjectMap() -> [String: AnyHashable?] {
return attributes.mapValues(convertValue)
}

public func deepCopy() -> ImmutableStructure {
return ImmutableStructure(attributes: attributes)
}
}

extension ImmutableStructure {
private func convertValue(value: Value) -> AnyHashable? {
switch value {
case .boolean(let value):
return value
case .string(let value):
return value
case .integer(let value):
return value
case .double(let value):
return value
case .date(let value):
return value
case .list(let value):
return value.map(convertValue)
case .structure(let value):
return value.mapValues(convertValue)
case .null:
return nil
}
}
}
10 changes: 5 additions & 5 deletions Sources/OpenFeature/OpenFeatureAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,11 @@ public class OpenFeatureAPI {
self.providerSubject.send(provider)

if let initialContext = initialContext {
self.evaluationContext = initialContext.deepCopy()
self.evaluationContext = initialContext
}

do {
try await provider.initialize(initialContext: initialContext?.deepCopy())
try await provider.initialize(initialContext: initialContext)
self.providerStatus = .ready
self.eventHandler.send(.ready)
} catch {
Expand All @@ -177,13 +177,13 @@ public class OpenFeatureAPI {

private func updateContext(evaluationContext: EvaluationContext) async {
do {
let oldContext = self.evaluationContext?.deepCopy()
self.evaluationContext = evaluationContext.deepCopy()
let oldContext = self.evaluationContext
self.evaluationContext = evaluationContext
self.providerStatus = .reconciling
eventHandler.send(.reconciling)
try await self.providerSubject.value?.onContextSet(
oldContext: oldContext,
newContext: evaluationContext.deepCopy()
newContext: evaluationContext
)
self.providerStatus = .ready
eventHandler.send(.contextChanged)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import Foundation

@testable import OpenFeature

/// The ``MutableContext`` is an ``EvaluationContext`` implementation which is threadsafe, and whose attributes can
/// be modified after instantiation.
public class MutableContext: EvaluationContext {
Expand Down Expand Up @@ -28,12 +30,6 @@ public class MutableContext: EvaluationContext {
}
}

public func setTargetingKey(targetingKey: String) {
queue.sync {
self.targetingKey = targetingKey
}
}

public func keySet() -> Set<String> {
return queue.sync {
structure.keySet()
Expand All @@ -57,12 +53,18 @@ public class MutableContext: EvaluationContext {
structure.asObjectMap()
}
}

public func setTargetingKey(targetingKey: String) {
queue.sync {
self.targetingKey = targetingKey
}
}
}

extension MutableContext {
@discardableResult
public func add(key: String, value: Value) -> MutableContext {
queue.sync {
_ = queue.sync {
self.structure.add(key: key, value: value)
}
return self
Expand Down
Loading
Loading