Sources/Confidence/RemoteConfidenceClient.swift (127 lines of code) (raw):

import Foundation import os public class RemoteConfidenceClient: ConfidenceClient { private var options: ConfidenceClientOptions private let metadata: ConfidenceMetadata private var httpClient: HttpClient private var baseUrl: String private let debugLogger: DebugLogger? init( options: ConfidenceClientOptions, session: URLSession? = nil, metadata: ConfidenceMetadata, debugLogger: DebugLogger? = nil ) { self.options = options switch options.region { case .global: self.baseUrl = "https://events.confidence.dev/v1/events" case .europe: self.baseUrl = "https://events.eu.confidence.dev/v1/events" case .usa: self.baseUrl = "https://events.us.confidence.dev/v1/events" } self.httpClient = NetworkClient( session: session, baseUrl: baseUrl, timeoutIntervalForRequests: options.timeoutIntervalForRequest ) self.metadata = metadata self.debugLogger = debugLogger } func upload(events: [NetworkEvent]) async throws -> Bool { let timeString = Date.backport.nowISOString let request = PublishEventRequest( events: events.map { event in NetworkEvent( eventDefinition: "eventDefinitions/\(event.eventDefinition)", payload: event.payload, eventTime: event.eventTime) }, clientSecret: options.credentials.getSecret(), sendTime: timeString, sdk: Sdk(id: metadata.name, version: metadata.version) ) do { let result: HttpClientResult<PublishEventResponse> = try await self.httpClient.post(path: ":publish", data: request) switch result { case .success(let successData): let status = successData.response.statusCode let indecesWithError = successData.decodedData?.errors.map { error in error.index } ?? [] let successEventNames = events.enumerated() // Filter only events in batch that have no error reported from backend .filter { index, _ in return !(indecesWithError.contains(index)) } .map { _, event in event.eventDefinition } switch status { case 200: // clean up in case of success debugLogger?.logMessage( message: "Event upload: HTTP status 200. Events: \(successEventNames.joined(separator: ","))", isWarning: false ) return true case 429: // we shouldn't clean up for rate limiting debugLogger?.logMessage( message: "Event upload: HTTP status 429", isWarning: true ) return false case 400...499: // if batch couldn't be processed, we should clean it up debugLogger?.logMessage( message: "Event upload: couldn't process batch", isWarning: true ) return true default: debugLogger?.logMessage( message: "Event upload error. Status code \(status)", isWarning: true ) return false } case .failure(let errorData): throw handleError(error: errorData) } } } private func handleError(error: Error) -> Error { debugLogger?.logMessage( message: "Event upload error: \(error.localizedDescription)", isWarning: true ) if error is ConfidenceError { return error } else { return ConfidenceError.internalError(message: "\(error)") } } } struct PublishEventRequest: Codable { var events: [NetworkEvent] var clientSecret: String var sendTime: String var sdk: Sdk } struct NetworkEvent: Codable { var eventDefinition: String var payload: NetworkStruct var eventTime: String } struct PublishEventResponse: Codable { var errors: [EventError] } struct EventError: Codable { var index: Int var reason: Reason var message: String enum Reason: String, Codable, CaseIterableDefaultsLast { case unspecified = "REASON_UNSPECIFIED" case eventDefinitionNotFound = "EVENT_DEFINITION_NOT_FOUND" case eventSchemaValidationFailed = "EVENT_SCHEMA_VALIDATION_FAILED" case unknown } }