Apollo-android: Custom code for enums / open enums

Created on 14 Mar 2020  ·  8Comments  ·  Source: apollographql/apollo-android

Is your feature request related to a problem? Please describe.
I have some finite set of values for which graphql enums and java enums work perfectly, but in some instances i have enums that are finite but just for now, new values are likely to be added.
And in the consumer i rather keep the original value and not i don't want the caller to end up with "$UNKNOWN", specially when the value is going to be stored or returned without further inspection to an upstream service.

Describe the solution you'd like
One option is to declare them as Scalars, but that's not ideal, i the constants are still useful.

I'd like the ability to tag some schema enums with a directive and map those graphql enums not to java enums but to regular java classes.

i'd like the ability to turn something this:

@openEnum
enum UserType {
  CUSTOMER,
  EMPLOYEE
}

into something like this:

public class UserType {
  private static final CUSTOMER("CUSTOMER");

  private static final EMPLOYEE("EMPLOYEE");

  private final String name;

  UserType(String name) {
    this.name = name;
  }

  public String name() {
    return name;
  }

  public static UserType[] values() {
    return new UserType[] {
      CUSTOMER,
      EMPLOYEE
    }
  }

  public static UserType valueOf(String name) {
    for (UserType name : values()) {
      if (enumValue.name.equals(name)) {
        return enumValue;
      }
    }
    return new UserType(name);
  }
}
compiler Feature

Most helpful comment

In terms of Kotlin models, we can change to generated sealed class instead of enums, for example, instead of:

enum class Episode(
  val rawValue: String
) {
  NEWHOPE("NEWHOPE"),
  EMPIRE("EMPIRE"),
  JEDI("jedi"),
  UNKNOWN__("UNKNOWN__");

We can generate this:

sealed class Episode(
  val rawValue: String
) {
  object Newhope: Episode("NEWHOPE")
  object Empire: Episode("EMPIRE")
  object Jedi: Episode("jedi")
  class Unknown__ private constructor (rawValue: String) : Episode("rawValue")
}

Unfortunately as for the Java it's more complicated, there is no such concept as sealed class and pattern matching on classes.

We can create a new configuration as @enriquedacostacambio suggested to generated classes instead of enums for both Java / Kotlin targets.

If everyone agrees I can make it happen, it shouldn't take too much.

All 8 comments

I think we should re-evaluate the suggested solution since that would require a lot of maintenance in Apollo Compiler.

But I agree that we also sometimes need to access the rawValue of the new enums provided. With the current approach, the values are just lost. $UNKNOWN("$UNKNOWN");

In your case, having them as scalars really makes sense since you are not going to process them and just pass them.

Hi,
sorry for joining here
I would like to if possible to disable puting $UNKNOWN("$UNKNOWN") from enum generally.
We have generated enum classes (probably generated by older version) without unknown value and we would like to stay by this way.

thanks
brpalo

@metalpalo in that scenario how would you handle the situation where your backend adds a new value to the enum? How would already released old versions work in that case?

Hi
Firstly – is this value $UNKNOWN("$UNKNOWN") required by apollo runtime itself in order to handle unknown enum values on properly. ?
We only read data a it would be just fine for us if we received null values in case of unknown values.
We read data from one backend e.g.
Enum1Type {
VALUE1
VALUE2
$UNKNOWN
}
And we send data to another backend and thus map this enum to corresponding type via mapstruct.
Enum2Type {
VALUE1
VALUE2
}
But these new UNKNOWN values causes a problem for mapping, of cource we can ignore unknown value by this ...
@ValueMapping(source = MappingConstants.ANY_REMAINING, target = MappingConstants.NULL)

Also for update queries – would it be a problems if null value instead of UNKNOWN would be sent?
What is purpose of usage $UNKNOWN values, mainly for update queries?

Mapping them to null is not possible in case the property is marked as NotNull in the schema.

In terms of Kotlin models, we can change to generated sealed class instead of enums, for example, instead of:

enum class Episode(
  val rawValue: String
) {
  NEWHOPE("NEWHOPE"),
  EMPIRE("EMPIRE"),
  JEDI("jedi"),
  UNKNOWN__("UNKNOWN__");

We can generate this:

sealed class Episode(
  val rawValue: String
) {
  object Newhope: Episode("NEWHOPE")
  object Empire: Episode("EMPIRE")
  object Jedi: Episode("jedi")
  class Unknown__ private constructor (rawValue: String) : Episode("rawValue")
}

Unfortunately as for the Java it's more complicated, there is no such concept as sealed class and pattern matching on classes.

We can create a new configuration as @enriquedacostacambio suggested to generated classes instead of enums for both Java / Kotlin targets.

If everyone agrees I can make it happen, it shouldn't take too much.

Sealed classes makes a lot of sense. That's a good way to keep the value.

I'm skeptical about Java one. I wouldn't use it since the usage will not be nice. I.e cannot be used with switch statements. But since it will be opt-in, I would say why not?

Are there any updates about this issue?
It would be very nice have this feature ❤️
We always need to implement workarounds on the backend side affecting also other clients (iOS, frontend-web) and leaking an implementation detail of the client library, which is not great.
Also, it looks like iOS doesn't have this issue since the compiler uses enum with associated values, which should be the equivalent of Kotlin sealed class.
Thanks!

Was this page helpful?
0 / 5 - 0 ratings