[java] Map unknown values of nullable enums to the unknown default case

ND
5d ago
+64-7

With enumUnknownDefaultCase=true, fromValue returns UNKNOWN_DEFAULT_OPEN_API for an unknown value only when the enum is not nullable. For a nullable enum it returns null, so a value added to the API after the client was generated is read as "no value", and the caller can't tell it apart from an explicit null.

This PR makes fromValue of nullable enums return null for a null value and UNKNOWN_DEFAULT_OPEN_API for any other unknown value, as non-nullable enums already do. Without enumUnknownDefaultCase the generated code is unchanged.

The change is the same in each Java client template that has this fromValue: modelEnum, modelInnerEnum, libraries/native/modelEnum, libraries/okhttp-gson/modelEnum and modelInnerEnum, and libraries/microprofile/enumClass.

Spec

openapi: 3.0.3
info: { title: repro, version: 1.0.0 }
paths: {}
components:
  schemas:
    Mode:
      type: string
      nullable: true
      enum: [auto, manual]
    Pet:
      type: object
      properties:
        kind: { type: string, nullable: true, enum: [cat, dog] }
        mode: { $ref: '#/components/schemas/Mode' }

-g java --library jersey2 --additional-properties=enumUnknownDefaultCase=true

Before

public static Mode fromValue(String value) {
  for (Mode b : Mode.values()) {
    if (b.value.equals(value)) {
      return b;
    }
  }
  return null;
}

After

public static Mode fromValue(String value) {
  for (Mode b : Mode.values()) {
    if (b.value.equals(value)) {
      return b;
    }
  }
  if (value == null) {
    return null;
  }
  return UNKNOWN_DEFAULT_OPEN_API;
}

Deserializing with Jackson (new ObjectMapper().readValue(json, Mode.class)):

JSONBeforeAfter
"auto"AUTOAUTO
"scheduled"nullUNKNOWN_DEFAULT_OPEN_API
nullnullnull

Testing

  • ./mvnw -DskipTests -pl modules/openapi-generator-cli -am package
  • ./bin/generate-samples.sh bin/configs/java-*.yaml (140 configs): no changes, because no Java sample config sets enumUnknownDefaultCase. For the spec above without the option, master and this branch generate identical code apart from the @Generated timestamps.
  • Added JavaClientCodegenTest#testEnumUnknownDefaultCaseWithNullableEnums, covering a nullable inline enum and a nullable top-level enum. It fails without the template change and passes with it. JavaClientCodegenTest: 312 tests, 0 failures.
  • Compiled the generated Mode.java with Jackson 2.18 and ran the table above, before and after. Also checked the generated fromValue for the okhttp-gson and native libraries.

PR checklist

  • Read the contribution guidelines.
  • Run the following to build the project and update samples:
    ./mvnw clean package || exit
    ./bin/generate-samples.sh ./bin/configs/*.yaml || exit
    ./bin/utils/export_docs_generators.sh || exit
    
    (For Windows users, please run the script in WSL)
    Commit all changed files.
    This is important, as CI jobs will verify all generator outputs of your HEAD commit as it would merge with master.
    These must match the expectations made by your contribution.
    You may regenerate an individual generator by passing the relevant config(s) as an argument to the script, for example ./bin/generate-samples.sh bin/configs/java*.
    IMPORTANT: Do NOT purge/delete any folders/files (e.g. tests) when regenerating the samples as manually written tests may be removed.
    (Ran the build and regenerated all java-* configs; nothing changed. Not all configs.)
  • If your PR is targeting a particular programming language, @mention the technical committee members, so they are more likely to review the pull request.

@bbdouglas @sreeshas @jfiala @lukoyanov @cbornet @jeff9finger @karismann @Zomzog @lwlee2608 @martin-mfg @KannaKim


Summary by cubic

Makes fromValue on nullable enums map unknown values to UNKNOWN_DEFAULT_OPEN_API when enumUnknownDefaultCase=true, instead of returning null.

  • Previously, an unknown value in a nullable enum was read as "no value", indistinguishable from an explicit null.
  • Now only a null input returns null; any other unknown value returns UNKNOWN_DEFAULT_OPEN_API.
  • Generated code is unchanged when enumUnknownDefaultCase is not set.
  • Applies to the Java templates containing this fromValue: base, native, okhttp-gson, and microprofile.
  • MicroProfile's top-level enums also return null for a null input rather than the unknown default case.
  • Adds JavaClientCodegenTest#testEnumUnknownDefaultCaseWithNullableEnums covering nullable inline and top-level enums across okhttp-gson, resttemplate, native, and microprofile.