Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SmallRye OpenAPI includes coroutine context in schema #21509

Closed
rgmz opened this issue Nov 17, 2021 · 10 comments
Closed

SmallRye OpenAPI includes coroutine context in schema #21509

rgmz opened this issue Nov 17, 2021 · 10 comments

Comments

@rgmz
Copy link
Contributor

rgmz commented Nov 17, 2021

Describe the bug

When using the quarkus-smallrye-openapi extension with routes that use suspend fun (Kotlin coroutines), the generated OpenAPI specification will show coroutine continuations (ContinuationSuperResponse or ContinuationSuperList) as the request body schema.

OpenAPI sample (click to expand)
---
openapi: 3.0.3
info:
  title: quarkus-kotlin-quickstart API
  version: 1.0.0-SNAPSHOT
paths:
  /fruits:
    get:
      tags:
      - Fruit Resource
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ContinuationSuperList'
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Fruit'
    post:
      tags:
      - Fruit Resource
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Fruit'
      responses:
        "200":
          description: OK
  /fruits/{id}:
    get:
      tags:
      - Fruit Resource
      parameters:
      - name: id
        in: path
        required: true
        schema:
          format: int64
          type: integer
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ContinuationSuperResponse'
      responses:
        "200":
          description: OK
    delete:
      tags:
      - Fruit Resource
      parameters:
      - name: id
        in: path
        required: true
        schema:
          format: int64
          type: integer
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ContinuationSuperResponse'
      responses:
        "200":
          description: OK
components:
  schemas:
    ContinuationSuperList:
      type: object
      properties:
        context:
          $ref: '#/components/schemas/CoroutineContext'
    ContinuationSuperResponse:
      type: object
      properties:
        context:
          $ref: '#/components/schemas/CoroutineContext'
    CoroutineContext:
      type: object
    Fruit:
      type: object
      properties:
        id:
          format: int64
          type: integer
        name:
          type: string

Expected behavior

The internal continuation classes to be excluded from the generated schema.

Actual behavior

The response schema is a continuation:

paths:
  /fruits:
    get:
      tags:
      - Fruit Resource
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ContinuationSuperList'

How to Reproduce?

Run https://github.com/rgmz/quarkus-kotlin-openapi-21509 and visit http://localhost:8080/q/openapi

Output of uname -a or ver

No response

Output of java -version

No response

GraalVM version (if different from Java)

No response

Quarkus version or git rev

2.4.2.Final

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

No response

Additional information

No response

@rgmz rgmz added the kind/bug Something isn't working label Nov 17, 2021
@quarkus-bot
Copy link

quarkus-bot bot commented Nov 17, 2021

@rgmz rgmz changed the title SmallRye OpenAPI does not work with Kotlin Coroutines SmallRye OpenAPI includes coroutine context in schema Nov 17, 2021
@rgmz
Copy link
Contributor Author

rgmz commented Nov 17, 2021

Here's the decompiled code from the reproducer, for reference. It seems like ignoring kotlin.coroutines.Continuation should do the trick.

Decompiled Kotlin code (click to expand)
// Greeting.java
package org.acme;

import kotlin.Metadata;
import kotlin.jvm.internal.DefaultConstructorMarker;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

@Metadata(
   mv = {1, 5, 1},
   k = 1,
   d1 = {"\u0000\"\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0010\u000e\n\u0002\b\u0006\n\u0002\u0010\u000b\n\u0002\b\u0002\n\u0002\u0010\b\n\u0002\b\u0002\b\u0086\b\u0018\u00002\u00020\u0001B\u000f\u0012\b\b\u0002\u0010\u0002\u001a\u00020\u0003¢\u0006\u0002\u0010\u0004J\t\u0010\u0007\u001a\u00020\u0003\u0003J\u0013\u0010\b\u001a\u00020\u00002\b\b\u0002\u0010\u0002\u001a\u00020\u0003\u0001J\u0013\u0010\t\u001a\u00020\n2\b\u0010\u000b\u001a\u0004\u0018\u00010\u0001\u0003J\t\u0010\f\u001a\u00020\r\u0001J\t\u0010\u000e\u001a\u00020\u0003\u0001R\u0011\u0010\u0002\u001a\u00020\u0003¢\u0006\b\n\u0000\u001a\u0004\b\u0005\u0010\u0006¨\u0006\u000f"},
   d2 = {"Lorg/acme/Greeting;", "", "message", "", "(Ljava/lang/String;)V", "getMessage", "()Ljava/lang/String;", "component1", "copy", "equals", "", "other", "hashCode", "", "toString", "quarkus-kotlin-openapi"}
)
public final class Greeting {
   @NotNull
   private final String message;

   @NotNull
   public final String getMessage() {
      return this.message;
   }

   public Greeting(@NotNull String message) {
      Intrinsics.checkNotNullParameter(message, "message");
      super();
      this.message = message;
   }

   // $FF: synthetic method
   public Greeting(String var1, int var2, DefaultConstructorMarker var3) {
      if ((var2 & 1) != 0) {
         var1 = "Hello, world!";
      }

      this(var1);
   }

   public Greeting() {
      this((String)null, 1, (DefaultConstructorMarker)null);
   }

   @NotNull
   public final String component1() {
      return this.message;
   }

   @NotNull
   public final Greeting copy(@NotNull String message) {
      Intrinsics.checkNotNullParameter(message, "message");
      return new Greeting(message);
   }

   // $FF: synthetic method
   public static Greeting copy$default(Greeting var0, String var1, int var2, Object var3) {
      if ((var2 & 1) != 0) {
         var1 = var0.message;
      }

      return var0.copy(var1);
   }

   @NotNull
   public String toString() {
      return "Greeting(message=" + this.message + ")";
   }

   public int hashCode() {
      String var10000 = this.message;
      return var10000 != null ? var10000.hashCode() : 0;
   }

   public boolean equals(@Nullable Object var1) {
      if (this != var1) {
         if (var1 instanceof Greeting) {
            Greeting var2 = (Greeting)var1;
            if (Intrinsics.areEqual(this.message, var2.message)) {
               return true;
            }
         }

         return false;
      } else {
         return true;
      }
   }
}
// ReactiveGreetingResource.java
package org.acme;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import kotlin.Metadata;
import kotlin.coroutines.Continuation;
import kotlin.jvm.internal.DefaultConstructorMarker;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

@Path("/hello")
@Metadata(
   mv = {1, 5, 1},
   k = 1,
   d1 = {"\u0000\u0014\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\b\u0017\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\u0011\u0010\u0003\u001a\u00020\u0004H\u0097\u0001\u0000¢\u0006\u0002\u0010\u0005\u0082\u0002\u0004\n\u0002\b\u0019¨\u0006\u0006"},
   d2 = {"Lorg/acme/ReactiveGreetingResource;", "", "()V", "hello", "Lorg/acme/Greeting;", "(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;", "quarkus-kotlin-openapi"}
)
public class ReactiveGreetingResource {
   @GET
   @Produces({"text/plain"})
   @Nullable
   public Object hello(@NotNull Continuation $completion) {
      return hello$suspendImpl(this, $completion);
   }

   // $FF: synthetic method
   static Object hello$suspendImpl(ReactiveGreetingResource var0, Continuation $completion) {
      return new Greeting((String)null, 1, (DefaultConstructorMarker)null);
   }
}

@geoand
Copy link
Contributor

geoand commented Nov 23, 2021

Do you have a reproducer we can try?

@rgmz
Copy link
Contributor Author

rgmz commented Nov 23, 2021

Do you have a reproducer we can try?

Yes: https://github.com/rgmz/quarkus-kotlin-openapi-21509

FYI: based on the discussion in the upstream repository (issue and PR), this seems to be an issue on Quarkus' end. All of the test cases for the smallrye-open-api-jaxrs extension work as expected.

@geoand
Copy link
Contributor

geoand commented Nov 24, 2021

This is not a Quarkus issue as we don't do any OpenAPI schema generation in Quarkus itself.

A did a tiny bit of debugging on your reproducer and it seems like io.smallrye.openapi.runtime.scanner.spi.AnnotationScanner#processRequestBody does not handle the Kotlin Coroutine value properly (it should probably just ignore it) - cc @MikeEdgar @phillip-kruger

@MikeEdgar
Copy link
Contributor

@geoand, it only should attempt to derive a request body if the endpoint specifies a @Consumes. My only thought is that Quarkus is adding that dynamically at build time. Is that what it does?

@MikeEdgar
Copy link
Contributor

I opened a PR to skip the Kotlin params generally for method body. They should never be interpreted for that purpose regardless of @Consumes being present.

@geoand
Copy link
Contributor

geoand commented Nov 25, 2021

Sounds good!

@mhmtszr
Copy link

mhmtszr commented Nov 28, 2021

We are having the same problem, could please say whenever it is resolved?

@MikeEdgar
Copy link
Contributor

Fix incorporated with #21813

@geoand geoand closed this as completed Nov 30, 2021
@geoand geoand added this to the 2.6 - main milestone Nov 30, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants