Repository: parse-community/Parse-Swift (swift)

Issue:
Better error logging for Invalid Struct
### New Feature / Enhancement Checklist
<!--
    Check all of the following boxes [x] before submitting your issue.
    Click the "Preview" tab for better readability.
    Thanks for contributing to Parse Swift!
-->

- [x] I am not disclosing a [vulnerability](https://github.com/parse-community/Parse-Swift/security/policy).
- [x] I am not just asking a [question](https://github.com/parse-community/.github/blob/main/SUPPORT.md).
- [x] I have searched through [existing issues](https://github.com/parse-community/Parse-Swift/issues?q=is%3Aissue).

### Current Limitation
<!-- Which current limitation is the feature or enhancement addressing? -->

At the moment, if you define a struct which doesn't match your server payload, you get no meaningful errors that help you isolate the problem.

### Feature / Enhancement Description
<!-- What is the concept of the functionality and how should it be implemented? -->

Add a `.invalidStruct` error with information as to where the struct doesn't match the server payload

### Example Use Case
<!-- What is an example use case in steps (1. / 2. / 3. / etc.) that describes the functionality? -->

Current response:

"Error decoding parse-server response: Optional(<NSHTTPURLResponse: 0x6000019d1100> { URL: http://localhost:1337/1/classes/GameScore } {

Better response:

"Invalid struct: No value associated with key CodingKeys(stringValue: \"score\", intValue: nil) (\"score\")."

### Alternatives / Workarounds
<!-- Which alternatives or workarounds exist currently? -->

-

### 3rd Party References
<!-- Have you seen a similar functionality provided somewhere else? -->

-

Pull request description:
### New Pull Request Checklist
<!--
    Please check the following boxes [x] before submitting your issue.
    Click the "Preview" tab for better readability.
    Thanks for contributing to Parse Platform!
-->

- [x] I am not disclosing a [vulnerability](https://github.com/parse-community/Parse-Swift/security/policy).
- [x] I am creating this PR in reference to an [issue](https://github.com/parse-community/Parse-Swift/issues?q=is%3Aissue).

### Issue Description
<!-- Add a brief description of the issue this PR solves. -->

Invalid structs are really hard to isolate the cause of the problem, especially with larger classes

Related issue: #237

### Approach
<!-- Add a description of the approach in this PR. -->

Checks if error code is `4865` and returns error .invalidStruct if so

### TODOs before merging
<!--
    Add TODOs that need to be completed before merging this PR.
    Delete TODOs that do not apply to this PR.
-->

- [x] Add tests
- [x] Add entry to changelog

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3dcedc664..09758fa0c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@
 
 __Improvements__
 - Added `operation` for `set` and `forceSet`, used for single key updates ([#248](https://github.com/parse-community/Parse-Swift/pull/248)), thanks to [Daniel Blyth](https://github.com/dblythy) and [Corey Baker](https://github.com/cbaker6).
+- Add more detail to invalid struct errors ([#238](https://github.com/parse-community/Parse-Swift/pull/238)), thanks to [Daniel Blyth](https://github.com/dblythy).
 
 ### 1.10.4
 [Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.10.3...1.10.4)
diff --git a/ParseSwift.podtemplate b/ParseSwift.podtemplate
index c2e752dca..03382c518 100644
--- a/ParseSwift.podtemplate
+++ b/ParseSwift.podtemplate
@@ -14,7 +14,7 @@ Pod::Spec.new do |s|
   s.osx.deployment_target = "10.13"
   s.tvos.deployment_target = "12.0"
   s.watchos.deployment_target = "5.0"
-  s.swift_versions = ['5.1', '5.2', '5.3', '5.4']
+  s.swift_versions = ['5.1', '5.2', '5.3', '5.4', '5.5']
   s.source_files = "Sources/ParseSwift/**/*.swift"
   s.license = {
     :type => "MIT",
diff --git a/Sources/ParseSwift/API/URLSession+extensions.swift b/Sources/ParseSwift/API/URLSession+extensions.swift
index 9be9eb90c..3df1e2f0a 100755
--- a/Sources/ParseSwift/API/URLSession+extensions.swift
+++ b/Sources/ParseSwift/API/URLSession+extensions.swift
@@ -66,6 +66,11 @@ extension URLSession {
                           let json = try? JSONSerialization
                             .data(withJSONObject: responseData,
                               options: .prettyPrinted) else {
+                        let nsError = error as NSError
+                        if nsError.code == 4865,
+                          let description = nsError.userInfo["NSDebugDescription"] {
+                            return .failure(ParseError(code: .unknownError, message: "Invalid struct: \(description)"))
+                        }
                         return .failure(ParseError(code: .unknownError,
                                                    // swiftlint:disable:next line_length
                                                    message: "Error decoding parse-server response: \(response) with error: \(error.localizedDescription) Format: \(String(describing: String(data: responseData, encoding: .utf8)))"))
diff --git a/Sources/ParseSwift/Types/ParseError.swift b/Sources/ParseSwift/Types/ParseError.swift
index 97d1eee96..e0e0844de 100644
--- a/Sources/ParseSwift/Types/ParseError.swift
+++ b/Sources/ParseSwift/Types/ParseError.swift
@@ -407,6 +407,13 @@ extension ParseError: CustomStringConvertible {
     }
 }
 
+// MARK: LocalizedError
+extension ParseError: LocalizedError {
+    public var errorDescription: String? {
+        debugDescription
+    }
+}
+
 // MARK: Compare Errors
 public extension Error {
 


Test patch:
diff --git a/Tests/ParseSwiftTests/ParseErrorTests.swift b/Tests/ParseSwiftTests/ParseErrorTests.swift
index f091fd102..b229d1edf 100644
--- a/Tests/ParseSwiftTests/ParseErrorTests.swift
+++ b/Tests/ParseSwiftTests/ParseErrorTests.swift
@@ -46,6 +46,7 @@ class ParseErrorTests: XCTestCase {
         XCTAssertEqual(decoded.message, message)
         XCTAssertEqual(decoded.debugDescription, "ParseError code=\(code) error=\(message)")
         XCTAssertEqual(decoded.description, "ParseError code=\(code) error=\(message)")
+        XCTAssertEqual(decoded.errorDescription, "ParseError code=\(code) error=\(message)")
     }
 
     func testEncodeOther() throws {
diff --git a/Tests/ParseSwiftTests/ParseQueryTests.swift b/Tests/ParseSwiftTests/ParseQueryTests.swift
index 832d20106..85fcb22fd 100644
--- a/Tests/ParseSwiftTests/ParseQueryTests.swift
+++ b/Tests/ParseSwiftTests/ParseQueryTests.swift
@@ -726,6 +726,20 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length
         }
 
         let query = GameScore.query()
+        do {
+            _ = try query.first(options: [])
+            XCTFail("Should have thrown error")
+        } catch {
+            guard let error = error as? ParseError else {
+                XCTFail("Should have casted as ParseError")
+                return
+            }
+            #if !os(Linux) && !os(Android)
+            // swiftlint:disable:next line_length
+            XCTAssertEqual(error.message, "Invalid struct: No value associated with key CodingKeys(stringValue: \"score\", intValue: nil) (\"score\").")
+            XCTAssertEqual(error.code, .unknownError)
+            #endif
+        }
         XCTAssertThrowsError(try query.first(options: []))
     }