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 9 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)
}
148 changes: 148 additions & 0 deletions Sources/Confidence/ConfidenceValue.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import Foundation

public typealias ConfidenceStruct = [String: ConfidenceValue]

/// Serializable data structure meant for event sending via Confidence
public enum ConfidenceValue: Equatable, Encodable {
case boolean(Bool)
case string(String)
case integer(Int64)
case double(Double)
case date(DateComponents)
case timestamp(Date)
case list([ConfidenceValue])
case structure(ConfidenceStruct)
case null

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

return nil
}

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

return nil
}

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

return nil
}

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

return nil
}

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

return nil
}

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

return nil
}

public func asList() -> [ConfidenceValue]? {
if case let .list(values) = self {
return values
}

return nil
}

public func asStructure() -> [String: ConfidenceValue]? {
if case let .structure(values) = self {
return values
}

return nil
}

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

return false
}
}

extension ConfidenceValue: 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 ConfidenceValue {
public func encode(to encoder: Encoder) throws {
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()
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 .boolean(value)
case .string(let value):
return .string(value)
case .integer(let value):
return .integer(value)
case .double(let value):
return .double(value)
case .date(let value):
return .timestamp(value)
case .list(let values):
return .list(values.compactMap(convertValue))
case .structure(let values):
return .structure(values.compactMapValues(convertValue))
case .null:
return .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
50 changes: 50 additions & 0 deletions Tests/ConfidenceProviderTests/ConfidenceTypeMapperTest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import Foundation
import Confidence
import OpenFeature
import XCTest

@testable import ConfidenceProvider

class ValueConverterTest: XCTestCase {
func testContextConversion() throws {
let openFeatureCtx = MutableContext(
targetingKey: "userid",
structure: MutableStructure(attributes: (["key": .string("value")])))
let confidenceStruct = ConfidenceTypeMapper.from(ctx: openFeatureCtx)
let expected = [
"key": ConfidenceValue.string("value"),
"targeting_key": ConfidenceValue.string("userid")
]
XCTAssertEqual(confidenceStruct, expected)
}

func testValueConversion() throws {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let date = try XCTUnwrap(formatter.date(from: "2022-01-01 12:00:00"))

let openFeatureValue = Value.structure([
"key": .string("value"),
"null": .null,
"bool": .boolean(true),
"int": .integer(3),
"double": .double(4.5),
"date": .date(date),
"list": .list([.integer(3), .integer(5)]),
"structure": .structure(["field1": .string("test"), "field2": .integer(12)]),
])

let confidenceValue = ConfidenceTypeMapper.from(value: openFeatureValue)
let expected = ConfidenceValue.structure([
"key": .string("value"),
"null": .null,
"bool": .boolean(true),
"int": .integer(3),
"double": .double(4.5),
"date": .timestamp(date),
"list": .list([.integer(3), .integer(5)]),
"structure": .structure(["field1": .string("test"), "field2": .integer(12)]),
])
XCTAssertEqual(confidenceValue, expected)
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Foundation
import Confidence
import OpenFeature

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

Expand Down
Loading
Loading