iOS/Sources/Shared/ClientEvents/Model/ClientEvent.swift
2025-02-24 17:24:10 +01:00

146 lines
4.8 KiB
Swift

import Foundation
import GRDB
public struct ClientEvent: Codable, FetchableRecord, PersistableRecord {
public var id: String = UUID().uuidString
public var text: String = ""
public var type: EventType = .unknown
public var jsonPayload: [String: AnyCodable] = [:]
public var date: Date = Current.date()
public enum EventType: String, Codable, CaseIterable {
case notification
case serviceCall
case locationUpdate
case networkRequest
case settings
case database
case backgroundOperation
case unknown
}
public var jsonPayloadDescription: String? {
jsonData().flatMap { String(data: $0, encoding: .utf8) }
}
public init(
text: String,
type: EventType,
payload: [String: Any]? = [:],
date: Date = Current.date()
) {
self.text = text
self.type = type
self.jsonPayload = ClientEvent.convertToAnyCodable(payload ?? [:])
}
private func jsonData() -> Data? {
do {
let writeOptions: JSONSerialization.WritingOptions = [.prettyPrinted, .withoutEscapingSlashes]
return try JSONSerialization.data(withJSONObject: jsonPayloadJSONObject(), options: writeOptions)
} catch {
Current.Log.error("Error serializing json payload: \(error)")
return nil
}
}
static func convertToAnyCodable(_ dictionary: [String: Any]) -> [String: AnyCodable] {
var newDictionary: [String: AnyCodable] = [:]
for (key, value) in dictionary {
if let value = value as? [String: Any] {
newDictionary[key] = AnyCodable(value)
} else {
newDictionary[key] = AnyCodable(value)
}
}
return newDictionary
}
public func jsonPayloadJSONObject() -> [String: Any] {
var newDictionary: [String: Any] = [:]
for (key, value) in jsonPayload {
newDictionary[key] = value.value
}
return newDictionary
}
}
public struct AnyCodable: Codable {
public let value: Any
public init(_ value: Any) {
self.value = value
}
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let bool = try? container.decode(Bool.self) {
self.value = bool
} else if let int = try? container.decode(Int.self) {
self.value = int
} else if let double = try? container.decode(Double.self) {
self.value = double
} else if let string = try? container.decode(String.self) {
self.value = string
} else if let array = try? container.decode([AnyCodable].self) {
self.value = array.map(\.value)
} else if let dictionary = try? container.decode([String: AnyCodable].self) {
self.value = dictionary.mapValues { $0.value }
} else {
throw DecodingError.dataCorruptedError(
in: container,
debugDescription: "AnyCodable value cannot be decoded"
)
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch value {
case let bool as Bool:
try container.encode(bool)
case let int as Int:
try container.encode(int)
case let double as Double:
try container.encode(double)
case let string as String:
try container.encode(string)
case let array as [Any]:
try container.encode(array.map(AnyCodable.init))
case let dictionary as [String: Any]:
try container.encode(dictionary.mapValues(AnyCodable.init))
default:
throw EncodingError.invalidValue(
value,
EncodingError.Context(
codingPath: container.codingPath,
debugDescription: "AnyCodable value cannot be encoded"
)
)
}
}
}
public extension ClientEvent.EventType {
var displayText: String {
switch self {
case .notification:
return L10n.ClientEvents.EventType.notification
case .locationUpdate:
return L10n.ClientEvents.EventType.locationUpdate
case .serviceCall:
return L10n.ClientEvents.EventType.serviceCall
case .networkRequest:
return L10n.ClientEvents.EventType.networkRequest
case .unknown:
return L10n.ClientEvents.EventType.unknown
case .settings:
return L10n.ClientEvents.EventType.settings
case .database:
return L10n.ClientEvents.EventType.database
case .backgroundOperation:
return L10n.ClientEvents.EventType.backgroundOperation
}
}
}