swift-openapi-generator: Handling of enum unknown cases

Hello,

Unless I’m mistaken, Swift OpenAPI Generator does not generate code that is able to handle unknown enum cases in responses.

The scenario is very classic:

  1. The openapi.yml file defines an enum with a limited number of known cases.
  2. Code is generated against this specification.
  3. The server outputs a value that is not listed in the known cases.
  4. How is this handled by the generated code?

As far as I know, the sanction is immediate: a DecodingError.dataCorrupted is thrown. It is impossible for the client to decide how unrecognized cases should be handled.


Of course, adding a new enum case is, strictly speaking, a breaking change. So a server that adds an undocumented enum case is wrong.

A certain amount of pragmatism can help sorting things out, though. Not all server apis have reached “version 1.0”. It is not unreasonable for the client to request the ability to decide what to do with unrecognized cases, just in case the server evolves in an unexpected way:

  • Sometimes the whole response has to be discarded.
  • Sometimes, the client can ignore some values.
  • Sometimes, the client performs some kind of fallback.
  • Always, the client is able to decide what to do.

The OpenAPI format does not allow to distinguish between “frozen” and “not frozen” enums. It’s impossible for the author of the OpenAPI spec to express the intent. When all enums are considered “frozen”, the only way to avoid errors from overly strict OpenAPI generators is to discard all non-frozen enums and replace them with the type of their raw value. But this creates new problems on its own:

  • Some $ref are used in both requests and responses. When a $ref uses a raw value in place of a proper enum, responses are decoded without error (👍), but the requests look like they accepts any value, when it is not the case (👎).
  • Assuming that the user of Swift OpenAPI Generator can modify openapi.yml at will is overly optimistic, as already discussed at length in #299 (not mentioning that #303 sets a precedent where Swift OpenAPI Generator is able to adjust its behavior without any modification to openapi.yml).

Finally, a Google search reveals that most OpenAPI generators repositories have an issue similar to this one. Most support unknown cases. Apple/swift-protobuf supports “unrecognized” cases as well.


Looking a what swift-protobuf does, I find this examples in the tests:

// SwiftProtobug.Enum is found at
// <https://github.com/apple/swift-protobuf/blob/0d922f013e900fccea5d083dedc691202ea8d399/Sources/SwiftProtobuf/Enum.swift#L19>
// public protocol Enum: RawRepresentable, Hashable, CaseIterable, Sendable { ... }
enum SwiftProtoTesting_UnknownEnum_Proto3_MyEnum: SwiftProtobuf.Enum {
  typealias RawValue = Int
  case foo // = 0
  case bar // = 1
  case baz // = 2
  case UNRECOGNIZED(Int)

  init() {
    self = .foo
  }

  init?(rawValue: Int) {
    switch rawValue {
    case 0: self = .foo
    case 1: self = .bar
    case 2: self = .baz
    default: self = .UNRECOGNIZED(rawValue)
    }
  }

  var rawValue: Int {
    switch self {
    case .foo: return 0
    case .bar: return 1
    case .baz: return 2
    case .UNRECOGNIZED(let i): return i
    }
  }

  // The compiler won't synthesize support with the UNRECOGNIZED case.
  static let allCases: [SwiftProtoTesting_UnknownEnum_Proto3_MyEnum] = [
    .foo,
    .bar,
    .baz,
  ]
}

I don’t like the init() at all (maybe it fits ProtoBuf), but otherwise I find it pretty much satisfying.

I like the CaseIterable support that is a nice touch.

I also like the big scary UNRECOGNIZED that helps the user avoid producing this case when feeding requests.

About this issue

  • Original URL
  • State: closed
  • Created 7 months ago
  • Comments: 15

Most upvoted comments

Regarding excluding some schemas from generation, please file a separate issue for it and describe the use case more there. It was briefly considered when the filtering feature was being added, but we wanted to wait for folks asking for it. So this might help motivate adding it.

The other idea, generate: open_enum, is less likely to make it through, as it’d invalidate all our tests that assume enums are closed, so while I understand the appeal, I think it’s much more likely that we go the path of #383 instead, and let folks customize in their own apps, without complicating the generator itself.

And the open enum pattern is pretty well know, for example see OpenAI’s API here, using this to either provide a well-known model name, or a freeform string: https://github.com/openai/openai-openapi/blob/master/openapi.yaml#L5307-L5322