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: Add ConfidenceValue #84

Merged
merged 18 commits into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from 11 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
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ extension ConfidenceDemoApp {
let ctx = MutableContext(targetingKey: UUID.init().uuidString, structure: MutableStructure())
Task {
await OpenFeatureAPI.shared.setProviderAndWait(provider: provider, initialContext: ctx)
confidence.send(eventName: "my_event")
confidence.send(definition: "my_event", payload: ConfidenceStruct())
}
}
}
8 changes: 7 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ let package = Package(
dependencies: [
"ConfidenceProvider",
]
)
),
.testTarget(
name: "ConfidenceTests",
dependencies: [
"Confidence"
]
),
]
)
10 changes: 5 additions & 5 deletions Sources/Confidence/Confidence.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

public class Confidence: ConfidenceEventSender {
public var context: [String: String]
public var context: ConfidenceStruct
public let clientSecret: String
public var timeout: TimeInterval
public var region: ConfidenceRegion
Expand All @@ -21,11 +21,11 @@ public class Confidence: ConfidenceEventSender {
}

// TODO: Implement actual event uploading to the backend
public func send(eventName: String) {
print("Sending \(eventName) - Targeting key: \(context["targeting_key"] ?? "UNKNOWN")")
public func send(definition: String, payload: ConfidenceStruct) {
print("Sending \(definition) - Targeting key: \(payload)")
}

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

Expand All @@ -38,7 +38,7 @@ public class Confidence: ConfidenceEventSender {
}

// TODO: Implement creation of child instances
public func withContext(_ context: [String: String]) -> Self {
public func withContext(_ context: ConfidenceStruct) -> Self {
return self
}
}
Expand Down
3 changes: 1 addition & 2 deletions Sources/Confidence/ConfidenceEventSender.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import Foundation

/// Sends events to Confidence. Contextual data is appended to each event
// TODO: Add functions for sending events with payload
public protocol ConfidenceEventSender: Contextual {
func send(eventName: String)
func send(definition: String, payload: ConfidenceStruct)
}
231 changes: 231 additions & 0 deletions Sources/Confidence/ConfidenceValue.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
import Foundation

public typealias ConfidenceStruct = [String: ConfidenceValue]

public class ConfidenceValue: Equatable, Encodable {
private let value: ConfidenceValueInternal

public init(boolean: Bool) {
self.value = .boolean(boolean)
}

public init(string: String) {
self.value = .string(string)
}

public init(integer: Int64) {
self.value = .integer(integer)
}

public init(double: Double) {
self.value = .double(double)
}

public init(date: DateComponents) {
nicklasl marked this conversation as resolved.
Show resolved Hide resolved
self.value = .date(date)
}

public init(timestamp: Date) {
self.value = .timestamp(timestamp)
}
nicklasl marked this conversation as resolved.
Show resolved Hide resolved

// TODO: Handle heterogeneous types
public init(valueList: [ConfidenceValue]) {
fabriziodemaria marked this conversation as resolved.
Show resolved Hide resolved
self.value = .list(valueList.map { $0.value })
}

public init(boolList: [Bool]) {
self.value = .list(boolList.map { .boolean($0) })
}

public init(stringList: [String]) {
self.value = .list(stringList.map { .string($0) })
}


public init(integerList: [Int64]) {
self.value = .list(integerList.map { .integer($0) })
}

public init(doubleList: [Double]) {
self.value = .list(doubleList.map { .double($0) })
}


public init(dateComponentList: [DateComponents]) {
self.value = .list(dateComponentList.map { .date($0) })
}

public init(dateList: [Date]) {
self.value = .list(dateList.map { .timestamp($0) })
}

public init(structure: [String: ConfidenceValue]) {
self.value = .structure(structure.mapValues { $0.value })
}

public init(null: ()) {
self.value = .null
}

private init(valueInternal: ConfidenceValueInternal) {
self.value = valueInternal
}

public func asBoolean() -> Bool? {
if case let .boolean(bool) = value {
return bool
}

return nil
}

public func asString() -> String? {
if case let .string(string) = value {
return string
}

return nil
}

public func asInteger() -> Int64? {
if case let .integer(int64) = value {
return int64
}

return nil
}

public func asDouble() -> Double? {
if case let .double(double) = value {
return double
}

return nil
}

public func asDateComponents() -> DateComponents? {
if case let .date(dateComponents) = value {
return dateComponents
}

return nil
}

public func asDate() -> Date? {
if case let .timestamp(date) = value {
return date
}

return nil
}

public func asList() -> [ConfidenceValue]? {
if case let .list(values) = value {
return values.map { i in ConfidenceValue(valueInternal: i) }
}

return nil
}

public func asStructure() -> [String: ConfidenceValue]? {
if case let .structure(values) = value {
return values.mapValues { ConfidenceValue(valueInternal: $0) }
}

return nil
}

public func isNull() -> Bool {
if case .null = value {
return true
}

return false
}

public static func == (lhs: ConfidenceValue, rhs: ConfidenceValue) -> Bool {
lhs.value == rhs.value
}
}

extension ConfidenceValue {
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(value)
}
}


/// Serializable data structure meant for event sending via Confidence
private enum ConfidenceValueInternal: Equatable, Encodable {
fabriziodemaria marked this conversation as resolved.
Show resolved Hide resolved
case boolean(Bool)
case string(String)
case integer(Int64)
case double(Double)
case date(DateComponents)
case timestamp(Date)
case list([ConfidenceValueInternal])
case structure([String: ConfidenceValueInternal])
case null
}

extension ConfidenceValueInternal: CustomStringConvertible {
public var description: String {
switch self {
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 .timestamp(let value):
return "\(value)"
case .list(value: let values):
return "\(values.map { value in value.description })"
case .structure(value: let values):
return "\(values.mapValues { value in value.description })"
case .null:
return "null"
}
}
}

extension ConfidenceValueInternal {
public func encode(to encoder: Encoder) throws {
nicklasl marked this conversation as resolved.
Show resolved Hide resolved
var container = encoder.singleValueContainer()

switch self {
case .null:
try container.encodeNil()
case .integer(let integer):
try container.encode(integer)
case .double(let double):
try container.encode(double)
case .string(let string):
try container.encode(string)
case .boolean(let boolean):
try container.encode(boolean)
case .date(let dateComponents):
let dateFormatter = DateFormatter()
fabriziodemaria marked this conversation as resolved.
Show resolved Hide resolved
dateFormatter.dateFormat = "dd-MM-yyyy"
if let date = Calendar.current.date(from: dateComponents) {
try container.encode(dateFormatter.string(from: date))
} else {
throw ConfidenceError.internalError(message: "Could not create date from components")
}
case .timestamp(let date):
let isoFormatter = ISO8601DateFormatter()
let formattedDate = isoFormatter.string(from: date)
try container.encode(formattedDate)
case .structure(let structure):
try container.encode(structure)
case .list(let list):
try container.encode(list)
}
}
}
7 changes: 3 additions & 4 deletions Sources/Confidence/Contextual.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@ import Foundation
/// A Contextual implementer maintains context data and can create child instances
/// that can still access their parent's data
public protocol Contextual {
// TODO: Add complex type to the context Dictionary
var context: [String: String] { get set }
var context: ConfidenceStruct { get set }

func updateContextEntry(key: String, value: String)
func updateContextEntry(key: String, value: ConfidenceValue)
func removeContextEntry(key: String)
func clearContext()
/// Creates a child Contextual instance that still has access
/// to its parent context
func withContext(_ context: [String: String]) -> Self
func withContext(_ context: ConfidenceStruct) -> Self
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Foundation
import Confidence
import OpenFeature
import os

Expand Down
1 change: 1 addition & 0 deletions Sources/ConfidenceProvider/Cache/DefaultStorage.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Foundation
import Confidence

public class DefaultStorage: Storage {
private let storageQueue = DispatchQueue(label: "com.confidence.storage")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Combine
import Foundation
import Combine
import Confidence
import OpenFeature
import os

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Foundation
import Confidence
import OpenFeature

public class LocalStorageResolver: Resolver {
Expand Down
36 changes: 36 additions & 0 deletions Sources/ConfidenceProvider/Utils/ConfidenceTypeMapper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import Foundation
import Confidence
import OpenFeature

public enum ConfidenceTypeMapper {
static func from(value: Value) -> ConfidenceValue {
return convertValue(value)
}

static func from(ctx: EvaluationContext) -> ConfidenceStruct {
var ctxMap = ctx.asMap()
ctxMap["targeting_key"] = .string(ctx.getTargetingKey())
return ctxMap.compactMapValues(convertValue)
}

static private func convertValue(_ value: Value) -> ConfidenceValue {
switch value {
case .boolean(let value):
return ConfidenceValue(boolean: value)
case .string(let value):
return ConfidenceValue(string: value)
case .integer(let value):
return ConfidenceValue(integer: value)
case .double(let value):
return ConfidenceValue(double: value)
case .date(let value):
return ConfidenceValue(timestamp: value)
case .list(let values):
return ConfidenceValue(valueList: values.compactMap(convertValue))
case .structure(let values):
return ConfidenceValue(structure: values.compactMapValues(convertValue))
case .null:
return ConfidenceValue(null: ())
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Foundation
import Confidence
import OpenFeature

extension HTTPURLResponse {
Expand Down
Loading
Loading