Lucas Werle Melz
Lucas Werle Melz

Reputation: 41

How to properly chain calls to external API's using Mutiny and process the response? Java / Quarkus / Mutiny

I'm a beginner on asynchronous Java programming and I'm trying to understand how to properly use Mutiny for a Quarkus application which has a Client that sends requests to an external API. This client has two methods that return Uni. Those are the following:

    @POST
    @Path("/repos/{owner}/{repo}/forks")
    Uni<Response> createFork(@HeaderParam("Authorization") String authorization, 
                             @PathParam("owner") String owner,
                             @PathParam("repo") String repo, 
                             String json);`
    @POST
    @Path("/repos/{owner}/{repo}/transfer")
    Uni<Response> transferRepoOwnership(@HeaderParam("Authorization") String authorization,   @PathParam("owner") String owner, @PathParam("repo") String repo, String json);

These were tested and the calls to the API are working just fine. In my service class, I'm trying to use Mutiny to chain these two operations, that is, I want to create a fork and then, after the fork is created, I want to transfer the ownership of the repository... I've tried different approaches and I actually managed to chain these operations and execute them: the problem is, in the "callback" of the second call, I can't access the response to process the data. This is my business logic:

type public Uni<Response> beginChallengeWorkflow(String user, String parentRepoId) {

    // Check if the challenge exists, that is,
    // if the repo to be forked exists
    Optional<Challenge> parentChallenge = challengeRepository.findById(parentRepoId);
    if (parentChallenge.isEmpty()) {
        return Uni.createFrom().item(Response.status(Response.Status.NOT_FOUND)
                .entity("The challenge with the specified id doesn't exist!")
                .build());
    }

    // Check if user has already started the challenge
    Optional<Challenge> startedChallenge = challengeRepository.findByOwnerAndParentId(user, parentRepoId);
    if (startedChallenge.isPresent()) {
        return Uni.createFrom().item(Response.status(Response.Status.CONFLICT)
                .entity("The user has already started the challenge!")
                .build());
    }

    // User has not started challenge yet, so we want to fork the challenge repo
    // and transfer ownership to user
    String challengeCreator = parentChallenge.get().getOwnerUsername();
    String parentRepoName = parentChallenge.get().getName();
    String childRepoName = UUID.randomUUID().toString().substring(0,10) + "-" + user + "-" + parentRepoId;

    Uni<Response> forkUni = forkRepository(challengeCreator, parentRepoName, childRepoName)
        .onItem().transform(response -> {
            if (response.getStatus() != 202) {
                throw new RuntimeException("Failed to fork the 'master' challenge PK");
            }
            return response;
        });


    Uni<Response> transferUni = transferRepositoryOwnership(user, "root", childRepoName)
        .onItem().transform(response -> {
            if (response.getStatus() != 202) {
                throw new RuntimeException("Failed to transfer ownership of the repo to the user");
            }
            else{
                // Save Challenge entity
                // HERE IS WHERE I WANTED TO ACCESS THE RESPONSE
                // BUT I CAN'T
                // System.out.println(response.getEntity().toString()) causes exception:
                // Cannot invoke "Object.toString()" because the return
                // value of "javax.ws.rs.core.Response.getEntity()" is null
            }
            return response;
        });
    
       return forkUni.chain(() -> transferUni);
}

I've tried also:

   forkRepository(challengeCreator, parentRepoName, childRepoName).onItem().transformToUni( response1 -> {
            if (response1.getStatus() != 202) {
                throw new RuntimeException("Failed to fork the 'master' challenge PK");
            }
            return transferRepositoryOwnership(user, "root", childRepoName);
        }).subscribe().with(response2 -> {

                // response2 is null... I can't access it
        });

The strangest thing is that when I call this method from my controller, the endpoint returns the response as I wanted, but I can't access it inside the "callback"... I'm used to asynchronous programming in JavaScript/NodeJS where I could easily access the responses inside the callbacks and I don't understand why is it so complicated to do the same with Mutiny.

Thank you for your attention!

I've tried to chain calls to an external API using Mutiny in a Quarkus application and despite managing to execute the chained operations, I can't access the response in the callback...

Edit: I've added some checks in the second callback to see if there was any response at all at that point:


  // Log response status code and headers
                        System.out.println(response.getStatus());
                        System.out.println(response.getHeaders());

                        if (!response.hasEntity()) {
                            throw new RuntimeException("Response body is empty");
                        }
                        else {
                            // Process response body
                            String responseBody = response.getEntity().toString();
                            System.out.println(responseBody);
                            // ...
                        } 

The status of the response is 202, and the headers are: [Cache-Control=max-age=0, private, must-revalidate, no-transform,Connection=keep-alive,Content-Type=application/json;charset=utf-8,Date=Tue, 25 Apr 2023 11:12:42 GMT,Transfer-Encoding=chunked,X-Content-Type-Options=nosniff,X-Frame-Options=SAMEORIGIN]

But response.getEntity() is null!

Upvotes: 3

Views: 1512

Answers (1)

Lucas Werle Melz
Lucas Werle Melz

Reputation: 41

I've found out that this is actually a Mutiny bug. The response is actually there inside the callback, since we can print its headers and the status code, but not the body of the response... It happens when you have an Uni<Response> object. https://github.com/quarkusio/quarkus/issues/25496

My way around it was to work with Uni<String> instead of Uni<Response> and since I could handle exceptions just by looking at the body of the responses, I could use this approach (I don't need the status code or the headers). Anyway this was kind of a hack... If any Mutiny developer is reading this, please take a look at this bug.

Upvotes: 1

Related Questions