apollographql / apollo-kotlin

:rocket:  A strongly-typed, caching GraphQL client for the JVM, Android, and Kotlin multiplatform.
https://www.apollographql.com/docs/kotlin
MIT License
3.76k stars 655 forks source link

[compiler] Add Enum.KNOWN__ as an intermediary interface #6248

Closed martinbonnin closed 2 weeks ago

martinbonnin commented 2 weeks ago

Closes https://github.com/apollographql/apollo-kotlin/issues/6243

Generated code

At a high level, this change introduces an intermediate KNOWN__ interface, symmetrical with UNKNOWN__:

public sealed interface Color {
  public val rawValue: String

  public object BLUEBERRY : KNOWN__ {
    override val rawValue: String = "BLUEBERRY"
  }

  public object CHERRY : KNOWN__ {
    override val rawValue: String = "CHERRY"
  }

  public object CANDY : KNOWN__ {
    override val rawValue: String = "CANDY"
  }

  /**
   * An enum value that is known at build time.
   */
  @Suppress("ClassName")
  public sealed interface KNOWN__ : Color {
    override val rawValue: String
  }

  /**
   * An enum value that isn't known at build time.
   */
  @Suppress("ClassName")
  public class UNKNOWN__ @ApolloPrivateEnumConstructor constructor(
    override val rawValue: String,
  ) : Color 
}

Usage

This allows unwrapping an unknown value to a known one to provide a "default":

  /**
   * Turns a maybe unknown color value into a known one
   */
  private fun Color.orDefault(): Color.KNOWN__ = when (this) {
    is Color.UNKNOWN__ -> Color.CANDY
    is Color.KNOWN__ -> this
  }

This way, callers don't have to switch on the unknown case:

    when (color.orDefault()) {
      Color.BLUEBERRY -> TODO()
      Color.CANDY -> TODO()
      Color.CHERRY -> TODO()
      // no need to check for unknown here
    }

Compatibility

I contemplated adding another option but ultimately decided to reuse sealedClassesAsEnumMatching.

This is a source-compatible change but a binary breaking change because of the changing of UNKNOWN__ from interface to class.

See https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.4:

If C is not an interface, interface method resolution throws an IncompatibleClassChangeError. 

You are impacted if you are shipping a library that exposes a generated SomeEnum.UNKNOWN__ in its public API.

I'm hoping that this pattern is not too widespread. If it is, and you're reading this, past me apologize in advance 😬 .

svc-apollo-docs commented 2 weeks ago

❌ Docs Preview Failed

Error

Error: Failed to fetch GitHub source: Not Found