From 64151bc52ad3cc66df4165b2f11c125437c315fd Mon Sep 17 00:00:00 2001 From: Helber Belmiro Date: Wed, 16 Aug 2023 10:58:04 -0300 Subject: [PATCH] Added support for custom templates (#439) * Added support for custom templates Signed-off-by: Helber Belmiro * Update README.md Co-authored-by: Ricardo Zanini <1538000+ricardozanini@users.noreply.github.com> --------- Signed-off-by: Helber Belmiro Co-authored-by: Ricardo Zanini <1538000+ricardozanini@users.noreply.github.com> --- README.md | 12 + .../codegen/OpenApiGeneratorCodeGenBase.java | 9 +- .../OpenApiGeneratorStreamCodeGen.java | 3 +- .../OpenApiClientGeneratorWrapper.java | 4 + .../wrapper/QuarkusJavaClientCodegen.java | 1 + .../additionalEnumTypeAnnotations.qute | 0 .../additionalModelTypeAnnotations.qute | 0 .../{ => libraries/microprofile}/api.qute | 0 .../auth/compositeAuthenticationProvider.qute | 0 .../microprofile}/auth/headersFactory.qute | 0 .../microprofile}/beanValidation.qute | 0 .../microprofile}/beanValidationCore.qute | 0 .../beanValidationHeaderParams.qute | 0 .../microprofile}/bodyParams.qute | 0 .../microprofile}/enumClass.qute | 0 .../microprofile}/enumOuterClass.qute | 0 .../microprofile}/headerParams.qute | 0 .../{ => libraries/microprofile}/model.qute | 0 .../microprofile}/multipartFormdataPojo.qute | 0 .../microprofile}/pathParams.qute | 0 .../{ => libraries/microprofile}/pojo.qute | 0 .../microprofile}/pojoQueryParam.qute | 0 .../microprofile}/queryParams.qute | 0 integration-tests/custom-templates/pom.xml | 90 ++++++ .../main/openapi/quarkus-simple-openapi.yaml | 274 ++++++++++++++++++ .../src/main/resources/application.properties | 1 + .../src/main/resources/templates/api.qute | 132 +++++++++ .../resources/templates/pojoQueryParam.qute | 141 +++++++++ .../it/QuarkusSimpleOpenApiTest.java | 19 ++ integration-tests/pom.xml | 1 + 30 files changed, 683 insertions(+), 4 deletions(-) rename deployment/src/main/resources/templates/{ => libraries/microprofile}/additionalEnumTypeAnnotations.qute (100%) rename deployment/src/main/resources/templates/{ => libraries/microprofile}/additionalModelTypeAnnotations.qute (100%) rename deployment/src/main/resources/templates/{ => libraries/microprofile}/api.qute (100%) rename deployment/src/main/resources/templates/{ => libraries/microprofile}/auth/compositeAuthenticationProvider.qute (100%) rename deployment/src/main/resources/templates/{ => libraries/microprofile}/auth/headersFactory.qute (100%) rename deployment/src/main/resources/templates/{ => libraries/microprofile}/beanValidation.qute (100%) rename deployment/src/main/resources/templates/{ => libraries/microprofile}/beanValidationCore.qute (100%) rename deployment/src/main/resources/templates/{ => libraries/microprofile}/beanValidationHeaderParams.qute (100%) rename deployment/src/main/resources/templates/{ => libraries/microprofile}/bodyParams.qute (100%) rename deployment/src/main/resources/templates/{ => libraries/microprofile}/enumClass.qute (100%) rename deployment/src/main/resources/templates/{ => libraries/microprofile}/enumOuterClass.qute (100%) rename deployment/src/main/resources/templates/{ => libraries/microprofile}/headerParams.qute (100%) rename deployment/src/main/resources/templates/{ => libraries/microprofile}/model.qute (100%) rename deployment/src/main/resources/templates/{ => libraries/microprofile}/multipartFormdataPojo.qute (100%) rename deployment/src/main/resources/templates/{ => libraries/microprofile}/pathParams.qute (100%) rename deployment/src/main/resources/templates/{ => libraries/microprofile}/pojo.qute (100%) rename deployment/src/main/resources/templates/{ => libraries/microprofile}/pojoQueryParam.qute (100%) rename deployment/src/main/resources/templates/{ => libraries/microprofile}/queryParams.qute (100%) create mode 100644 integration-tests/custom-templates/pom.xml create mode 100644 integration-tests/custom-templates/src/main/openapi/quarkus-simple-openapi.yaml create mode 100644 integration-tests/custom-templates/src/main/resources/application.properties create mode 100644 integration-tests/custom-templates/src/main/resources/templates/api.qute create mode 100644 integration-tests/custom-templates/src/main/resources/templates/pojoQueryParam.qute create mode 100644 integration-tests/custom-templates/src/test/java/io/quarkiverse/openapi/generator/it/QuarkusSimpleOpenApiTest.java diff --git a/README.md b/README.md index 2560c4832..4a13d7625 100644 --- a/README.md +++ b/README.md @@ -729,6 +729,18 @@ It's also possible to only use a type mapping with a fully qualified name, for i See the module [type-mapping](integration-tests/type-mapping) for an example of how to use this feature. +## Template Customization + +You have the option to swap out the [templates used by this extension](deployment/src/main/resources/templates/libraries/microprofile) with your customized versions. To achieve this, place your custom templates under the `resources/templates` directory. It's crucial that the filename of each custom template matches that of the original template. + +You can find an example of using customized templates in [integration-tests/custom-templates](integration-tests/custom-templates). + +### **⚠️** Important + +While the option to replace templates exists, it's essential to exercise caution and consider this as a final resort. Prior to altering templates, exhaust all possibilities of achieving your goals through configuration settings. Modifying templates could have broader implications for the extension's functionality and may introduce complexities. Only resort to template replacement when configuration adjustments prove insufficient for your requirements. + +Furthermore, be aware that customizing templates increases the risk of compatibility issues during future upgrades. Therefore, exercise discretion and weigh the benefits against the potential risks before opting for template customization. + ## Known Limitations These are the known limitations of this pre-release version: diff --git a/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/OpenApiGeneratorCodeGenBase.java b/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/OpenApiGeneratorCodeGenBase.java index 03489eb41..19712b9a5 100644 --- a/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/OpenApiGeneratorCodeGenBase.java +++ b/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/OpenApiGeneratorCodeGenBase.java @@ -106,6 +106,7 @@ public boolean trigger(CodeGenContext context) throws CodeGenException { } try (Stream openApiFilesPaths = Files.walk(openApiDir)) { + Path templateDir = context.workDir().resolve("classes").resolve("templates"); openApiFilesPaths .filter(Files::isRegularFile) .filter(path -> { @@ -114,7 +115,8 @@ public boolean trigger(CodeGenContext context) throws CodeGenException { && !filesToExclude.contains(fileName) && (filesToInclude.isEmpty() || filesToInclude.contains(fileName)); }) - .forEach(openApiFilePath -> generate(context.config(), openApiFilePath, outDir, isRestEasyReactive)); + .forEach(openApiFilePath -> generate(context.config(), openApiFilePath, outDir, templateDir, + isRestEasyReactive)); } catch (IOException e) { throw new CodeGenException("Failed to generate java files from OpenApi files in " + openApiDir.toAbsolutePath(), e); @@ -146,8 +148,7 @@ private static boolean isExtensionCapabilityPresent(CodeGenContext context, Stri // TODO: do not generate if the output dir has generated files and the openapi file has the same checksum of the previous run protected void generate(final Config config, final Path openApiFilePath, final Path outDir, - boolean isRestEasyReactive) { - + Path templateDir, boolean isRestEasyReactive) { final String basePackage = getBasePackage(config, openApiFilePath); final Boolean verbose = config.getOptionalValue(getGlobalConfigName(CodegenConfig.ConfigName.VERBOSE), Boolean.class) .orElse(false); @@ -158,6 +159,8 @@ protected void generate(final Config config, final Path openApiFilePath, final P final OpenApiClientGeneratorWrapper generator = createGeneratorWrapper(openApiFilePath, outDir, isRestEasyReactive, verbose, validateSpec); + generator.withTemplateDir(templateDir); + generator.withClassesCodeGenConfig(ClassCodegenConfigParser.parse(config, basePackage)) .withCircuitBreakerConfig(CircuitBreakerConfigurationParser.parse( config)); diff --git a/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/OpenApiGeneratorStreamCodeGen.java b/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/OpenApiGeneratorStreamCodeGen.java index a3aeceadf..04ef4acef 100644 --- a/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/OpenApiGeneratorStreamCodeGen.java +++ b/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/OpenApiGeneratorStreamCodeGen.java @@ -74,7 +74,8 @@ public boolean trigger(CodeGenContext context) throws CodeGenException { StandardOpenOption.CREATE)) { outChannel.transferFrom(inChannel, 0, Integer.MAX_VALUE); LOGGER.debug("Saved OpenAPI spec input model in {}", openApiFilePath); - this.generate(this.mergeConfig(context, inputModel), openApiFilePath, outDir, isRestEasyReactive); + this.generate(this.mergeConfig(context, inputModel), openApiFilePath, outDir, + context.workDir().resolve("classes").resolve("templates"), isRestEasyReactive); generated = true; } } catch (IOException e) { diff --git a/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/wrapper/OpenApiClientGeneratorWrapper.java b/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/wrapper/OpenApiClientGeneratorWrapper.java index 2854ef799..38f30d6bc 100644 --- a/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/wrapper/OpenApiClientGeneratorWrapper.java +++ b/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/wrapper/OpenApiClientGeneratorWrapper.java @@ -175,6 +175,10 @@ public OpenApiClientGeneratorWrapper withAdditionalApiTypeAnnotationsConfig(fina return this; } + public void withTemplateDir(Path templateDir) { + this.configurator.addAdditionalProperty("templateDir", templateDir.toString()); + } + public List generate(final String basePackage) { this.basePackage = basePackage; this.consolidatePackageNames(); diff --git a/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/wrapper/QuarkusJavaClientCodegen.java b/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/wrapper/QuarkusJavaClientCodegen.java index 2ddb58ab6..1b9b4a679 100644 --- a/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/wrapper/QuarkusJavaClientCodegen.java +++ b/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/wrapper/QuarkusJavaClientCodegen.java @@ -48,6 +48,7 @@ public void processOpts() { this.projectTestFolder = ""; this.sourceFolder = ""; this.testFolder = ""; + this.embeddedTemplateDir = "templates"; this.replaceWithQuarkusTemplateFiles(); } diff --git a/deployment/src/main/resources/templates/additionalEnumTypeAnnotations.qute b/deployment/src/main/resources/templates/libraries/microprofile/additionalEnumTypeAnnotations.qute similarity index 100% rename from deployment/src/main/resources/templates/additionalEnumTypeAnnotations.qute rename to deployment/src/main/resources/templates/libraries/microprofile/additionalEnumTypeAnnotations.qute diff --git a/deployment/src/main/resources/templates/additionalModelTypeAnnotations.qute b/deployment/src/main/resources/templates/libraries/microprofile/additionalModelTypeAnnotations.qute similarity index 100% rename from deployment/src/main/resources/templates/additionalModelTypeAnnotations.qute rename to deployment/src/main/resources/templates/libraries/microprofile/additionalModelTypeAnnotations.qute diff --git a/deployment/src/main/resources/templates/api.qute b/deployment/src/main/resources/templates/libraries/microprofile/api.qute similarity index 100% rename from deployment/src/main/resources/templates/api.qute rename to deployment/src/main/resources/templates/libraries/microprofile/api.qute diff --git a/deployment/src/main/resources/templates/auth/compositeAuthenticationProvider.qute b/deployment/src/main/resources/templates/libraries/microprofile/auth/compositeAuthenticationProvider.qute similarity index 100% rename from deployment/src/main/resources/templates/auth/compositeAuthenticationProvider.qute rename to deployment/src/main/resources/templates/libraries/microprofile/auth/compositeAuthenticationProvider.qute diff --git a/deployment/src/main/resources/templates/auth/headersFactory.qute b/deployment/src/main/resources/templates/libraries/microprofile/auth/headersFactory.qute similarity index 100% rename from deployment/src/main/resources/templates/auth/headersFactory.qute rename to deployment/src/main/resources/templates/libraries/microprofile/auth/headersFactory.qute diff --git a/deployment/src/main/resources/templates/beanValidation.qute b/deployment/src/main/resources/templates/libraries/microprofile/beanValidation.qute similarity index 100% rename from deployment/src/main/resources/templates/beanValidation.qute rename to deployment/src/main/resources/templates/libraries/microprofile/beanValidation.qute diff --git a/deployment/src/main/resources/templates/beanValidationCore.qute b/deployment/src/main/resources/templates/libraries/microprofile/beanValidationCore.qute similarity index 100% rename from deployment/src/main/resources/templates/beanValidationCore.qute rename to deployment/src/main/resources/templates/libraries/microprofile/beanValidationCore.qute diff --git a/deployment/src/main/resources/templates/beanValidationHeaderParams.qute b/deployment/src/main/resources/templates/libraries/microprofile/beanValidationHeaderParams.qute similarity index 100% rename from deployment/src/main/resources/templates/beanValidationHeaderParams.qute rename to deployment/src/main/resources/templates/libraries/microprofile/beanValidationHeaderParams.qute diff --git a/deployment/src/main/resources/templates/bodyParams.qute b/deployment/src/main/resources/templates/libraries/microprofile/bodyParams.qute similarity index 100% rename from deployment/src/main/resources/templates/bodyParams.qute rename to deployment/src/main/resources/templates/libraries/microprofile/bodyParams.qute diff --git a/deployment/src/main/resources/templates/enumClass.qute b/deployment/src/main/resources/templates/libraries/microprofile/enumClass.qute similarity index 100% rename from deployment/src/main/resources/templates/enumClass.qute rename to deployment/src/main/resources/templates/libraries/microprofile/enumClass.qute diff --git a/deployment/src/main/resources/templates/enumOuterClass.qute b/deployment/src/main/resources/templates/libraries/microprofile/enumOuterClass.qute similarity index 100% rename from deployment/src/main/resources/templates/enumOuterClass.qute rename to deployment/src/main/resources/templates/libraries/microprofile/enumOuterClass.qute diff --git a/deployment/src/main/resources/templates/headerParams.qute b/deployment/src/main/resources/templates/libraries/microprofile/headerParams.qute similarity index 100% rename from deployment/src/main/resources/templates/headerParams.qute rename to deployment/src/main/resources/templates/libraries/microprofile/headerParams.qute diff --git a/deployment/src/main/resources/templates/model.qute b/deployment/src/main/resources/templates/libraries/microprofile/model.qute similarity index 100% rename from deployment/src/main/resources/templates/model.qute rename to deployment/src/main/resources/templates/libraries/microprofile/model.qute diff --git a/deployment/src/main/resources/templates/multipartFormdataPojo.qute b/deployment/src/main/resources/templates/libraries/microprofile/multipartFormdataPojo.qute similarity index 100% rename from deployment/src/main/resources/templates/multipartFormdataPojo.qute rename to deployment/src/main/resources/templates/libraries/microprofile/multipartFormdataPojo.qute diff --git a/deployment/src/main/resources/templates/pathParams.qute b/deployment/src/main/resources/templates/libraries/microprofile/pathParams.qute similarity index 100% rename from deployment/src/main/resources/templates/pathParams.qute rename to deployment/src/main/resources/templates/libraries/microprofile/pathParams.qute diff --git a/deployment/src/main/resources/templates/pojo.qute b/deployment/src/main/resources/templates/libraries/microprofile/pojo.qute similarity index 100% rename from deployment/src/main/resources/templates/pojo.qute rename to deployment/src/main/resources/templates/libraries/microprofile/pojo.qute diff --git a/deployment/src/main/resources/templates/pojoQueryParam.qute b/deployment/src/main/resources/templates/libraries/microprofile/pojoQueryParam.qute similarity index 100% rename from deployment/src/main/resources/templates/pojoQueryParam.qute rename to deployment/src/main/resources/templates/libraries/microprofile/pojoQueryParam.qute diff --git a/deployment/src/main/resources/templates/queryParams.qute b/deployment/src/main/resources/templates/libraries/microprofile/queryParams.qute similarity index 100% rename from deployment/src/main/resources/templates/queryParams.qute rename to deployment/src/main/resources/templates/libraries/microprofile/queryParams.qute diff --git a/integration-tests/custom-templates/pom.xml b/integration-tests/custom-templates/pom.xml new file mode 100644 index 000000000..a2b33e273 --- /dev/null +++ b/integration-tests/custom-templates/pom.xml @@ -0,0 +1,90 @@ + + + + quarkus-openapi-generator-integration-tests + io.quarkiverse.openapi.generator + 3.0.0-SNAPSHOT + + 4.0.0 + + quarkus-openapi-generator-it-custom-templates + Quarkus - Openapi Generator - Integration Tests - Custom Templates + Example project with custom templates + + + + io.quarkiverse.openapi.generator + quarkus-openapi-generator + + + org.assertj + assertj-core + test + + + io.quarkus + quarkus-junit5 + test + + + + + + io.quarkus + quarkus-maven-plugin + true + + + + build + generate-code + generate-code-tests + + + + + + + + + native-image + + + native + + + + + + maven-surefire-plugin + + ${native.surefire.skip} + + + + maven-failsafe-plugin + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + + + + native + + + + + \ No newline at end of file diff --git a/integration-tests/custom-templates/src/main/openapi/quarkus-simple-openapi.yaml b/integration-tests/custom-templates/src/main/openapi/quarkus-simple-openapi.yaml new file mode 100644 index 000000000..4a853fe0b --- /dev/null +++ b/integration-tests/custom-templates/src/main/openapi/quarkus-simple-openapi.yaml @@ -0,0 +1,274 @@ +--- +openapi: 3.0.3 +info: + title: greeting-flow API + version: "1.0" +paths: + /: + post: + requestBody: + content: + '*/*': + schema: + $ref: '#/components/schemas/CloudEvent' + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Response' + /hello: + get: + tags: + - Reactive Greeting Resource + operationId: hello + responses: + "200": + description: OK + content: + text/plain: + schema: + type: string + /messaging/topics: + get: + tags: + - Quarkus Topics Information Resource + responses: + "200": + description: OK +components: + schemas: + CloudEvent: + type: object + properties: + specVersion: + $ref: '#/components/schemas/SpecVersion' + id: + type: string + type: + type: string + source: + format: uri + type: string + dataContentType: + type: string + dataSchema: + format: uri + type: string + subject: + type: string + time: + format: date-time + type: string + attributeNames: + uniqueItems: true + type: array + items: + type: string + extensionNames: + uniqueItems: true + type: array + items: + type: string + data: + $ref: '#/components/schemas/CloudEventData' + CloudEventData: + type: object + EntityTag: + type: object + properties: + value: + type: string + weak: + type: boolean + Family: + enum: + - INFORMATIONAL + - SUCCESSFUL + - REDIRECTION + - CLIENT_ERROR + - SERVER_ERROR + - OTHER + type: string + Link: + type: object + properties: + uri: + format: uri + type: string + uriBuilder: + $ref: '#/components/schemas/UriBuilder' + rel: + type: string + rels: + type: array + items: + type: string + title: + type: string + type: + type: string + params: + type: object + additionalProperties: + type: string + Locale: + type: object + properties: + language: + type: string + script: + type: string + country: + type: string + variant: + type: string + extensionKeys: + uniqueItems: true + type: array + items: + format: byte + type: string + unicodeLocaleAttributes: + uniqueItems: true + type: array + items: + type: string + unicodeLocaleKeys: + uniqueItems: true + type: array + items: + type: string + iSO3Language: + type: string + iSO3Country: + type: string + displayLanguage: + type: string + displayScript: + type: string + displayCountry: + type: string + displayVariant: + type: string + displayName: + type: string + MediaType: + type: object + properties: + type: + type: string + subtype: + type: string + parameters: + type: object + additionalProperties: + type: string + wildcardType: + type: boolean + wildcardSubtype: + type: boolean + MultivaluedMapStringObject: + type: object + additionalProperties: + type: array + items: + type: object + MultivaluedMapStringString: + type: object + additionalProperties: + type: array + items: + type: string + NewCookie: + type: object + properties: + name: + type: string + value: + type: string + version: + format: int32 + type: integer + path: + type: string + domain: + type: string + comment: + type: string + maxAge: + format: int32 + type: integer + expiry: + format: date + type: string + secure: + type: boolean + httpOnly: + type: boolean + Response: + type: object + properties: + status: + format: int32 + type: integer + statusInfo: + $ref: '#/components/schemas/StatusType' + entity: + type: object + mediaType: + $ref: '#/components/schemas/MediaType' + language: + $ref: '#/components/schemas/Locale' + length: + format: int32 + type: integer + allowedMethods: + uniqueItems: true + type: array + items: + type: string + cookies: + type: object + additionalProperties: + $ref: '#/components/schemas/NewCookie' + entityTag: + $ref: '#/components/schemas/EntityTag' + date: + format: date + type: string + lastModified: + format: date + type: string + location: + format: uri + type: string + links: + uniqueItems: true + type: array + items: + $ref: '#/components/schemas/Link' + metadata: + $ref: '#/components/schemas/MultivaluedMapStringObject' + headers: + $ref: '#/components/schemas/MultivaluedMapStringObject' + stringHeaders: + $ref: '#/components/schemas/MultivaluedMapStringString' + SpecVersion: + enum: + - V03 + - V1 + type: string + StatusType: + type: object + properties: + statusCode: + format: int32 + type: integer + family: + $ref: '#/components/schemas/Family' + reasonPhrase: + type: string + UriBuilder: + type: object diff --git a/integration-tests/custom-templates/src/main/resources/application.properties b/integration-tests/custom-templates/src/main/resources/application.properties new file mode 100644 index 000000000..a2f16a854 --- /dev/null +++ b/integration-tests/custom-templates/src/main/resources/application.properties @@ -0,0 +1 @@ +quarkus.rest-client.quarkus_simple_openapi_yaml.url=http://localhost:8080 \ No newline at end of file diff --git a/integration-tests/custom-templates/src/main/resources/templates/api.qute b/integration-tests/custom-templates/src/main/resources/templates/api.qute new file mode 100644 index 000000000..afa968c2f --- /dev/null +++ b/integration-tests/custom-templates/src/main/resources/templates/api.qute @@ -0,0 +1,132 @@ +package {package}; + +{#for imp in imports} +import {imp.import}; +{/for} +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; +{#if hasAuthMethods || custom-register-providers.orEmpty.size > 0} +import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; +{/if} +{#if hasAuthMethods} +import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders; +import {package}.auth.CompositeAuthenticationProvider; +{#if client-headers-factory is 'default'} +import {package}.auth.AuthenticationPropagationHeadersFactory; +{/if} +{/if} + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; +import java.util.Map; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; + +import jakarta.enterprise.context.ApplicationScoped; + +import io.quarkiverse.openapi.generator.annotations.GeneratedClass; +import io.quarkiverse.openapi.generator.annotations.GeneratedMethod; +import io.quarkiverse.openapi.generator.annotations.GeneratedParam; + +{#if appName} +/** + * {appName} + * {#if appDescription}

{appDescription}

{/if} + */ +{/if} +@Path("{#if useAnnotatedBasePath}{contextPath}{/if}{commonPath}") +@RegisterRestClient({#if defaultServerUrl}baseUri="{defaultServerUrl}",{/if} configKey="{quarkus-generator.openApiSpecId}") +@GeneratedClass(value="{openapi:parseUri(inputSpec)}", tag = "{baseName}") +{#if hasAuthMethods} +@RegisterProvider(CompositeAuthenticationProvider.class) +{#when client-headers-factory} +{#is 'default'} +@RegisterClientHeaders(AuthenticationPropagationHeadersFactory.class) +{#is 'none'} +@RegisterClientHeaders +{#else} +@RegisterClientHeaders({client-headers-factory}.class) +{/when} +{/if} +{#for crpClassConfig in custom-register-providers.orEmpty} +@RegisterProvider({crpClassConfig}.class) +{/for} +@ApplicationScoped +public interface {classname} { + + public default void myCustomMethod() { + } + + {#for op in operations.operation} + {#if !op.isDeprecated || openapi:genDeprecatedApiAttr(package, classname, classes-codegen)} + {#if op.summary} + /** + * {op.summary} + * + {#if op.notes} + * {op.notes} + * + {/if} + */ + {/if} + @{op.httpMethod} + {#if op.subresourceOperation} + @Path("{op.path}") + {/if} + {#if op.hasConsumes} + @Consumes(\{{#for consume in op.consumes}"{consume.mediaType}"{#if consume_hasNext}, {/if}{/for}\}) + {/if} + {#if op.hasProduces} + @Produces(\{{#for produce in op.produces}"{produce.mediaType}"{#if produce_hasNext}, {/if}{/for}\}) + {/if} + @GeneratedMethod ("{op.operationIdOriginal}") + {#for cbClassConfig in circuit-breaker.orEmpty}{#if cbClassConfig.key == package + classname} + {#for cbMethod in cbClassConfig.value.orEmpty}{#if cbMethod == op.nickname} + @org.eclipse.microprofile.faulttolerance.CircuitBreaker + {/if}{/for} + {/if}{/for} + {#if mutiny} + {#if return-response} + public io.smallrye.mutiny.Uni {op.nickname}( + {#else} + {#if op.returnType == "void"} + public {#if op.returnType}io.smallrye.mutiny.Uni{#else}io.smallrye.mutiny.Uni{/if} {op.nickname}( + {#else} + public {#if op.returnType}io.smallrye.mutiny.Uni<{op.returnType}>{#else}io.smallrye.mutiny.Uni{/if} {op.nickname}( + {/if} + {/if} + {#else} + {#if return-response} + 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 op.hasFormParams} + {#if is-resteasy-reactive} + @jakarta.ws.rs.BeanParam {op.operationIdCamelCase}MultipartForm multipartForm{#if op.hasPathParams},{/if}{! + !}{#for p in op.pathParams}{#include templates.path_params param=p/}{#if p_hasNext}, {/if}{/for}{#if op.hasQueryParams},{/if}{! + !}{#for p in op.queryParams}{#include templates.query_params param=p/}{#if p_hasNext}, {/if}{/for}{#if op.hasBodyParams},{/if}{! + !}{#for p in op.bodyParams}{#include bodyParams.qute param=p/}{#if p_hasNext}, {/if}{/for}{#if op.hasHeaderParams},{/if}{! + !}{#for p in op.headerParams}{#include headerParams.qute param=p/}{#if p_hasNext}, {/if}{/for} + {#else} + @org.jboss.resteasy.annotations.providers.multipart.MultipartForm {op.operationIdCamelCase}MultipartForm multipartForm{#if op.hasPathParams},{/if}{! + !}{#for p in op.pathParams}{#include pathParams.qute param=p/}{#if p_hasNext}, {/if}{/for}{#if op.hasQueryParams},{/if}{! + !}{#for p in op.queryParams}{#include queryParams.qute param=p/}{#if p_hasNext}, {/if}{/for}{#if op.hasBodyParams},{/if}{! + !}{#for p in op.bodyParams}{#include bodyParams.qute param=p/}{#if p_hasNext}, {/if}{/for}{#if op.hasHeaderParams},{/if}{! + !}{#for p in op.headerParams}{#include headerParams.qute param=p/}{#if p_hasNext}, {/if}{/for} + {/if} + {#else} + {#for p in op.allParams} + {#include pathParams.qute param=p/}{#include queryParams.qute param=p/}{#include bodyParams.qute param=p/}{#include headerParams.qute param=p/}{#if p_hasNext}, {/if} + {/for} + {/if} + ); + {#if op.hasFormParams} + + {#include multipartFormdataPojo.qute param=op/} + {/if} + + {/if} {! check deprecated !} + {/for} +} diff --git a/integration-tests/custom-templates/src/main/resources/templates/pojoQueryParam.qute b/integration-tests/custom-templates/src/main/resources/templates/pojoQueryParam.qute new file mode 100644 index 000000000..74d2d1646 --- /dev/null +++ b/integration-tests/custom-templates/src/main/resources/templates/pojoQueryParam.qute @@ -0,0 +1,141 @@ + {#if withXml} + @XmlAccessorType(XmlAccessType.FIELD) + {#if m.hasVars}@XmlType(name = "{m.classname}", propOrder = + { {#for var in m.vars}"{var.name}"{#if var_hasNext}, {/if}{/for} + }){#else} + @XmlType(name = "{m.classname}") + {/if} + {#if !m.parent || m.parent.isEmpty}@XmlRootElement(name = "{m.classname}"){/if} + {#else} + @JsonIgnoreProperties(ignoreUnknown = true) + {/if} + {#if m.description} + /** + * {m.description} + **/ + {/if} + {#include additionalModelTypeAnnotations.qute m=m/} + public static class {m.classname}QueryParam {#if m.parent}extends {m.parent}{/if}{#if m.serializableModel} implements Serializable{/if} { + + public final int myCustomQueryParamAttribute = 42; + + {#for v in m.vars} + {#if !v.deprecated || openapi:genDeprecatedModelAttr(package, m.classname, codegen)} + {#if v.isEnum} + {#if v.isContainer && v.mostInnerItems} + {#include enumClass.qute e=v/} + {#else if !v.isContainer} + + {#include enumClass.qute e=v/}{/if} + {/if} + {#if withXml} + @XmlElement(name="{v.basename}"{#if v.required}, required = {v.required}{/if}) + {/if} + {#if m.description} + /** + * {m.description} + **/ + {/if} + {#if !v.isEnum} + @jakarta.ws.rs.QueryParam("{v.name}") + {/if} + {#if v.isContainer} + private {v.datatypeWithEnum} {v.name}{#if v.required} = {v.defaultValue}{#else} = null{/if}; + {#else} + private {v.datatypeWithEnum} {v.name}{#if v.defaultValue} = {v.defaultValue}{/if}; + {/if} + {/if} + {/for} + + {#for v in m.vars} + {#if !v.deprecated || openapi:genDeprecatedModelAttr(package, m.classname, codegen)} + /** + {#if v.description} + * {v.description} + {#else} + * Get {v.name} + {/if} + {#if v.minimum} + * minimum: {v.minimum} + {/if} + {#if v.maximum} + * maximum: {v.maximum} + {/if} + * @return {v.name} + **/ + {#if !withXml} + @JsonProperty("{v.baseName}") + {/if} + {#for ext in v.vendorExtensions.x-extra-annotation.orEmpty} + {ext} + {/for} + {#if v.useBeanValidation}{#include beanValidation.qute p=v/}{/if} + {#if v.isEnum && v.isContainer}public {v.datatypeWithEnum} {v.getter}(){ + return {v.name}; + }{#else if v.isEnum && !v.isArray && !v.isMap}public {v.datatypeWithEnum} {v.getter}() { + return {v.name}; + }{#else if !v.isEnum && !v.isArray && !v.isMap}public {v.datatype} {v.getter}() { + return {v.name}; + }{#else if !v.isEnum && (v.isArray || v.isMap)}public {v.datatype} {v.getter}() { + return {v.name}; + }{#else if !v.isEnum}public {v.datatype} {v.getter}() { + return {v.name}; + }{/if} + + {#if !v.isReadOnly} + /** + * Set {v.name} + **/ + public void {v.setter}({v.datatypeWithEnum} {v.name}) { + this.{v.name} = {v.name}; + } + + public {m.classname}QueryParam {v.name}({v.datatypeWithEnum} {v.name}) { + this.{v.name} = {v.name}; + return this; + } + {#if v.isArray} + public {m.classname}QueryParam add{v.nameInCamelCase}Item({v.items.datatypeWithEnum} {v.name}Item) { + this.{v.name}.add({v.name}Item); + return this; + } + {/if} + {#if v.isMap} + public {m.classname}QueryParam put{v.nameInCamelCase}Item(String key, {v.items.datatypeWithEnum} {v.name}Item) { + this.{v.name}.put(key, {v.name}Item); + return this; + } + {/if} + {/if} + + {/if} + {/for} + /** + * Create a string representation of this pojo. + **/ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class {m.classname}QueryParam {\n"); + {#if m.parent} + sb.append(" ").append(toIndentedString(super.toString())).append("\n");{/if} + {#for v in m.vars} + {#if !v.deprecated || openapi:genDeprecatedModelAttr(package, m.classname, codegen)} + sb.append(" {v.name}: ").append(toIndentedString({v.name})).append("\n"); + {/if} + {/for} + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private static String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + } \ No newline at end of file diff --git a/integration-tests/custom-templates/src/test/java/io/quarkiverse/openapi/generator/it/QuarkusSimpleOpenApiTest.java b/integration-tests/custom-templates/src/test/java/io/quarkiverse/openapi/generator/it/QuarkusSimpleOpenApiTest.java new file mode 100644 index 000000000..bfa63566c --- /dev/null +++ b/integration-tests/custom-templates/src/test/java/io/quarkiverse/openapi/generator/it/QuarkusSimpleOpenApiTest.java @@ -0,0 +1,19 @@ +package io.quarkiverse.openapi.generator.it; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; +import org.openapi.quarkus.quarkus_simple_openapi_yaml.api.ReactiveGreetingResourceApi; +import org.openapi.quarkus.quarkus_simple_openapi_yaml.model.CloudEvent; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +class QuarkusSimpleOpenApiTest { + + @Test + void apiIsBeingGenerated() throws NoSuchFieldException, NoSuchMethodException { + assertThat(ReactiveGreetingResourceApi.class.getMethod("myCustomMethod")).isNotNull(); + assertThat(CloudEvent.CloudEventQueryParam.class.getField("myCustomQueryParamAttribute")).isNotNull(); + } +} diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 683100f72..8176e93ec 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -14,6 +14,7 @@ beanparam change-directory circuit-breaker + custom-templates enum-property exclude generation-input