diff --git a/Core/Pixel.swift b/Core/Pixel.swift index fc2e7f3945..be9771b5d2 100644 --- a/Core/Pixel.swift +++ b/Core/Pixel.swift @@ -302,18 +302,37 @@ private extension Pixel.Event { } extension Dictionary where Key == String, Value == String { + mutating func appendErrorPixelParams(error: Error) { let nsError = error as NSError self[PixelParameters.errorCode] = "\(nsError.code)" self[PixelParameters.errorDomain] = nsError.domain - if let underlyingError = nsError.userInfo["NSUnderlyingError"] as? NSError { - self[PixelParameters.underlyingErrorCode] = "\(underlyingError.code)" - self[PixelParameters.underlyingErrorDomain] = underlyingError.domain + let underlyingErrorParameters = underlyingErrorParameters(for: error as NSError) + self.merge(underlyingErrorParameters) { first, _ in first } + } + + private func underlyingErrorParameters(for nsError: NSError, level: Int = 0) -> [String: String] { + if let underlyingError = nsError.userInfo[NSUnderlyingErrorKey] as? NSError { + let errorCodeParameterName = PixelParameters.underlyingErrorCode + (level == 0 ? "" : String(level + 1)) + let errorDomainParameterName = PixelParameters.underlyingErrorDomain + (level == 0 ? "" : String(level + 1)) + + let currentUnderlyingErrorParameters = [ + errorCodeParameterName: "\(underlyingError.code)", + errorDomainParameterName: underlyingError.domain + ] + + let additionalParameters = underlyingErrorParameters(for: underlyingError, level: level + 1) + return currentUnderlyingErrorParameters.merging(additionalParameters) { first, _ in first } } else if let sqlErrorCode = nsError.userInfo["NSSQLiteErrorDomain"] as? NSNumber { - self[PixelParameters.underlyingErrorCode] = "\(sqlErrorCode.intValue)" - self[PixelParameters.underlyingErrorDomain] = "NSSQLiteErrorDomain" + return [ + PixelParameters.underlyingErrorCode: "\(sqlErrorCode.intValue)", + PixelParameters.underlyingErrorDomain: "NSSQLiteErrorDomain" + ] } + + return [:] } + } diff --git a/DuckDuckGoTests/PixelTests.swift b/DuckDuckGoTests/PixelTests.swift index 28a9b29158..acbd4c756b 100644 --- a/DuckDuckGoTests/PixelTests.swift +++ b/DuckDuckGoTests/PixelTests.swift @@ -193,4 +193,38 @@ class PixelTests: XCTestCase { wait(for: [firstFireExpectation, thirdFireExpectation], timeout: Double(debounceInterval + 4)) } + func testWhenDefiningUnderlyingErrorParametersThenNestedErrorsAreIncluded() { + let underlyingError4 = NSError(domain: "underlyingError4", code: 5, userInfo: [:]) + let underlyingError3 = NSError(domain: "underlyingError3", code: 4, userInfo: [NSUnderlyingErrorKey: underlyingError4]) + let underlyingError2 = NSError(domain: "underlyingError2", code: 3, userInfo: [NSUnderlyingErrorKey: underlyingError3]) + let underlyingError1 = NSError(domain: "underlyingError1", code: 2, userInfo: [NSUnderlyingErrorKey: underlyingError2]) + let error = NSError(domain: "error", code: 1, userInfo: [NSUnderlyingErrorKey: underlyingError1]) + + var parameters: [String: String] = [:] + parameters.appendErrorPixelParams(error: error) + + XCTAssertEqual(parameters.count, 10) + XCTAssertEqual(parameters["d"], error.domain) + XCTAssertEqual(parameters["e"], String(error.code)) + XCTAssertEqual(parameters["ud"], underlyingError1.domain) + XCTAssertEqual(parameters["ue"], String(underlyingError1.code)) + XCTAssertEqual(parameters["ud2"], underlyingError2.domain) + XCTAssertEqual(parameters["ue2"], String(underlyingError2.code)) + XCTAssertEqual(parameters["ud3"], underlyingError3.domain) + XCTAssertEqual(parameters["ue3"], String(underlyingError3.code)) + XCTAssertEqual(parameters["ud4"], underlyingError4.domain) + XCTAssertEqual(parameters["ue4"], String(underlyingError4.code)) + } + + func testWhenDefiningUnderlyingErrorParametersAndThereIsNoUnderlyingErrorThenOnlyTopLevelParametersAreIncluded() { + let error = NSError(domain: "error", code: 1, userInfo: [:]) + + var parameters: [String: String] = [:] + parameters.appendErrorPixelParams(error: error) + + XCTAssertEqual(parameters.count, 2) + XCTAssertEqual(parameters["d"], error.domain) + XCTAssertEqual(parameters["e"], String(error.code)) + } + }