From f9e36a65dd8ddaeb5f924222fe2b0014e34461f4 Mon Sep 17 00:00:00 2001 From: Manu Date: Wed, 24 May 2023 12:22:27 -0300 Subject: [PATCH] Add additional information when decoding the models --- README.md | 35 +++++++++ .../CoreNetworking/DecodingErrorLogger.swift | 72 +++++++++++++++++++ .../DecodingSuccessLogger.swift | 24 +++++++ Sources/CoreNetworking/HTTPClient.swift | 16 ++++- 4 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 Sources/CoreNetworking/DecodingErrorLogger.swift create mode 100644 Sources/CoreNetworking/DecodingSuccessLogger.swift diff --git a/README.md b/README.md index af172db..481c2f2 100644 --- a/README.md +++ b/README.md @@ -7,3 +7,38 @@ A lightweight networking library. # Usage * Use HTTPClient.shared for executing your requests. * You can change the default JsonDecoder if needed. +* If you want to toggle the verbosity of the prints to the console, change the value of the `logResponses` boolean in HTTPClient. It's `true` by default. + +## Logging + +### Success Decoding Example: + +```swift +✅ ===> JSON Decoding start: +Model: +▿ CoreNetworkingTests.CatFact + - fact: "The biggest wildcat today is the Siberian Tiger. It can be more than 12 feet (3.6 m) long (about the size of a small car) and weigh up to 700 pounds (317 kg)." + - length: 158 +Additional Info: +▿ 1 key/value pair + ▿ (2 elements) + - key: "UTF8 - String" + - value: "{\"fact\":\"The biggest wildcat today is the Siberian Tiger. It can be more than 12 feet (3.6 m) long (about the size of a small car) and weigh up to 700 pounds (317 kg).\",\"length\":158}" +✅ <=== JSON Decoding end. +``` + +### Decoding Issue Example: + +```swift +❌ ===> JSON Decoding issue start: +Error description: Key 'CodingKeys(stringValue: "fact", intValue: nil)' not found +Additional Info: +▿ 2 key/value pairs + ▿ (2 elements) + - key: "Model" + - value: "CatFact" + ▿ (2 elements) + - key: "Context" + - value: "No value associated with key CodingKeys(stringValue: \"fact\", intValue: nil) (\"fact\")." +❌ <=== JSON Decoding issue end. +``` diff --git a/Sources/CoreNetworking/DecodingErrorLogger.swift b/Sources/CoreNetworking/DecodingErrorLogger.swift new file mode 100644 index 0000000..a4161b9 --- /dev/null +++ b/Sources/CoreNetworking/DecodingErrorLogger.swift @@ -0,0 +1,72 @@ +// +// DecodingErrorLogger.swift +// +// +// Created by Manu on 24/05/2023. +// + +import Foundation + +/// Logs error to the console when decoding network models. +struct DecodingErrorLogger { + private let jsonDecoder: JSONDecoder + + init( + jsonDecoder: JSONDecoder = JSONDecoder() + ) { + self.jsonDecoder = jsonDecoder + } + + func logAdditionalDecodingFailureInfo(with error: Error, for type: Decodable.Type) { + var errorDescription: String = "" + var logProperties: [String: String] = [:] + logProperties["Model"] = "\(type)" + + if let decodingError = error as? DecodingError { + switch decodingError { + case let .dataCorrupted(context): + // An indication that the data is corrupted or otherwise invalid. + addContext(context, logProperties: &logProperties) + errorDescription = "Corrupted Data" + case let .keyNotFound(key, context): + // An indication that a keyed decoding container was asked for an entry for the given key, + // but did not contain one. + addContext(context, logProperties: &logProperties) + errorDescription = "Key '\(key)' not found" + case let .valueNotFound(value, context): + // An indication that a non-optional value of the given type was expected, but a null value was found. + addContext(context, logProperties: &logProperties) + errorDescription = "Value '\(value)' not found" + case let .typeMismatch(type, context): + // An indication that a value of the given type could not be decoded because + // it did not match the type of what was found in the encoded payload. + addContext(context, logProperties: &logProperties) + errorDescription = "Type '\(type)' mismatch" + default: () + } + } + + print("❌ ===> JSON Decoding issue start:") + print("Error description: \(errorDescription)") + print("Additional Info:") + dump(logProperties) + print("❌ <=== JSON Decoding issue end.") + print("") + } +} + +private extension DecodingErrorLogger { + /// Add Decoding Error context information to the dictionary. + func addContext( + _ context: DecodingError.Context, + logProperties: inout [String: String] + ) { + logProperties["Context"] = context.debugDescription + if context.codingPath.count > 0 { + logProperties["Coding Path"] = context.codingPath.debugDescription + } + if let underlyingError = context.underlyingError { + logProperties["Underlying Error"] = underlyingError.localizedDescription + } + } +} diff --git a/Sources/CoreNetworking/DecodingSuccessLogger.swift b/Sources/CoreNetworking/DecodingSuccessLogger.swift new file mode 100644 index 0000000..ccb7daa --- /dev/null +++ b/Sources/CoreNetworking/DecodingSuccessLogger.swift @@ -0,0 +1,24 @@ +// +// DecodingSuccessLogger.swift +// +// +// Created by Manu on 24/05/2023. +// + +import Foundation + +/// Logs information to the console when decoding network models. +struct DecodingSuccessLogger { + func logInfo(model: Decodable, data: Data) { + var logProperties: [String: String] = [:] + logProperties["UTF8 - String"] = String(data: data, encoding: .utf8) + + print("✅ ===> JSON Decoding start:") + print("Model:") + dump(model) + print("Additional Info:") + dump(logProperties) + print("✅ <=== JSON Decoding end.") + print("") + } +} diff --git a/Sources/CoreNetworking/HTTPClient.swift b/Sources/CoreNetworking/HTTPClient.swift index 19117e1..3fab377 100644 --- a/Sources/CoreNetworking/HTTPClient.swift +++ b/Sources/CoreNetworking/HTTPClient.swift @@ -6,6 +6,12 @@ public class HTTPClient { public static let shared = HTTPClient() /// Replace the default JSONDecoder if necessary. public var jsonDecoder: JSONDecoder = JSONDecoder() + /// Determines if the HTTPClient will log information about the responses to the console. + public var logResponses: Bool = true + /// Console logger for successful decodes. + private lazy var decodingSuccessLogger = DecodingSuccessLogger() + /// Console logger for decoding issues. + private lazy var decodingErrorLogger = DecodingErrorLogger(jsonDecoder: jsonDecoder) /// Executes a request asynchronously and returns a response, or throws an error. public func execute( @@ -27,13 +33,21 @@ public class HTTPClient { responseType, from: data ) + + if logResponses { + decodingSuccessLogger.logInfo(model: decodedResponse, data: data) + } return decodedResponse } catch { + if logResponses { + decodingErrorLogger.logAdditionalDecodingFailureInfo(with: error, for: responseType) + } + guard let decodingError = error as? DecodingError else { throw Request.RequestError.decode() } - + throw Request.RequestError.decode(decodingError) } case 401: