8000 Open enum support · Issue #374 · cjbooms/fabrikt · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Open enum support #374

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintain 8000 ers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
nsprod opened this issue Feb 20, 2025 · 6 comments
Open

Open enum support #374

nsprod opened this issue Feb 20, 2025 · 6 comments

Comments

@nsprod
Copy link
Contributor
nsprod commented Feb 20, 2025

Hey 👋

I'm using an OpenAPI specification that makes use of anyOf to implement the "open enum pattern".

This is what I'm doing:

openapi: 3.0.0
info:
paths:
components:
  schemas:
    SomeObj:
      type: object
      properties:
        open_enum:
          $ref: '#/components/schemas/OpenEnum'
        close_enum:
          $ref: '#/components/schemas/CloseEnum'
    OpenEnum:
      anyOf:
        - type: string
          enum:
            - foo
            - bar
            - baz
        - type: string

    CloseEnum:
      type: string
      enum:
        - foo
        - bar
        - baz

Generated model:

@Serializable
public data class SomeObj(
  @SerialName("open_enum")
  public val openEnum: String? = null,
  @SerialName("close_enum")
  public val closeEnum: CloseEnum? = null,
)

When using kotlinx as serialization library the OpenEnum model is not generated and the property using this schema is of type String. Does fabrikt (+ kotlinx) have support for anyOf?

This pattern is very interesting to force the client (mobile app for eg) to handle unknown cases. It allows more flexibility for the server to add new cases without breaking the API contract or having to version each new addition to the enum

Example taken from the Swift OpenAPI generator documentation: https://swiftpackageindex.com/apple/swift-openapi-generator/1.7.0/documentation/swift-openapi-generator/useful-openapi-patterns#Open-enums-and-oneOfs

@ulrikandersen
Copy link
Collaborator

While it is an interesting pattern I struggle to imagine how this would look in Kotlin's type system.

In TypeScript it would be a union type (type OpenEnum = "foo" | "bar" | "baz" | string;).

Designing how this should look in Kotlin would be the first step to make this work.

@cjbooms
Copy link
Owner
cjbooms commented Feb 20, 2025

In Zalando we make use of x-extensible-enum for this pattern, and there is support in fabrikt for either treating them as enums for server-side, or just treating them as strings for client-side. This allows the server to have type safety for the enums they control, and clients can deserialize as strings so they are prepared for new values as the enum definition evolves server side. It's ok, but not great for client side development.

What has been a proposal before, is generating fallbacks for enums, which I think would be a better addition to fabrikt. See proposed options: #256 (comment)

@nsprod
Copy link
Contributor Author
nsprod commented Feb 21, 2025

While it is an interesting pattern I struggle to imagine how this would look in Kotlin's type system.

In TypeScript it would be a union type (type OpenEnum = "foo" | "bar" | "baz" | string;).

Designing how this should look in Kotlin would be the first step to make this work.

In Swift it generates a struct with two optional properties.

public struct OpenEnum: Codable, Hashable, Sendable {
    /// - Remark: Generated from `#/components/schemas/OpenEnum/value1`.
    @frozen public enum Value1Payload: String, Codable, Hashable, Sendable, CaseIterable {
        case foo = "foo"
        case bar = "bar"
        case baz = "baz"
    }
    /// - Remark: Generated from `#/components/schemas/OpenEnum/value1`.
    public var value1: Components.Schemas.OpenEnum.Value1Payload?
    /// - Remark: Generated from `#/components/schemas/OpenEnum/value2`.
    public var value2: Swift.String?
}

If OpenEnum.value1 is not nil we have a valid enum value otherwise we apply the fallback logic we want.

They also add a check when deserializing the data to make sure one of the two properties is present

public struct OpenEnum: Codable, Hashable, Sendable {
    // ....    
    public init(from decoder: any Decoder) throws {
        var errors: [any Error] = []
        do {
            self.value1 = try decoder.decodeFromSingleValueContainer()
        } catch {
            errors.append(error)
        }
        do {
            self.value2 = try decoder.decodeFromSingleValueContainer()
        } catch {
            errors.append(error)
        }
        try Swift.DecodingError.verifyAtLeastOneSchemaIsNotNil(
            [
                self.value1,
                self.value2
            ],
            type: Self.self,
            codingPath: decoder.codingPath,
            errors: errors
        )
    }
}

@nsprod
Copy link
Contributor Author
nsprod commented Feb 21, 2025

In Zalando we make use of x-extensible-enum for this pattern, and there is support in fabrikt for either treating them as enums for server-side, or just treating them as strings for client-side. This allows the server to have type safety for the enums they control, and clients can deserialize as strings so they are prepared for new values as the enum definition evolves server side. It's ok, but not great for client side development.

What has been a proposal before, is generating fallbacks for enums, which I think would be a better addition to fabrikt. See proposed options: #256 (comment)

Agreed, it would be very useful to have this fallback property added to the OpenAPI specification. But until it gets added to the spec, it would be difficult to use this property because fabrikt would be the only generator to support it :/.

The trick with anyOf is a bit of a stretch, but it relies on a keyword that is already part of the spec.

Do you think it would be complicated to add support for open enums using anyOf to fabrikt? Or is it more the concept of defining open enums using anyOf that doesn't feel right to you?

@cjbooms
Copy link
Owner
cjbooms commented Feb 21, 2025

I don't think it will ever get added to the spec. But fabrikt could generate a fallback based on a global CLI parameter.

@nsprod
Copy link
Contributor Author
nsprod commented Feb 21, 2025

I don't think it will ever get added to the spec. But fabrikt could generate a fallback based on a global CLI parameter.

Yes we could make it work for fabrikt. But for eg, in our case we are trying to use the same OpenAPI spec to generate:

  • the server controllers and DTOs using fabrikt
  • the backend api client using Swift OpenApi generator for iOS
  • the backend api client using OpenApi generator for Android

So our OpenAPI spec needs to use keywords and patterns that are supported by the 3 generators. If fabrikt supports a fallback property for enums but the Swift generator doesn't, for example, we can't use it in our spec.

This is why I feel using anyOf is interesting in this case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants
0