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

Add additional information when decoding the models #3

Merged
merged 1 commit into from
May 24, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
```
72 changes: 72 additions & 0 deletions Sources/CoreNetworking/DecodingErrorLogger.swift
Original file line number Diff line number Diff line change
@@ -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
}
}
}
24 changes: 24 additions & 0 deletions Sources/CoreNetworking/DecodingSuccessLogger.swift
Original file line number Diff line number Diff line change
@@ -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("")
}
}
16 changes: 15 additions & 1 deletion Sources/CoreNetworking/HTTPClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<Response: Decodable>(
Expand All @@ -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:
Expand Down