Skip to content

Commit

Permalink
#782: Support for Mutiny Return Type on Specific Endpoints (#788)
Browse files Browse the repository at this point in the history
Co-authored-by: oussama Dahmaz <dahmaz@MacBook-Pro-de-odahmaz.local>
  • Loading branch information
oben01 and oussama Dahmaz authored Sep 16, 2024
1 parent 5b9c5a6 commit fe3dfeb
Show file tree
Hide file tree
Showing 18 changed files with 1,617 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public enum ConfigName {
//global & spec configs
SKIP_FORM_MODEL("skip-form-model"),
MUTINY("mutiny"),
MUTINY_OPERATION_IDS("mutiny.operation-ids"),
ADDITIONAL_MODEL_TYPE_ANNOTATIONS("additional-model-type-annotations"),
ADDITIONAL_ENUM_TYPE_UNEXPECTED_MEMBER("additional-enum-type-unexpected-member"),
ADDITIONAL_ENUM_TYPE_UNEXPECTED_MEMBER_NAME("additional-enum-type-unexpected-member-name"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,34 @@ public class CommonItemConfig {
@ConfigItem(name = "mutiny")
public Optional<Boolean> supportMutiny;

/**
* Handles the return type for each operation, depending on the configuration.
* The following cases are supported:
* <p>
* 1. If {@code mutiny} is enabled and the operation ID is specified to return {@code Multi}:
* - The return type will be wrapped in {@link io.smallrye.mutiny.Multi}.
* - If {@code return-response} is enabled, the return type will be
* {@link io.smallrye.mutiny.Multi<jakarta.ws.rs.core.Response>}.
* - If the operation has a void return type, it will return {@link io.smallrye.mutiny.Multi<jakarta.ws.rs.core.Response>}.
* - Otherwise, it will return {@link io.smallrye.mutiny.Multi<returnType>}.
* <p>
* 2. If {@code mutiny} is enabled and the operation ID is specified to return {@code Uni}:
* - The return type will be wrapped in {@link io.smallrye.mutiny.Uni}.
* - If {@code return-response} is enabled, the return type will be
* {@link io.smallrye.mutiny.Uni<jakarta.ws.rs.core.Response>}.
* - If the operation has a void return type, it will return {@link io.smallrye.mutiny.Uni<jakarta.ws.rs.core.Response>}.
* - Otherwise, it will return {@link io.smallrye.mutiny.Uni<returnType>}.
* <p>
* 3. If {@code mutiny} is enabled but no specific operation ID is configured for {@code Multi} or {@code Uni}:
* - The return type defaults to {@code Uni}.
* - If {@code return-response} is enabled, the return type will be
* {@link io.smallrye.mutiny.Uni<jakarta.ws.rs.core.Response>}.
* - If the operation has a void return type, it will return {@link io.smallrye.mutiny.Uni<jakarta.ws.rs.core.Response>}.
* - Otherwise, it will return {@link io.smallrye.mutiny.Uni<returnType>}`.
*/
@ConfigItem(name = "mutiny.operation-ids")
public Optional<Map<String, String>> mutinyMultiOperationIds;

/**
* Defines, whether the `PartFilename` ({@link org.jboss.resteasy.reactive.PartFilename} or
* {@link org.jboss.resteasy.annotations.providers.multipart.PartFilename}) annotation should be generated for
Expand Down Expand Up @@ -118,4 +146,4 @@ public class CommonItemConfig {
*/
@ConfigItem(name = "use-bean-validation")
public Optional<Boolean> useBeanValidation;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,9 @@ protected void generate(final Config config, final Path openApiFilePath, final P
CodegenConfig.ConfigName.ADDITIONAL_PROPERTIES_AS_ATTRIBUTE, Boolean.class)
.orElse(Boolean.FALSE);

getValues(smallRyeConfig, openApiFilePath, CodegenConfig.ConfigName.MUTINY_OPERATION_IDS, String.class, String.class)
.ifPresent(generator::withMutinyReturnTypes);

generator.withAdditionalPropertiesAsAttribute(additionalPropertiesAsAttribute);
GlobalSettings.setProperty(
OpenApiClientGeneratorWrapper.SUPPORTS_ADDITIONAL_PROPERTIES_AS_ATTRIBUTE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import java.io.File;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand Down Expand Up @@ -117,6 +118,14 @@ public OpenApiClientGeneratorWrapper withMutiny(final Boolean config) {
return this;
}

public OpenApiClientGeneratorWrapper withMutinyReturnTypes(final Map<String, String> returnTypeMappings) {
if (returnTypeMappings != null && !returnTypeMappings.isEmpty()) {
Map<String, Object> mutinyOperationIdsMap = new HashMap<>(returnTypeMappings);
configurator.addAdditionalProperty("mutiny-operation-ids", mutinyOperationIdsMap);
}
return this;
}

/**
* Sets the global 'skipFormModel' setting. If not set this setting will default to true.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,57 @@ public interface {classname} {
{/if}{/for}
{/if}{/for}
{#if mutiny}
{#if return-response}
public io.smallrye.mutiny.Uni<jakarta.ws.rs.core.Response> {op.nickname}(
{#if mutiny-operation-ids.get(op.operationIdOriginal) == "Multi"}
{#if return-response}
public io.smallrye.mutiny.Multi<jakarta.ws.rs.core.Response> {op.nickname}(
{#else}
{#if op.returnType == "void"}
public io.smallrye.mutiny.Multi<jakarta.ws.rs.core.Response> {op.nickname}(
{#else}
public io.smallrye.mutiny.Multi<{#if op.returnType}{op.returnType}{#else}jakarta.ws.rs.core.Response{/if}> {op.nickname}(
{/if}
{/if}
{#else}
{#if op.returnType == "void"}
{#if mutiny-operation-ids.get(op.operationIdOriginal) == "Uni"}
{#if return-response}
public io.smallrye.mutiny.Uni<jakarta.ws.rs.core.Response> {op.nickname}(
{#else}
{#if op.returnType == "void"}
public io.smallrye.mutiny.Uni<jakarta.ws.rs.core.Response> {op.nickname}(
{#else}
public io.smallrye.mutiny.Uni<{#if op.returnType}{op.returnType}{#else}jakarta.ws.rs.core.Response{/if}> {op.nickname}(
{/if}
{/if}
{#else}
{#if !mutiny-operation-ids}
{#if return-response}
public io.smallrye.mutiny.Uni<jakarta.ws.rs.core.Response> {op.nickname}(
{#else}
{#if op.returnType == "void"}
public io.smallrye.mutiny.Uni<jakarta.ws.rs.core.Response> {op.nickname}(
{#else}
public {#if op.returnType}io.smallrye.mutiny.Uni<{op.returnType}>{#else}io.smallrye.mutiny.Uni<jakarta.ws.rs.core.Response>{/if} {op.nickname}(
{/if}
{/if}
{#else}
{#if mutiny-operation-ids.get(op.operationIdOriginal)}
{#if op.returnType == "void"}
public io.smallrye.mutiny.Uni<jakarta.ws.rs.core.Response> {op.nickname}(
{#else}
public {#if op.returnType}io.smallrye.mutiny.Uni<{op.returnType}>{#else}io.smallrye.mutiny.Uni<jakarta.ws.rs.core.Response>{/if} {op.nickname}(
{/if}
{#else}
{#if return-response}
public jakarta.ws.rs.core.Response {op.nickname}(
{#else}
{#if op.returnType == "void"}
public jakarta.ws.rs.core.Response {op.nickname}(
{#else}
public {#if op.returnType}{op.returnType}{#else}jakarta.ws.rs.core.Response{/if} {op.nickname}(
{/if}
{/if}
{/if}
{/if}
{/if}
{/if}
{#else}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static io.quarkiverse.openapi.generator.deployment.assertions.Assertions.assertThat;
import static java.util.Objects.requireNonNull;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
Expand Down Expand Up @@ -434,6 +435,75 @@ void shouldBeAbleToEnableMutiny() throws URISyntaxException, FileNotFoundExcepti

}

@Test
void shouldBeAbleToApplyMutinyOnSpecificEndpoints() throws URISyntaxException, FileNotFoundException {
List<File> generatedFiles = createGeneratorWrapper("simple-openapi.json")
.withMutiny(true)
.withMutinyReturnTypes(Map.of("helloMethod", "Uni", "Bye method_get", "Multi"))
.generate("org.mutiny.enabled");

Optional<File> file = generatedFiles.stream()
.filter(f -> f.getName().endsWith("DefaultApi.java"))
.findAny();
assertThat(file).isNotEmpty();
CompilationUnit compilationUnit = StaticJavaParser.parse(file.orElseThrow());
List<MethodDeclaration> methodDeclarations = compilationUnit.findAll(MethodDeclaration.class);
assertThat(methodDeclarations).isNotEmpty();

Optional<MethodDeclaration> helloMethodDeclaration = getMethodDeclarationByIdentifier(methodDeclarations,
"helloMethod");
Optional<MethodDeclaration> byeMethodGetDeclaration = getMethodDeclarationByIdentifier(methodDeclarations,
"byeMethodGet");
Optional<MethodDeclaration> getUserDeclaration = getMethodDeclarationByIdentifier(methodDeclarations,
"getUser");
Optional<MethodDeclaration> getNumbersDeclaration = getMethodDeclarationByIdentifier(methodDeclarations,
"getNumbers");

assertThat(helloMethodDeclaration).hasValueSatisfying(
methodDeclaration -> assertEquals("io.smallrye.mutiny.Uni<String>", methodDeclaration.getType().toString()));
assertThat(byeMethodGetDeclaration).hasValueSatisfying(
methodDeclaration -> assertEquals("io.smallrye.mutiny.Multi<String>", methodDeclaration.getType().toString()));
assertThat(getUserDeclaration).hasValueSatisfying(
methodDeclaration -> assertEquals("GetUser200Response", methodDeclaration.getType().toString()));
assertThat(getNumbersDeclaration).hasValueSatisfying(
methodDeclaration -> assertEquals("List<Integer>", methodDeclaration.getType().toString()));
}

@Test
void shouldBeAbleToApplyMutinyOnSpecificEndpointsWhenUserDefineWrongConfiguration()
throws URISyntaxException, FileNotFoundException {
List<File> generatedFiles = createGeneratorWrapper("simple-openapi.json")
.withMutiny(true)
.withMutinyReturnTypes(Map.of("helloMethod", "Uni", "Bye method_get", "BadConfig"))
.generate("org.mutiny.enabled");

Optional<File> file = generatedFiles.stream()
.filter(f -> f.getName().endsWith("DefaultApi.java"))
.findAny();
assertThat(file).isNotEmpty();
CompilationUnit compilationUnit = StaticJavaParser.parse(file.orElseThrow());
List<MethodDeclaration> methodDeclarations = compilationUnit.findAll(MethodDeclaration.class);
assertThat(methodDeclarations).isNotEmpty();

Optional<MethodDeclaration> helloMethodDeclaration = getMethodDeclarationByIdentifier(methodDeclarations,
"helloMethod");
Optional<MethodDeclaration> byeMethodGetDeclaration = getMethodDeclarationByIdentifier(methodDeclarations,
"byeMethodGet");
Optional<MethodDeclaration> getUserDeclaration = getMethodDeclarationByIdentifier(methodDeclarations,
"getUser");
Optional<MethodDeclaration> getNumbersDeclaration = getMethodDeclarationByIdentifier(methodDeclarations,
"getNumbers");

assertThat(helloMethodDeclaration).hasValueSatisfying(
methodDeclaration -> assertEquals("io.smallrye.mutiny.Uni<String>", methodDeclaration.getType().toString()));
assertThat(byeMethodGetDeclaration).hasValueSatisfying(
methodDeclaration -> assertEquals("io.smallrye.mutiny.Uni<String>", methodDeclaration.getType().toString()));
assertThat(getUserDeclaration).hasValueSatisfying(
methodDeclaration -> assertEquals("GetUser200Response", methodDeclaration.getType().toString()));
assertThat(getNumbersDeclaration).hasValueSatisfying(
methodDeclaration -> assertEquals("List<Integer>", methodDeclaration.getType().toString()));
}

@Test
void shouldBeAbleToAddCustomDateAndTimeMappings() throws URISyntaxException, FileNotFoundException {
List<File> generatedFiles = createGeneratorWrapper("datetime-regression.yml")
Expand Down Expand Up @@ -677,4 +747,9 @@ private Optional<VariableDeclarator> findVariableByName(List<FieldDeclaration> f
.filter((VariableDeclarator variable) -> name.equals(variable.getName().asString()))
.findFirst();
}

private static Optional<MethodDeclaration> getMethodDeclarationByIdentifier(List<MethodDeclaration> methodDeclarations,
String methodName) {
return methodDeclarations.stream().filter(md -> md.getName().getIdentifier().equals(methodName)).findAny();
}
}
47 changes: 46 additions & 1 deletion client/deployment/src/test/resources/openapi/simple-openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,51 @@
}
}
}
},
"/user": {
"get": {
"operationId": "getUser",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"name": {
"type": "string"
}
}
}
}
}
}
}
}
},
"/numbers": {
"get": {
"operationId": "getNumbers",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "integer"
}
}
}
}
}
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
openapi: 3.0.3
info:
title: Test API
version: "1.0"
paths:
/testEndpoint1:
get:
tags:
- Test Endpoint
summary: Simple test endpoint that returns a plain message.
operationId: testEndpoint1
responses:
"200":
description: Success
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: "Test Endpoint 1 success!"
/testEndpoint2:
post:
tags:
- Test Endpoint
summary: Test endpoint to validate POST requests.
operationId: testEndpoint2
requestBody:
content:
application/json:
schema:
type: object
properties:
input:
type: string
example: "Test input"
responses:
"200":
description: Successfully received input.
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: "Test Endpoint 2 received the input."
/testEndpoint3:
put:
tags:
- Test Endpoint
summary: Test PUT request with a JSON body.
operationId: testEndpoint3
requestBody:
content:
application/json:
schema:
type: object
properties:
name:
type: string
example: "Test Name"
value:
type: integer
example: 123
responses:
"200":
description: PUT request successful.
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: "Test Endpoint 3 processed the data successfully."
components:
schemas:
Response:
type: object
properties:
status:
format: int32
type: integer
entity:
type: object
Loading

0 comments on commit fe3dfeb

Please sign in to comment.