JARMourato / Kodable

A supercharged extension of Codable
MIT License
83 stars 6 forks source link

Improve Error Forwarding #13

Closed rogerluan closed 2 years ago

rogerluan commented 2 years ago

Hey 👋

I've noticed it's extremely hard to understand errors when you face them. Errors from Decodable provide full context on what went wrong, what was expected and what actually happened, whereas Kodable seems to abstract away that info, effectively leaving the developer clueless.

Example:

▿ Kodable.KodableError.invalidValueForPropertyWithKey
  - invalidValueForPropertyWithKey: "message"

When decoding a complex object, a few questions come to mind:

  1. Where is message? Which model does it belong to?
  2. What was the expected type of message?
  3. Sounds like the incoming JSON contains a key named message, but the value wasn't the expected one. What value did the JSON contain, for the message key?

Now, compare the same scenario with the Decodable error:

▿ DecodingError
  ▿ typeMismatch : 2 elements
    - .0 : Swift.String
    ▿ .1 : Context
      ▿ codingPath : 1 element
        - 0 : CodingKeys(stringValue: "message", intValue: nil)
      - debugDescription : "Expected to decode String but found a number instead."
      - underlyingError : nil

Although it doesn't answer questions 1 and 3, it answers 2 pretty clearly, specially with the debugDescription. Perhaps Kodable could incorporate that information and also help answer question 1 and 3, which would increase developers productivity significantly 😊

What do you think @JARMourato ?

rogerluan commented 2 years ago

Here's the Playground I used to generate that sample response btw:

import Foundation

let json = """
{
    "message": 3
}
""".data(using: .utf8)!

struct MyResponseModel : Decodable {
    var message: String
}

let response = try JSONDecoder().decode(MyResponseModel.self, from: json)
rogerluan commented 2 years ago

To add to that, here's another scenario that is a source of problems:

Imagine you have models like these:

struct MyStruct : Codable {
    var property: MyProperty
}

struct MyProperty : Codable {
    var id: String
    var requiredProperty: String
}

In the scenario above, if we try to parse this:

{
    "my_struct": {
        "id": "123"
    }
}

Kodable would surface this error only:

▿ Kodable.KodableError.invalidValueForPropertyWithKey
  - invalidValueForPropertyWithKey: "my_struct"

Whereas if you catch the error and dump(error) right before re-throwing the invalidValueForPropertyWithKey error, you would actually see this for the dump(error) line:

▿ Kodable.KodableError.nonOptionalValueMissing
  ▿ nonOptionalValueMissing: (1 element)
    - property: "requiredProperty"

This is another reason why I think we should either not catch Codable errors (simply throw them up in the chain), or catch them and pass them alongside the KodableError (an implementation sample of this can be found here for instance: notice how the Swift.Error is embedded/attached to the custom error).

JARMourato commented 2 years ago

Thank you 🙌🏻 @rogerluan for bringing this issue up. I think you're right, let me think about how to make errors more user friendly within Kodable's context, and I'll get back to you this week 🚀

rogerluan commented 2 years ago

Just found another similar issue 😄

There could be an option to enable extra debugging logs so we can be notified/warned when parsing optional fields fail. For instance, there's a struct with an optional property var optional: MyOptionalClass?, but the API is sending optional field with some values in it. But it fails to be parsed. Right now this failure is silent, but it'd be great if we could be warned about it, at least.