quarkus icon indicating copy to clipboard operation
quarkus copied to clipboard

[OpenAPI] Custom type annotated with Schema and type = SchemaType.ARRAY and implementation = MyKlass.class inlines implementation properties

Open lloydmeta opened this issue 7 months ago • 6 comments

Describe the bug

Given a custom type (let's say collection type in a 3rd party lib) field that is annotated, e.g.

// Pretend this is something coming from a 3rd party lib, like Guava
// ImmutableList
public record ImmutableList<T>(List<T> items) {
}

public record Greeting(String message) {
}

public record Response(
        @Schema(
            description = "A list of messages",
            implementation = Greeting.class,
            type = SchemaType.ARRAY)
        ImmutableList<Greeting> greetings) {
}

The resulting OpenAPI file generated via https://quarkus.io/extensions/io.quarkus/quarkus-smallrye-openapi/ has an in-lined form.

Expected behavior

I would expect the Greeting class to have its own definition under schemas and for the items of the greeting property of Response to reference it

components:
  schemas:
    Greeting:
      type: object
      properties:
        message:
          type: string
    Response:
      type: object
      properties:
        greetings:
          description: A list of messages
          type: array
          items:
            $ref: "#/components/schemas/Greeting"
# Elided

Actual behavior

The Greeting class definition gets inlined directly into the items :

components:
  schemas:
    Response:
      type: object
      properties:
        greetings:
          description: A list of messages
          type: array
          items:
            type: object
            properties:
              message:
                type: string
paths:
  /hello:
    get:
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Response"
      summary: Hello
      tags:
      - Greeting Resource

How to Reproduce?

https://github.com/lloydmeta/quarkus-open-api-custom-items

Output of uname -a or ver

Darwin Mac 24.4.0 Darwin Kernel Version 24.4.0: Fri Apr 11 18:33:39 PDT 2025; root:xnu-11417.101.15~117/RELEASE_ARM64_T6020 arm64

Output of java -version

openjdk 21.0.6 2025-01-21

Quarkus version or git rev

3.22.1

Build tool (ie. output of mvnw --version or gradlew --version)

Gradle 8.13

Additional information

No response

lloydmeta avatar May 01 '25 12:05 lloydmeta

/cc @EricWittmann (openapi), @MikeEdgar (openapi), @phillip-kruger (openapi)

quarkus-bot[bot] avatar May 01 '25 12:05 quarkus-bot[bot]

@lloydmeta this is a common issue. The correct way to indicate that a class should be used as the items of an array is to specify implementation = Greeting[].class rather than implementation = Greeting.class. Note, the [].

MikeEdgar avatar May 01 '25 12:05 MikeEdgar

If the previous suggestion doesn't work, you can try adding @Schema to the Greeting class also.

MikeEdgar avatar May 01 '25 12:05 MikeEdgar

@lloydmeta this is a common issue. The correct way to indicate that a class should be used as the items of an array is to specify implementation = Greeting[].class rather than implementation = Greeting.class. Note, the [].

@MikeEdgar Gotcha. FWIW, I got that type+implementation idea from this comment https://github.com/microprofile/microprofile-open-api/issues/362#issuecomment-511429042,

I tried your suggestion and it doesn't work.. it results in something stranger:

  schemas:
    Response:
      type: object
      properties:
        greetings:
          description: A list of messages
          type: array
          items:
            type: array
            items:
              type: object
              properties:
                message:
                  type: string

https://github.com/lloydmeta/quarkus-open-api-custom-items/commit/28bfb8d96d159cc7a7e8e8312131e6e5612043ed

If the previous suggestion doesn't work, you can try adding @Schema to the Greeting class also.

So, there are limits to this: I'm specifically looking to be able to use this for models that use 3rd party collections, like ImmutableList from Guava, for instance, where I can't add an annotation to those classes.

In any case, I gave it a shot in https://github.com/lloydmeta/quarkus-open-api-custom-items/commit/9526bd457ad92acf35e848d7c50eff0f5dd34d1a; still doesn't seem to help.

components:
  schemas:
    Greeting:
      description: A greeting message
      type: object
      properties:
        message:
          type: string
          description: The message to be displayed
    ImmutableList:
      type: array
    Response:
      type: object
      properties:
        greetings:
          description: A list of messages
          type: array
          items:
            type: array
            items:
              description: A greeting message
              type: object
              properties:
                message:
                  type: string
                  description: The message to be displayed

The items just inlines+repeats the Greeting definition....

lloydmeta avatar May 01 '25 12:05 lloydmeta

This should get things closer to want you expect. Note that ImmutableList should likely extend/implement List or Iterable for it to behave how it would using some collection library.

// Pretend this is something coming from a 3rd party lib, like Guava
// ImmutableList
public class ImmutableList<T> extends java.util.ArrayList<T> {
    public ImmutableList(List<T> items) {
        super(items);
    }
}

@Schema(description = "A greeting message")
public record Greeting(
        @Schema(description = "The message to be displayed") String message) {
}

public record Response(
        @Schema(
            description = "A list of messages") ImmutableList<Greeting> greetings) {
}

Result - it does lead to an intermediate ImmutableListGreeting schema, but it's functionally the same as your expected result.

components:
  schemas:
    Greeting:
      type: object
      description: A greeting message
      properties:
        message:
          type: string
          description: The message to be displayed
    ImmutableListGreeting:
      type: array
      items:
        $ref: "#/components/schemas/Greeting"
    Response:
      type: object
      properties:
        greetings:
          $ref: "#/components/schemas/ImmutableListGreeting"
          type: array
          description: A list of messages

MikeEdgar avatar May 01 '25 12:05 MikeEdgar

Thanks @MikeEdgar , that's a useful workaround, and I suppose it's interesting that it's functionally the same.

I think having it on hand would lower the priority of this issue, but I'd love to see if we could get to something that would give the expected, and I hope not unreasonable, behaviour.

lloydmeta avatar May 01 '25 13:05 lloydmeta