Closed dankinsoid closed 9 months ago
Thanks for the PR. I'm going to need to give this a proper read through (and figure out why Bitrise is failing to build this with a mysterious error); I am heading out for a trip and leaving the laptop at home, so I wanted to let you know I won't be able to get back to this PR for a week or two, but it'll be on my todo list as soon as I return from vacation.
I see that OpenAPIKit's relatively old Swift version support is causing some headache. Hard to code in an old style of Swift and forget the new stuff you've learned since 5.1.
I do think I want to retain Swift 5.1 support for version 3 of OpenAPIKit. I think that version 4, which could come much faster on version 3's tail than the current major version bump, would be a nice place to specifically focus on bumping the Swift version requirement of the library. Doing it separately and explicitly as a goal of the version bump will motivate making broad changes to the codebase instead of having older parts of the codebase use older Swift coding styles than newer parts of the codebase.
Anyway, I see you're already working on adapting to the old version of Swift, I just figured I would give some thoughts on the subject of supporting Swift 5.1.
@mattpolzin Hello! I have some troubles with code coverage. I got 97.95% yesterday, I added some tests and then I got 97.92% 🤔 Maybe exclude tests themself from codecov? Also I notice, codecov takes into account even private API, imho it shouldn't.
@dankinsoid some parts of OpenAPIKit's test framework involves test helpers that I've on occasion discovered are not being called because I've failed to test a situation I intended to test so including tests in the coverage numbers has proven useful to me.
If you feel good about test coverage I'll take a closer look at this PR and consider adjusting the requirement downward. In my experience decoder/encoder implementations are definitely not areas I've cared to cover to as high of a percentage by their nature.
Those older compiler versions were not nearly as good at type inference; I've noticed that on a few occasions recently.
The first thing I am doing with the OpenAPIKit v4 release branch is dropping support for anything prior to Swift 5.8; that'll be nice!
I was curious about taking a different tactic to provide some comparison; whereas you've created a new Encoder which I think is the "rebuild from the ground up" tactic, I wanted to see how much could be accomplished by, roughly, "bolting something on."
I've managed something that passes the two test cases you gave in your initial description for this PR, but I would love if you had time to take a look and let me know if I have missed the point -- it seems very possible that I've fixed the narrow scope of the two examples you gave but not really managed to fix the bigger problems they represent.
My work is on this branch so you can pull it down and try to break it if you don't mind the exploration: https://github.com/mattpolzin/OpenAPIKit/compare/anycodable-correctness-exploration
Hi @mattpolzin, yes, your fixes are much more shorter. I checked them and found some edge case, this test fails:
func test_encodedDecodedQuality() throws {
let value = URL(string: "https://www.google.com")
let anyCodable = AnyCodable(value)
let encodedValue = try JSONEncoder().encode(value)
let encodedCodable = try JSONEncoder().encode(anyCodable)
let decodedFromValue = try JSONDecoder().decode(AnyCodable.self, from: encodedValue)
XCTAssertEqual(anyCodable, decodedFromValue)
let decodedFromAnyCodable = try JSONDecoder().decode(AnyCodable.self, from: encodedCodable)
XCTAssertEqual(anyCodable, decodedFromAnyCodable)
}
I beleve it fails because URL, Decimal (and maybe Data, Date) encoding/decoding is customized in JSONEncoder/JSONDecoder, PropertyListEncoder/PropertyListDecoder. Also this may be due to the fact that the types list you consider in init(from decoder: Decoder) method are different from the types list you consider in encode(to encoder: Encoder) I'll check how to fix it
I've merged and released OpenAPIKit 3.0.0. That created some merge conflicts with this PR, though there's no fundamental incompatibility.
I've merged and released OpenAPIKit 3.0.0. That created some merge conflicts with this PR, though there's no fundamental incompatibility.
Hi, I'll check conflicts, thank you
I've merged and released OpenAPIKit 3.0.0. That created some merge conflicts with this PR, though there's no fundamental incompatibility.
Hi! I've updated the branch
Heads up that your tests are failing on here (of course that wasn't clear until I accepted GitHub's request to run them again, a very annoying GitHub "feature").
I don't think you should spend the time to get the tests working again, though. After doing some more hard thinking, I have decided that I am very unlikely to want to incorporate another Encoder
implementation into OpenAPIKit. It's quite a bit of maintenance overhead to add new Encoders, but there's also the fact that I think both your Encoder based implementation and my* non-Encoder based implementation have their own limitations.
*well, it's really mostly Flight School's implementation that I borrowed.
The limitations of my approach were uncovered by you (and I truly appreciate that): If you take an AnyCodable
and you encode it and then decode it then it is not guaranteed to be equal to the original AnyCodable
. The limitation of your approach (I believe) is that two AnyCodables
created with different Swift types are equal sometimes even when the types they came from are different (e.g. String
and URL
). In different situations, either one of these is more important than the other. You could make the limitation of your Encodable
based approach less surprising by not supporting e.g. .init(Encodable)
, but you break backwards compatibility (not a deal breaker) and you stop supporting use cases where it is beneficial to consider AnyCodables
based on more than just the foundational types as distinct (arguably more of a deal breaker).
You brought to my attention more than just the encode -> decode equality limitation, though. I've patched all of the other issues you brought up (support for arbitrary Encodable
types and equality of nested structures within AnyCodable
) in a newly merged branch against v4
of OpenAPIKit here. That branch also adds a fair bit of README documentation around the limitations of AnyCodable
and suggests a relatively simple function that essentially adopts the equality behavior you would like to see by encoding and then checking for equality -- in other words, where your approach encodes on init of AnyCodable
, this function would support encoding specifically when you care about encode-equality and it does so using any existing Encoder (or a new one, if desired).
I want to stress my gratitude for you bringing these issues up; several of them were bugs I was very happy to patch and one of them was a more hard limitation on the approach I took which made for important additional documentation. I couldn't have made the decision I did without having seen all of your hard work in this branch, either. 🙏
@mattpolzin, thank you very much. I'll try to adapt my VaporToOpenAPI for your v4 library, and if the equating works fine, I'll be happy with it.
Problem Statement
The current `AnyCodable implementation, which relies on type casting, is inconsistent and has some hidden bugs.
Inconsistency Details
The
init(from: Decoder)
implementation casts to[String: Any?]
, while theEquatable
implementation casts to[String: AnyCodable]
. This causes an inconsistency.Examples
Example 1
This simple test fails:
This test tries to decode JSON into two instances of
AnyCodable
and compare them for equality. It fails because of the aforementioned inconsistency.Example 2
This test fails because only a few native types can be encoded:
Proposed Solution
I propose a new
AnyCodable
enum that is both consistent and backward compatible.Testing
The new implementation can be tested by updated
AnyCodableTests