Open JeonJiyoun opened 2 months ago
Hi @JeonJiyoun,
in OpenAPI/JSON Schema, enums are closed by default, so the current behavior of swift-openapi-generator is correct.
If in your API you'd like to express open enums, allowing new cases to be added in the future, we documented that common pattern here: https://swiftpackageindex.com/apple/swift-openapi-generator/1.3.0/documentation/swift-openapi-generator/useful-openapi-patterns#Open-enums-and-oneOfs
Thank you for your response. However, I still have a few questions.
When writing the yaml file as described above, should we use buttonTypePayload.value1.none and buttonTypePayload.value2 to utilize the generated code? I’m wondering if we really need to use the unnecessary value1 and value2. The enumUnknownDefaultCase option provided by OpenAPI Generator seems more convenient. Could you please help me understand why the open enum approach is followed in this manner?
Like how OpenAPI Generator has set the default option of enumUnknownDefaultCase to false, wouldn’t it be better to keep this option off by default and allow users to control it?
The problem with the simple approach of allowing open enums unless they were requested by the OpenAPI document is that it breaks decoding when e.g. oneOfs are nested within other oneOfs. The OpenAPI specification is very clear about the prescribed behavior, and our generator tries to follow the specification as closely as possible. While it might be more convenient to diverge, we believe that's not the right approach, as it will create incompatibilities between code written in different languages (e.g. one fails on an unknown enum case, another allows it). That's why the specification exists - to prescribe the correct behavior.
Now, regarding the practical terms of working with an open enum (anyOf with a nested enum), here are some tips:
value1
first - if it's not nil
, you know that the value matches one of the known cases, and you can unwrap the exact case conveniently in a switch
statement, for example. Now, when value1
is present, value2
will also be present, it'll just be the raw string of the received value. But you can safely ignore value2
in that case.value1
is nil, you read value2
and know that it's an unknown case, so treat it accordingly.value1
or value2
. The encoder will use the first non-nil value and skip the rest of the cases, so even if you fill in both value1
and value2
, it doesn't change the runtime behavior.value1
nil and fill in onlly value2
.@czechboy0 Please consider offering the option one more time.
I understand your perspective on using open enums. However, there are several challenges in applying this approach to our current project:
Hi @JeaSungLEE,
I empathize with your situation, but I'm genuinely not sure how to do what you're asking without breaking the generator.
If we generate an "unknown" case for all enums, meaning that the enum never fails to parse (regardless of what string you provided it), once you put such an enum into e.g. a oneOf
(which must match exactly one sub-schema, not more, not less), it will break the decoding logic. Meaning we will parse the incorrect case, and if you then forward that value to e.g. an upstream service, it'll be incorrect data. That goes directly against one of our three main principles.
What you're describing is that a set of teams (possibly unintentionally) decided to use a modified variant of OpenAPI (which then isn't OpenAPI + JSON Schema anymore), and configured all their tools to follow that specification. But any tool that follows the standard OpenAPI specification will fail to parse such data, defeating the point of having a cross-language specification in the first place.
Now, here's a suggestion that hopefully allows you to achieve what you need without having to ask the other teams to change what they're doing: preprocess the OpenAPI document.
You likely have some sort of process or a script that copies the OpenAPI document from an upstream location into the Sources directory of your package, which then allows Swift OpenAPI Generator to generate the Swift code from the OpenAPI document. So you can write a small script (e.g. using OpenAPIKit, the same library that Swift OpenAPI Generator uses to parse the OpenAPI document) that parses the original document, modifies all enums to be wrapped in anyOf (aka turning them into open enums), and writes the document back out. The modified document is then what you'd feed into Swift OpenAPI Generator, and you should get the best of both worlds. I've taken this approach in the past as well several times, usually when the upstream documents had some bugs that could be fixed up automatically.
Hope this unblocks you!
Motivation
The current swift-openapi-generator does not provide a built-in way to handle unknown enum cases gracefully. This can lead to decoding errors when the API introduces new enum cases that the client is not aware of. Currently, developers have to implement custom solutions or modify generated code manually to handle this scenario, which is not ideal for maintainability and can be error-prone.
As I know, the openapi-generator project already has an
enumUnknownDefaultCase
option for Swift code generation, which automatically adds an unknown case to enums and handles decoding gracefully. It would be beneficial if swift-openapi-generator could adopt a similar feature.Proposed solution
Implement an
enumUnknownDefaultCase
option in swift-openapi-generator, similar to the one in openapi-generator. Maybe it can be set at theopenapi-generator-config.yaml
file. When enabled, this option would:unknown
case to all generated enums.init(from:)
for Codable conformance that uses thisunknown
case when an unrecognized value is encountered.Example of desired output: