[java][jaxrs-spec][quarkus] - Add support for OneOfInterfaces & fix JsonTypeName value resolution

Merged
IG
4mo ago
+1039-9

Add support for OneOfInterfaces to avoid having circular dependencies because the child schemas components extend a parent class that also has oneOf and mapping pointing to the children.

The bug

x-discriminator-value was only ever populated from a model's Java parentModel. That works for classic allOf inheritance (CatRequest extends PetRequest), but not for the useOneOfInterfaces shape: there the child allOfs a shared base that gets flattened, so the child has no parentModel — it only implements the generated interface, and the discriminator lives on that interface, not on any Java parent.

Result: for oneOf-interface children, x-discriminator-value was never set, and @JsonTypeName fell back to the class name:


@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "petType", visible = true)
@JsonSubTypes({
  @JsonSubTypes.Type(value = CatRequest.class, name = "CAT"),
  @JsonSubTypes.Type(value = DogRequest.class, name = "DOG"),
})
public interface PetRequest { PetType getPetType(); }

// Before — wrong: class name, not the mapping value
@JsonTypeName("CatRequest")
public class CatRequest implements PetRequest { … }

// After — correct: the discriminator mapping value
@JsonTypeName("CAT")
public class CatRequest implements PetRequest { … }

Example:


   # -----------------------------------------------
   # Base schema in component that contains oneOf and mapping
   # Discriminator also lives here on the parent
   # Child classes have a circular dependency because allOf to the parent with the oneOf & mapping
   # PetRequest (mapping) -> CatRequest (allOf) -> PetRequest
   # -----------------------------------------------

   PetRequest:
     type: object
     required:
       - petType
       - name
     properties:
       petType:
         type: string
         enum:
           - CAT
           - DOG
       name:
         type: string
     discriminator:
       propertyName: petType
       mapping:
         CAT: '#/components/schemas/CatRequest'
         DOG: '#/components/schemas/DogRequest'

   CatRequest:
     type: object
     allOf:
       - $ref: '#/components/schemas/PetRequest'
       - type: object
         required:
           - breedType
           - petType
         properties:
           breedType:
             $ref: '#/components/schemas/CatBreedType'
   DogRequest:
     type: object
     allOf:
       - $ref: '#/components/schemas/PetRequest'
       - type: object
         properties:
           breedType:
             $ref: '#/components/schemas/DogBreedType'


   # -----------------------------------------------
   # Base schema a separate component
   # Discriminator lives in parent and base object until #24156 is merged
   # -----------------------------------------------

   PetRequestV2:
     type: object
     properties:
       petType:
         $ref: '#/components/schemas/PetType'      # --> This will removed once #24156 is merged
     oneOf:
       - $ref: '#/components/schemas/CatRequestV2'
       - $ref: '#/components/schemas/DogRequestV2'
     discriminator:
       propertyName: petType
       mapping:
         CAT: '#/components/schemas/CatRequestV2'
         DOG: '#/components/schemas/DogRequestV2'

   PetRequestV2Base:
       type: object
       required:
         - petType
         - name
       properties:
         petType:
           $ref: '#/components/schemas/PetType'
         name:
           type: string
   
   CatRequestV2:
     type: object
     allOf:
       - $ref: '#/components/schemas/PetRequestV2Base'
       - type: object
         properties:
           breedType:
             $ref: '#/components/schemas/CatBreedType'
          

   DogRequestV2:
     type: object
     allOf:
       - $ref: '#/components/schemas/PetRequestV2Base'
       - type: object
         properties:
           breedType:
             $ref: '#/components/schemas/DogBreedType'

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.
  • If your PR is targeting a particular programming language, @mention the technical committee members, so they are more likely to review the pull request.

Summary by cubic

Adds useOneOfInterfaces to jaxrs-spec, generating oneOf schemas as Java interfaces with a typed discriminator getter, and fixes subtype mapping so @JsonTypeName uses the discriminator mapping value. Opt-in; default output is unchanged.

  • New Features

    • Route x-is-one-of-interface models in model.mustache to oneof_interface.mustache to emit a plain interface with Jackson type info and a typed getter (enum when applicable); supports additionalOneOfTypeAnnotations.mustache and optional JsonTypeInfo.DEDUCTION.
    • Add sample samples/server/petstore/jaxrs-spec-oneof-interface, config bin/configs/jaxrs-spec-oneof-interface.yaml, tests, and register the sample in the JAX-RS samples workflow.
  • Bug Fixes

    • In JavaJAXRSSpecServerCodegen, index discriminators for generated oneOf interfaces and resolve subtype mappings from either parentModel or the implemented interface via x-implements, setting x-discriminator-value for correct @JsonTypeName and Jackson polymorphic (de)serialization.