From 97ddcc44c40341acd9538e8f58812bc87fa851ec Mon Sep 17 00:00:00 2001 From: Ricardo Zanini <1538000+ricardozanini@users.noreply.github.com> Date: Tue, 8 Apr 2025 09:54:55 -0300 Subject: [PATCH] Fix #1065 - Add Dynamic URL support (#1094) * Add @Url annotation by default on every api.qute generated methods Signed-off-by: Ricardo Zanini * Fix #1065 - Add Dynamic URL support Signed-off-by: Ricardo Zanini * Remove debug leftovers Signed-off-by: Ricardo Zanini --------- Signed-off-by: Ricardo Zanini --- .../generator/deployment/CodegenConfig.java | 3 +- .../generator/deployment/SpecItemConfig.java | 11 + .../codegen/OpenApiGeneratorCodeGenBase.java | 3 + .../OpenApiClientGeneratorWrapper.java | 6 + .../templates/libraries/microprofile/api.qute | 6 +- .../OpenApiClientGeneratorWrapperTest.java | 447 +++++++----------- docs/modules/ROOT/pages/client.adoc | 5 + .../ROOT/pages/includes/dynamic-url.adoc | 16 + .../includes/quarkus-openapi-generator.adoc | 21 + ...i-generator_quarkus.openapi-generator.adoc | 21 + docs/pom.xml | 2 +- 11 files changed, 270 insertions(+), 271 deletions(-) create mode 100644 docs/modules/ROOT/pages/includes/dynamic-url.adoc diff --git a/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/CodegenConfig.java b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/CodegenConfig.java index 883a6cf95..d926ac2f5 100644 --- a/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/CodegenConfig.java +++ b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/CodegenConfig.java @@ -77,7 +77,8 @@ enum ConfigName { GENERATE_MODELS("generate-models"), BEAN_VALIDATION("use-bean-validation"), SERIALIZABLE_MODEL("serializable-model"), - EQUALS_HASHCODE("equals-hashcode"); + EQUALS_HASHCODE("equals-hashcode"), + USE_DYNAMIC_URL("use-dynamic-url"); private final String name; diff --git a/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/SpecItemConfig.java b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/SpecItemConfig.java index 32ae2f30b..8c6652124 100644 --- a/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/SpecItemConfig.java +++ b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/SpecItemConfig.java @@ -2,6 +2,7 @@ import java.util.Optional; +import io.smallrye.config.WithDefault; import io.smallrye.config.WithName; /* @@ -68,4 +69,14 @@ public interface SpecItemConfig extends CommonItemConfig { @WithName("serializable-model") Optional serializableModel(); + /** + * Whether to enable Dynamic URLs on APIs methods. + * By enabling this property every method on `RestClients` will be annotated with `io.quarkus.rest.client.reactive.Url`. + * + * @see Dynamic base URLs + */ + @WithName("use-dynamic-url") + @WithDefault("false") + Optional useDynamicUrl(); + } diff --git a/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/OpenApiGeneratorCodeGenBase.java b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/OpenApiGeneratorCodeGenBase.java index 079189d8a..22bcad244 100644 --- a/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/OpenApiGeneratorCodeGenBase.java +++ b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/OpenApiGeneratorCodeGenBase.java @@ -339,6 +339,9 @@ protected void generate(OpenApiGeneratorOptions options) { OpenApiClientGeneratorWrapper.SUPPORTS_ADDITIONAL_PROPERTIES_AS_ATTRIBUTE, additionalPropertiesAsAttribute.toString()); + getValues(smallRyeConfig, openApiFilePath, CodegenConfig.ConfigName.USE_DYNAMIC_URL, Boolean.class) + .ifPresent(generator::withUseDynamicUrl); + generator.generate(basePackage); } diff --git a/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/wrapper/OpenApiClientGeneratorWrapper.java b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/wrapper/OpenApiClientGeneratorWrapper.java index 3ce73326a..9c1525bc1 100644 --- a/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/wrapper/OpenApiClientGeneratorWrapper.java +++ b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/wrapper/OpenApiClientGeneratorWrapper.java @@ -111,6 +111,7 @@ private void setDefaults() { this.configurator.addAdditionalProperty("verbose", FALSE); this.configurator.addAdditionalProperty(CodegenConstants.SERIALIZABLE_MODEL, FALSE); this.configurator.addAdditionalProperty("equals-hashcode", TRUE); + this.configurator.addAdditionalProperty("use-dynamic-url", FALSE); } /** @@ -323,6 +324,11 @@ public OpenApiClientGeneratorWrapper withModelNamePrefix(final String modelNameP return this; } + public OpenApiClientGeneratorWrapper withUseDynamicUrl(final Boolean useDynamicUrl) { + this.configurator.addAdditionalProperty("use-dynamic-url", useDynamicUrl); + return this; + } + /** * Main entrypoint, or where to generate the files based on the given base package. * diff --git a/client/deployment/src/main/resources/templates/libraries/microprofile/api.qute b/client/deployment/src/main/resources/templates/libraries/microprofile/api.qute index 5948d8a68..7b4ddc519 100644 --- a/client/deployment/src/main/resources/templates/libraries/microprofile/api.qute +++ b/client/deployment/src/main/resources/templates/libraries/microprofile/api.qute @@ -126,7 +126,11 @@ public interface {classname} { {#if additionalRequestArgs} {#for arg in additionalRequestArgs}{! !}{arg}{#if arg_hasNext}, {/if}{/for}{! - !}{#if op.hasFormParams || op.allParams},{/if} + !}{#if op.allParams || op.hasFormParams},{/if} + {/if} + {#if is-resteasy-reactive && use-dynamic-url} + // See https://quarkus.io/version/3.20/guides/rest-client#dynamic-base-urls + @io.quarkus.rest.client.reactive.Url String dynUrl{#if op.allParams || op.hasFormParams},{/if} {/if} {#if op.hasFormParams} {#if is-resteasy-reactive} diff --git a/client/deployment/src/test/java/io/quarkiverse/openapi/generator/deployment/wrapper/OpenApiClientGeneratorWrapperTest.java b/client/deployment/src/test/java/io/quarkiverse/openapi/generator/deployment/wrapper/OpenApiClientGeneratorWrapperTest.java index f354135bd..6fde9d9ac 100644 --- a/client/deployment/src/test/java/io/quarkiverse/openapi/generator/deployment/wrapper/OpenApiClientGeneratorWrapperTest.java +++ b/client/deployment/src/test/java/io/quarkiverse/openapi/generator/deployment/wrapper/OpenApiClientGeneratorWrapperTest.java @@ -36,7 +36,6 @@ import com.github.javaparser.ast.expr.Expression; import com.github.javaparser.ast.expr.MemberValuePair; import com.github.javaparser.ast.expr.SimpleName; -import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr; import com.github.javaparser.ast.nodeTypes.NodeWithName; import com.github.javaparser.ast.type.Type; @@ -70,9 +69,10 @@ void verifyOAuthDuplicateAnnotationOnCompositeAuthProvider() throws URISyntaxExc .orElseThrow(() -> new AssertionError("Class not found in the file")); // Collect all OauthAuthenticationMarker annotations - long oauthAnnotationsCount = classDeclaration.getAnnotations().stream() - .filter(annotation -> annotation.getNameAsString() - .equals("io.quarkiverse.openapi.generator.markers.OauthAuthenticationMarker")) + long oauthAnnotationsCount = classDeclaration + .getAnnotations().stream().filter( + annotation -> annotation.getNameAsString() + .equals("io.quarkiverse.openapi.generator.markers.OauthAuthenticationMarker")) .filter(Expression::isNormalAnnotationExpr) .filter(annotation -> annotation .findFirst(MemberValuePair.class, @@ -121,14 +121,13 @@ void verifyAllOfDiscriminatorGeneration() throws java.net.URISyntaxException, Fi assertNotNull(generatedFiles); assertFalse(generatedFiles.isEmpty()); - final Optional classWithDiscriminator = generatedFiles.stream() - .filter(f -> f.getName().startsWith("Thing.java")).findFirst(); + final Optional classWithDiscriminator = generatedFiles.stream().filter(f -> f.getName().startsWith("Thing.java")) + .findFirst(); assertThat(classWithDiscriminator).isPresent(); final CompilationUnit compilationUnit = StaticJavaParser.parse(classWithDiscriminator.orElseThrow()); assertThat(compilationUnit.findFirst(ClassOrInterfaceDeclaration.class) - .flatMap(first -> first.getAnnotationByClass(com.fasterxml.jackson.annotation.JsonSubTypes.class))) - .isPresent(); + .flatMap(first -> first.getAnnotationByClass(com.fasterxml.jackson.annotation.JsonSubTypes.class))).isPresent(); } @Test @@ -147,8 +146,7 @@ void verifyFlink() throws URISyntaxException, FileNotFoundException { assertThat(vars).isNotEmpty(); // This openApi file has a duplicated field - assertThat(vars.stream() - .filter(v -> "failureCause".equals(v.getNameAsString())).count()).isEqualTo(4); + assertThat(vars.stream().filter(v -> "failureCause".equals(v.getNameAsString())).count()).isEqualTo(4); } @Test @@ -203,8 +201,7 @@ void verifyAuthBasicWithMissingSecurityDefinition(String defaultSecurityScheme) List methodDeclarations = compilationUnit.findAll(MethodDeclaration.class); assertThat(methodDeclarations).isNotEmpty(); - Optional initMethod = methodDeclarations.stream() - .filter(m -> m.getNameAsString().equals("filter")) + Optional initMethod = methodDeclarations.stream().filter(m -> m.getNameAsString().equals("filter")) .findAny(); assertThat(initMethod).isPresent(); @@ -221,17 +218,14 @@ void verifyAuthBearerGenerated() throws URISyntaxException { @Test void verifyEnumGeneration() throws URISyntaxException, FileNotFoundException { - final List generatedFiles = createGeneratorWrapper("issue-28.yaml") - .generate("org.issue28"); + final List generatedFiles = createGeneratorWrapper("issue-28.yaml").generate("org.issue28"); final Optional enumFile = generatedFiles.stream() .filter(f -> f.getName().endsWith("ConnectorNamespaceState.java")).findFirst(); assertThat(enumFile).isPresent(); final CompilationUnit cu = StaticJavaParser.parse(enumFile.orElseThrow()); final List constants = cu.findAll(EnumConstantDeclaration.class); - assertThat(constants) - .hasSize(3) - .extracting(EnumConstantDeclaration::getNameAsString) + assertThat(constants).hasSize(3).extracting(EnumConstantDeclaration::getNameAsString) .containsExactlyInAnyOrder("DISCONNECTED", "READY", "DELETING"); } @@ -239,23 +233,20 @@ void verifyEnumGeneration() throws URISyntaxException, FileNotFoundException { void verifyDeprecatedFields() throws URISyntaxException, FileNotFoundException { final Map codegenConfig = ClassCodegenConfigParser .parse(MockConfigUtils.getTestConfig("/codegen/application.properties"), "org.issue38"); - final List generatedFiles = this.createGeneratorWrapper("issue-38.yaml") - .withClassesCodeGenConfig(codegenConfig) + final List generatedFiles = this.createGeneratorWrapper("issue-38.yaml").withClassesCodeGenConfig(codegenConfig) .generate("org.issue38"); // we have two attributes that will be generated with the same name, one of them is deprecated - final Optional metaV1Condition = generatedFiles.stream() - .filter(f -> f.getName().endsWith("MetaV1Condition.java")).findFirst(); + final Optional metaV1Condition = generatedFiles.stream().filter(f -> f.getName().endsWith("MetaV1Condition.java")) + .findFirst(); assertThat(metaV1Condition).isPresent(); final CompilationUnit cu = StaticJavaParser.parse(metaV1Condition.orElseThrow()); final List fields = cu.findAll(FieldDeclaration.class); assertThat(fields).extracting(FieldDeclaration::getVariables).hasSize(10); - assertThat(fields.stream() - .flatMap(v -> v.getVariables().stream()) - .anyMatch(f -> f.getNameAsString().equals("lastTransitionTime"))) - .isTrue(); + assertThat(fields.stream().flatMap(v -> v.getVariables().stream()) + .anyMatch(f -> f.getNameAsString().equals("lastTransitionTime"))).isTrue(); // this one we optionally removed the deprecated attribute final Optional connectorDeploymentSpec = generatedFiles.stream() @@ -264,10 +255,8 @@ void verifyDeprecatedFields() throws URISyntaxException, FileNotFoundException { final CompilationUnit cu2 = StaticJavaParser.parse(connectorDeploymentSpec.orElseThrow()); final List fields2 = cu2.findAll(FieldDeclaration.class); - assertThat(fields2.stream() - .flatMap(v -> v.getVariables().stream()) - .anyMatch(f -> f.getNameAsString().equals("allowUpgrade"))) - .isFalse(); + assertThat(fields2.stream().flatMap(v -> v.getVariables().stream()) + .anyMatch(f -> f.getNameAsString().equals("allowUpgrade"))).isFalse(); // this class has a deprecated attribute, so we check the default behavior final Optional connectorDeploymentStatus = generatedFiles.stream() @@ -276,24 +265,19 @@ void verifyDeprecatedFields() throws URISyntaxException, FileNotFoundException { final CompilationUnit cu3 = StaticJavaParser.parse(connectorDeploymentStatus.orElseThrow()); final List fields3 = cu3.findAll(FieldDeclaration.class); - assertThat(fields3.stream() - .flatMap(v -> v.getVariables().stream()) - .anyMatch(f -> f.getNameAsString().equals("availableUpgrades"))) - .isTrue(); + assertThat(fields3.stream().flatMap(v -> v.getVariables().stream()) + .anyMatch(f -> f.getNameAsString().equals("availableUpgrades"))).isTrue(); } @Test void verifyDeprecatedOperations() throws URISyntaxException, FileNotFoundException { final Map codegenConfig = ClassCodegenConfigParser .parse(MockConfigUtils.getTestConfig("/deprecated/application.properties"), "org.deprecated"); - List generatedFiles = this.createGeneratorWrapper("deprecated.json") - .withClassesCodeGenConfig(codegenConfig) + List generatedFiles = this.createGeneratorWrapper("deprecated.json").withClassesCodeGenConfig(codegenConfig) .generate("org.deprecated"); assertFalse(generatedFiles.isEmpty()); - Optional file = generatedFiles.stream() - .filter(f -> f.getName().endsWith("DefaultApi.java")) - .findAny(); + Optional file = generatedFiles.stream().filter(f -> f.getName().endsWith("DefaultApi.java")).findAny(); assertThat(file).isNotEmpty(); @@ -309,14 +293,12 @@ void verifyDeprecatedOperations() throws URISyntaxException, FileNotFoundExcepti // hello operation is NOT deprecated and should be generated Optional helloMethod = methodDeclarations.stream() - .filter(m -> m.getNameAsString().equals("helloGet")) - .findAny(); + .filter(m -> m.getNameAsString().equals("helloGet")).findAny(); assertThat(helloMethod).isNotEmpty(); // bye operation is deprecated and should NOT be generated - Optional byeMethod = methodDeclarations.stream() - .filter(m -> m.getNameAsString().equals("byeGet")) + Optional byeMethod = methodDeclarations.stream().filter(m -> m.getNameAsString().equals("byeGet")) .findAny(); assertThat(byeMethod).isEmpty(); @@ -329,9 +311,7 @@ void checkAnnotations() throws URISyntaxException, FileNotFoundException { assertNotNull(restClientFiles); assertFalse(restClientFiles.isEmpty()); - Optional file = restClientFiles.stream() - .filter(f -> f.getName().endsWith("DefaultApi.java")) - .findAny(); + Optional file = restClientFiles.stream().filter(f -> f.getName().endsWith("DefaultApi.java")).findAny(); assertThat(file).isNotEmpty(); @@ -358,30 +338,23 @@ void checkAnnotations() throws URISyntaxException, FileNotFoundException { }); Optional byeMethod = methodDeclarations.stream() - .filter(m -> m.getNameAsString().equals(byeMethodGet)) - .findAny(); + .filter(m -> m.getNameAsString().equals(byeMethodGet)).findAny(); assertThat(byeMethod).isNotEmpty(); - assertThat(byeMethod.orElseThrow()) - .hasCircuitBreakerAnnotation() - .doesNotHaveAnyCircuitBreakerAttribute(); + assertThat(byeMethod.orElseThrow()).hasCircuitBreakerAnnotation().doesNotHaveAnyCircuitBreakerAttribute(); - methodDeclarations.stream() - .filter(m -> !m.getNameAsString().equals(byeMethodGet)) + methodDeclarations.stream().filter(m -> !m.getNameAsString().equals(byeMethodGet)) .forEach(m -> assertThat(m).doesNotHaveCircuitBreakerAnnotation()); } @Test void verifyMultipartFormAnnotationIsGeneratedForParameter() throws URISyntaxException, FileNotFoundException { - List generatedFiles = createGeneratorWrapper("multipart-openapi.yml") - .withSkipFormModelConfig("false") + List generatedFiles = createGeneratorWrapper("multipart-openapi.yml").withSkipFormModelConfig("false") .generate("org.acme"); assertThat(generatedFiles).isNotEmpty(); - Optional file = generatedFiles.stream() - .filter(f -> f.getName().endsWith("UserProfileDataApi.java")) - .findAny(); + Optional file = generatedFiles.stream().filter(f -> f.getName().endsWith("UserProfileDataApi.java")).findAny(); assertThat(file).isPresent(); CompilationUnit compilationUnit = StaticJavaParser.parse(file.orElseThrow()); @@ -389,8 +362,7 @@ void verifyMultipartFormAnnotationIsGeneratedForParameter() throws URISyntaxExce assertThat(methodDeclarations).isNotEmpty(); Optional multipartPostMethod = methodDeclarations.stream() - .filter(m -> m.getNameAsString().equals("postUserProfileData")) - .findAny(); + .filter(m -> m.getNameAsString().equals("postUserProfileData")).findAny(); assertThat(multipartPostMethod).isPresent(); List parameters = multipartPostMethod.orElseThrow().getParameters(); @@ -402,21 +374,16 @@ void verifyMultipartFormAnnotationIsGeneratedForParameter() throws URISyntaxExce @Test void verifyMultipartPojoGeneratedAndFieldsHaveAnnotations() throws URISyntaxException, FileNotFoundException { - List generatedFiles = createGeneratorWrapper("multipart-openapi.yml") - .withSkipFormModelConfig("false") + List generatedFiles = createGeneratorWrapper("multipart-openapi.yml").withSkipFormModelConfig("false") .generate("org.acme"); assertFalse(generatedFiles.isEmpty()); - Optional file = generatedFiles.stream() - .filter(f -> f.getName().endsWith("UserProfileDataApi.java")) - .findAny(); + Optional file = generatedFiles.stream().filter(f -> f.getName().endsWith("UserProfileDataApi.java")).findAny(); assertThat(file).isNotEmpty(); CompilationUnit compilationUnit = StaticJavaParser.parse(file.orElseThrow()); Optional multipartPojo = compilationUnit.findAll(ClassOrInterfaceDeclaration.class) - .stream() - .filter(c -> c.getNameAsString().equals("PostUserProfileDataMultipartForm")) - .findAny(); + .stream().filter(c -> c.getNameAsString().equals("PostUserProfileDataMultipartForm")).findAny(); assertThat(multipartPojo).isNotEmpty(); List fields = multipartPojo.orElseThrow().getFields(); @@ -433,20 +400,15 @@ void verifyMultipartPojoGeneratedAndFieldsHaveAnnotations() throws URISyntaxExce @Test void shouldMapFileTypeToFullyQualifiedInputStream() throws URISyntaxException, FileNotFoundException { List generatedFiles = createGeneratorWrapper("multipart-openapi.yml") - .withTypeMappings(Map.of("File", "java.io.InputStream")) - .generate("org.acme"); + .withTypeMappings(Map.of("File", "java.io.InputStream")).generate("org.acme"); assertFalse(generatedFiles.isEmpty()); - Optional file = generatedFiles.stream() - .filter(f -> f.getName().endsWith("UserProfileDataApi.java")) - .findAny(); + Optional file = generatedFiles.stream().filter(f -> f.getName().endsWith("UserProfileDataApi.java")).findAny(); assertThat(file).isNotEmpty(); CompilationUnit compilationUnit = StaticJavaParser.parse(file.orElseThrow()); Optional multipartPojo = compilationUnit.findAll(ClassOrInterfaceDeclaration.class) - .stream() - .filter(c -> c.getNameAsString().equals("PostUserProfileDataMultipartForm")) - .findAny(); + .stream().filter(c -> c.getNameAsString().equals("PostUserProfileDataMultipartForm")).findAny(); assertThat(multipartPojo).isNotEmpty(); Optional fileUploadVariable = findVariableByName(multipartPojo.orElseThrow().getFields(), @@ -456,76 +418,55 @@ void shouldMapFileTypeToFullyQualifiedInputStream() throws URISyntaxException, F @Test void shouldReplaceFileImportWithInputStream() throws URISyntaxException, FileNotFoundException { - List generatedFiles = createGeneratorWrapper("multipart-openapi.yml") - .withSkipFormModelConfig("false") - .withTypeMappings(Map.of("File", "InputStream")) - .withImportMappings(Map.of("File", "java.io.InputStream")) + List generatedFiles = createGeneratorWrapper("multipart-openapi.yml").withSkipFormModelConfig("false") + .withTypeMappings(Map.of("File", "InputStream")).withImportMappings(Map.of("File", "java.io.InputStream")) .generate("org.acme"); assertFalse(generatedFiles.isEmpty()); - Optional file = generatedFiles.stream() - .filter(f -> f.getName().endsWith("UserProfileDataApi.java")) - .findAny(); + Optional file = generatedFiles.stream().filter(f -> f.getName().endsWith("UserProfileDataApi.java")).findAny(); assertThat(file).isNotEmpty(); CompilationUnit compilationUnit = StaticJavaParser.parse(file.orElseThrow()); Optional multipartPojo = compilationUnit.findAll(ClassOrInterfaceDeclaration.class) - .stream() - .filter(c -> c.getNameAsString().equals("PostUserProfileDataMultipartForm")) - .findAny(); + .stream().filter(c -> c.getNameAsString().equals("PostUserProfileDataMultipartForm")).findAny(); assertThat(multipartPojo).isNotEmpty(); Optional fileUploadVariable = findVariableByName(multipartPojo.orElseThrow().getFields(), "profileImage"); assertThat(fileUploadVariable.orElseThrow().getType().asString()).isEqualTo("InputStream"); - List imports = compilationUnit.findAll(ImportDeclaration.class) - .stream() - .map(importDeclaration -> importDeclaration.getName().asString()) - .collect(Collectors.toList()); - assertThat(imports).contains("java.io.InputStream") - .doesNotContain("java.io.File"); + List imports = compilationUnit.findAll(ImportDeclaration.class).stream() + .map(importDeclaration -> importDeclaration.getName().asString()).collect(Collectors.toList()); + assertThat(imports).contains("java.io.InputStream").doesNotContain("java.io.File"); } @Test void withoutAnyTypeOrImportMappingsItShouldGenerateUsingJava8DatesAndTimes() throws URISyntaxException, FileNotFoundException { - List generatedFiles = createGeneratorWrapper("datetime-regression.yml") - .generate("org.datetime.regression"); + List generatedFiles = createGeneratorWrapper("datetime-regression.yml").generate("org.datetime.regression"); - Optional file = generatedFiles.stream() - .filter(f -> f.getName().endsWith("SomeName.java")) - .findAny(); + Optional file = generatedFiles.stream().filter(f -> f.getName().endsWith("SomeName.java")).findAny(); assertThat(file).isNotEmpty(); CompilationUnit compilationUnit = StaticJavaParser.parse(file.orElseThrow()); List classes = compilationUnit.findAll(ClassOrInterfaceDeclaration.class); assertThat(classes).hasSize(2); ClassOrInterfaceDeclaration generatedPojoClass = classes.get(0); - verifyGeneratedDateAndTimeTypes( - generatedPojoClass, - Map.of( - "someDate", "LocalDate", - "someDateTime", "OffsetDateTime", - "dateArray", "List", - "dateTimeArray", "List", - "dateSet", "Set", - "dateTimeSet", "Set", - "dateMap", "Map", - "dateTimeMap", "Map")); - assertThat(compilationUnit.getImports().stream().map(NodeWithName::getNameAsString)) - .contains("java.time.LocalDate", "java.time.OffsetDateTime"); + verifyGeneratedDateAndTimeTypes(generatedPojoClass, + Map.of("someDate", "LocalDate", "someDateTime", "OffsetDateTime", "dateArray", "List", + "dateTimeArray", "List", "dateSet", "Set", "dateTimeSet", + "Set", "dateMap", "Map", "dateTimeMap", + "Map")); + assertThat(compilationUnit.getImports().stream().map(NodeWithName::getNameAsString)).contains("java.time.LocalDate", + "java.time.OffsetDateTime"); } @Test void shouldBeAbleToEnableMutiny() throws URISyntaxException, FileNotFoundException { - List generatedFiles = createGeneratorWrapper("simple-openapi.json") - .withMutiny(true) + List generatedFiles = createGeneratorWrapper("simple-openapi.json").withMutiny(true) .generate("org.mutiny.enabled"); - Optional file = generatedFiles.stream() - .filter(f -> f.getName().endsWith("DefaultApi.java")) - .findAny(); + Optional file = generatedFiles.stream().filter(f -> f.getName().endsWith("DefaultApi.java")).findAny(); assertThat(file).isNotEmpty(); CompilationUnit compilationUnit = StaticJavaParser.parse(file.orElseThrow()); List methodDeclarations = compilationUnit.findAll(MethodDeclaration.class); @@ -541,14 +482,10 @@ void shouldBeAbleToEnableMutiny() throws URISyntaxException, FileNotFoundExcepti @Test void shouldBeAbleToApplyMutinyOnSpecificEndpoints() throws URISyntaxException, FileNotFoundException { - List generatedFiles = createGeneratorWrapper("simple-openapi.json") - .withMutiny(true) - .withMutinyReturnTypes(Map.of("helloMethod", "Uni", "Bye method_get", "Multi")) - .generate("org.mutiny.enabled"); + List generatedFiles = createGeneratorWrapper("simple-openapi.json").withMutiny(true) + .withMutinyReturnTypes(Map.of("helloMethod", "Uni", "Bye method_get", "Multi")).generate("org.mutiny.enabled"); - Optional file = generatedFiles.stream() - .filter(f -> f.getName().endsWith("DefaultApi.java")) - .findAny(); + Optional file = generatedFiles.stream().filter(f -> f.getName().endsWith("DefaultApi.java")).findAny(); assertThat(file).isNotEmpty(); CompilationUnit compilationUnit = StaticJavaParser.parse(file.orElseThrow()); List methodDeclarations = compilationUnit.findAll(MethodDeclaration.class); @@ -558,10 +495,8 @@ void shouldBeAbleToApplyMutinyOnSpecificEndpoints() throws URISyntaxException, F "helloMethod"); Optional byeMethodGetDeclaration = getMethodDeclarationByIdentifier(methodDeclarations, "byeMethodGet"); - Optional getUserDeclaration = getMethodDeclarationByIdentifier(methodDeclarations, - "getUser"); - Optional getNumbersDeclaration = getMethodDeclarationByIdentifier(methodDeclarations, - "getNumbers"); + Optional getUserDeclaration = getMethodDeclarationByIdentifier(methodDeclarations, "getUser"); + Optional getNumbersDeclaration = getMethodDeclarationByIdentifier(methodDeclarations, "getNumbers"); assertThat(helloMethodDeclaration).hasValueSatisfying( methodDeclaration -> assertEquals("io.smallrye.mutiny.Uni", methodDeclaration.getType().toString())); @@ -569,21 +504,18 @@ void shouldBeAbleToApplyMutinyOnSpecificEndpoints() throws URISyntaxException, F methodDeclaration -> assertEquals("io.smallrye.mutiny.Multi", methodDeclaration.getType().toString())); assertThat(getUserDeclaration).hasValueSatisfying( methodDeclaration -> assertEquals("GetUser200Response", methodDeclaration.getType().toString())); - assertThat(getNumbersDeclaration).hasValueSatisfying( - methodDeclaration -> assertEquals("List", methodDeclaration.getType().toString())); + assertThat(getNumbersDeclaration) + .hasValueSatisfying(methodDeclaration -> assertEquals("List", methodDeclaration.getType().toString())); } @Test void shouldBeAbleToApplyMutinyOnSpecificEndpointsWhenUserDefineWrongConfiguration() throws URISyntaxException, FileNotFoundException { - List generatedFiles = createGeneratorWrapper("simple-openapi.json") - .withMutiny(true) + List generatedFiles = createGeneratorWrapper("simple-openapi.json").withMutiny(true) .withMutinyReturnTypes(Map.of("helloMethod", "Uni", "Bye method_get", "BadConfig")) .generate("org.mutiny.enabled"); - Optional file = generatedFiles.stream() - .filter(f -> f.getName().endsWith("DefaultApi.java")) - .findAny(); + Optional file = generatedFiles.stream().filter(f -> f.getName().endsWith("DefaultApi.java")).findAny(); assertThat(file).isNotEmpty(); CompilationUnit compilationUnit = StaticJavaParser.parse(file.orElseThrow()); List methodDeclarations = compilationUnit.findAll(MethodDeclaration.class); @@ -593,10 +525,8 @@ void shouldBeAbleToApplyMutinyOnSpecificEndpointsWhenUserDefineWrongConfiguratio "helloMethod"); Optional byeMethodGetDeclaration = getMethodDeclarationByIdentifier(methodDeclarations, "byeMethodGet"); - Optional getUserDeclaration = getMethodDeclarationByIdentifier(methodDeclarations, - "getUser"); - Optional getNumbersDeclaration = getMethodDeclarationByIdentifier(methodDeclarations, - "getNumbers"); + Optional getUserDeclaration = getMethodDeclarationByIdentifier(methodDeclarations, "getUser"); + Optional getNumbersDeclaration = getMethodDeclarationByIdentifier(methodDeclarations, "getNumbers"); assertThat(helloMethodDeclaration).hasValueSatisfying( methodDeclaration -> assertEquals("io.smallrye.mutiny.Uni", methodDeclaration.getType().toString())); @@ -604,54 +534,40 @@ void shouldBeAbleToApplyMutinyOnSpecificEndpointsWhenUserDefineWrongConfiguratio methodDeclaration -> assertEquals("io.smallrye.mutiny.Uni", methodDeclaration.getType().toString())); assertThat(getUserDeclaration).hasValueSatisfying( methodDeclaration -> assertEquals("GetUser200Response", methodDeclaration.getType().toString())); - assertThat(getNumbersDeclaration).hasValueSatisfying( - methodDeclaration -> assertEquals("List", methodDeclaration.getType().toString())); + assertThat(getNumbersDeclaration) + .hasValueSatisfying(methodDeclaration -> assertEquals("List", methodDeclaration.getType().toString())); } @Test void shouldBeAbleToAddCustomDateAndTimeMappings() throws URISyntaxException, FileNotFoundException { List generatedFiles = createGeneratorWrapper("datetime-regression.yml") - .withTypeMappings(Map.of( - "date", "ThaiBuddhistDate", - "DateTime", "Instant")) - .withImportMappings(Map.of( - "ThaiBuddhistDate", "java.time.chrono.ThaiBuddhistDate", - "Instant", "java.time.Instant")) + .withTypeMappings(Map.of("date", "ThaiBuddhistDate", "DateTime", "Instant")) + .withImportMappings( + Map.of("ThaiBuddhistDate", "java.time.chrono.ThaiBuddhistDate", "Instant", "java.time.Instant")) .generate("org.datetime.mappings"); - Optional file = generatedFiles.stream() - .filter(f -> f.getName().endsWith("SomeName.java")) - .findAny(); + Optional file = generatedFiles.stream().filter(f -> f.getName().endsWith("SomeName.java")).findAny(); assertThat(file).isNotEmpty(); CompilationUnit compilationUnit = StaticJavaParser.parse(file.orElseThrow()); List classes = compilationUnit.findAll(ClassOrInterfaceDeclaration.class); assertThat(classes).hasSize(2); ClassOrInterfaceDeclaration generatedPojoClass = classes.get(0); - verifyGeneratedDateAndTimeTypes( - generatedPojoClass, - Map.of( - "someDate", "ThaiBuddhistDate", - "someDateTime", "Instant", - "dateArray", "List", - "dateTimeArray", "List", - "dateSet", "Set", - "dateTimeSet", "Set", - "dateMap", "Map", - "dateTimeMap", "Map")); + verifyGeneratedDateAndTimeTypes(generatedPojoClass, + Map.of("someDate", "ThaiBuddhistDate", "someDateTime", "Instant", "dateArray", "List", + "dateTimeArray", "List", "dateSet", "Set", "dateTimeSet", "Set", + "dateMap", "Map", "dateTimeMap", "Map")); assertThat(compilationUnit.getImports().stream().map(NodeWithName::getNameAsString)) .contains("java.time.chrono.ThaiBuddhistDate", "java.time.Instant"); } - private void verifyGeneratedDateAndTimeTypes( - ClassOrInterfaceDeclaration classDeclaration, + private void verifyGeneratedDateAndTimeTypes(ClassOrInterfaceDeclaration classDeclaration, Map expectedFieldsAndTypes) { expectedFieldsAndTypes.forEach((fieldName, expectedFieldType) -> { Optional fieldDeclaration = classDeclaration.getFieldByName(fieldName); assertThat(fieldDeclaration).isPresent(); Optional fieldVariableDeclaration = fieldDeclaration.orElseThrow().getChildNodes().stream() - .filter(it -> it instanceof VariableDeclarator) - .findFirst(); + .filter(it -> it instanceof VariableDeclarator).findFirst(); assertThat(fieldVariableDeclaration).isPresent(); Type fieldType = ((VariableDeclarator) fieldVariableDeclaration.orElseThrow()).getType(); @@ -666,19 +582,17 @@ void verifyAdditionalModelTypeAnnotations() throws URISyntaxException { .generate("org.additionalmodeltypeannotations"); assertFalse(generatedFiles.isEmpty()); - generatedFiles.stream() - .filter(file -> file.getPath().matches(".*/model/.*.java")) + generatedFiles.stream().filter(file -> file.getPath().matches(".*/model/.*.java")) .forEach(this::verifyModelAdditionalAnnotations); } private void verifyModelAdditionalAnnotations(File file) { try { CompilationUnit compilationUnit = StaticJavaParser.parse(file); - compilationUnit.findAll(ClassOrInterfaceDeclaration.class) - .forEach(c -> { - assertThat(c.getAnnotationByName("Foo")).isPresent(); - assertThat(c.getAnnotationByName("Bar")).isPresent(); - }); + compilationUnit.findAll(ClassOrInterfaceDeclaration.class).forEach(c -> { + assertThat(c.getAnnotationByName("Foo")).isPresent(); + assertThat(c.getAnnotationByName("Bar")).isPresent(); + }); } catch (FileNotFoundException e) { throw new RuntimeException(e.getMessage()); } @@ -686,25 +600,22 @@ private void verifyModelAdditionalAnnotations(File file) { @Test void verifyAdditionalApiTypeAnnotations() throws URISyntaxException { - List generatedFiles = createGeneratorWrapper("petstore-openapi.json") - .withEnabledSecurityGeneration(false) + List generatedFiles = createGeneratorWrapper("petstore-openapi.json").withEnabledSecurityGeneration(false) .withAdditionalApiTypeAnnotationsConfig("@org.test.Foo;@org.test.Bar") .generate("org.additionalapitypeannotations"); assertFalse(generatedFiles.isEmpty()); - generatedFiles.stream() - .filter(file -> file.getPath().matches(".*api.*Api.java")) + generatedFiles.stream().filter(file -> file.getPath().matches(".*api.*Api.java")) .forEach(this::verifyApiAdditionalAnnotations); } private void verifyApiAdditionalAnnotations(File file) { try { CompilationUnit compilationUnit = StaticJavaParser.parse(file); - compilationUnit.findAll(ClassOrInterfaceDeclaration.class) - .forEach(c -> { - assertThat(c.getAnnotationByName("Foo")).isPresent(); - assertThat(c.getAnnotationByName("Bar")).isPresent(); - }); + compilationUnit.findAll(ClassOrInterfaceDeclaration.class).forEach(c -> { + assertThat(c.getAnnotationByName("Foo")).isPresent(); + assertThat(c.getAnnotationByName("Bar")).isPresent(); + }); } catch (FileNotFoundException e) { throw new RuntimeException(e.getMessage()); } @@ -712,86 +623,34 @@ private void verifyApiAdditionalAnnotations(File file) { @Test void verifyAdditionalRequestArgs() throws URISyntaxException { - List generatedFiles = createGeneratorWrapper("petstore-openapi.json") - .withEnabledSecurityGeneration(false) + List generatedFiles = createGeneratorWrapper("petstore-openapi.json").withEnabledSecurityGeneration(false) .withAdditionalRequestArgs( "@HeaderParam(\"jaxrs-style-header\") String headerValue;@HeaderParam(\"x-correlation-id\") String correlationId;@PathParam(\"stream\") String stream") .generate("org.additionalHTTPHeaders"); assertFalse(generatedFiles.isEmpty()); - generatedFiles.stream() - .filter(file -> file.getPath() - .matches(".*api.*Api.java")) + generatedFiles.stream().filter(file -> file.getPath().matches(".*api.*Api.java")) .forEach(this::verifyApiAdditionalHTTPHeaders); } - private void verifyApiAdditionalHTTPHeaders(File file) { - try { - CompilationUnit compilationUnit = StaticJavaParser.parse(file); - compilationUnit.findAll(MethodDeclaration.class) - .forEach(c -> { - assertParameter(c.getParameterByName("correlationId"), - "String", - Map.of("HeaderParam", - "\"x-correlation-id\"")); - assertParameter(c.getParameterByName("headerValue"), - "String", - Map.of("HeaderParam", - "\"jaxrs-style-header\"")); - assertParameter(c.getParameterByName("stream"), - "String", - Map.of("PathParam", - "\"stream\"")); - }); - } catch (FileNotFoundException e) { - throw new RuntimeException(e.getMessage()); - } - } - - private void assertParameter(Optional optionalParameter, - String type, - Map annotations) { - assertThat(optionalParameter).isPresent(); - var parameter = optionalParameter.orElseThrow(); - assertThat(parameter.getTypeAsString()).isEqualTo(type); - annotations.forEach((annotationName, annotationValue) -> { - var parameterAnnotation = parameter.getAnnotationByName(annotationName); - assertThat(parameterAnnotation).isPresent(); - assertThat(parameterAnnotation.get() - .asSingleMemberAnnotationExpr() - .getMemberValue() - .toString()).hasToString(annotationValue); - }); - } - @Test void verifyCookieParams() throws URISyntaxException { - List generatedFiles = createGeneratorWrapper("petstore-openapi.json") - .generate("org.cookieParams"); - - generatedFiles.stream() - .filter(file -> file.getPath() - .matches("PetApi.java")) - .forEach(file -> { - try { - CompilationUnit compilationUnit = StaticJavaParser.parse(file); - var positiveFounds = compilationUnit.findAll(MethodDeclaration.class) - .stream() - .filter(c -> c.getNameAsString() - .equals("findPetsByStatus")) - .filter(c -> { - assertParameter(c.getParameterByName("exampleCookie"), - "String", - Map.of("CookieParam", - "\"example-cookie\"")); - return true; - }) - .count(); - assertThat(positiveFounds).isEqualTo(1); - } catch (FileNotFoundException e) { - throw new RuntimeException(e.getMessage()); - } - }); + List generatedFiles = createGeneratorWrapper("petstore-openapi.json").generate("org.cookieParams"); + + generatedFiles.stream().filter(file -> file.getPath().matches("PetApi.java")).forEach(file -> { + try { + CompilationUnit compilationUnit = StaticJavaParser.parse(file); + var positiveFounds = compilationUnit.findAll(MethodDeclaration.class).stream() + .filter(c -> c.getNameAsString().equals("findPetsByStatus")).filter(c -> { + assertParameter(c.getParameterByName("exampleCookie"), "String", + Map.of("CookieParam", "\"example-cookie\"")); + return true; + }).count(); + assertThat(positiveFounds).isEqualTo(1); + } catch (FileNotFoundException e) { + throw new RuntimeException(e.getMessage()); + } + }); } @Test @@ -803,9 +662,7 @@ void verifyAPINormalization() throws Exception { assertNotNull(generatedFiles); assertFalse(generatedFiles.isEmpty()); - Optional file = generatedFiles.stream() - .filter(f -> f.getName().endsWith("Primate.java")) - .findAny(); + Optional file = generatedFiles.stream().filter(f -> f.getName().endsWith("Primate.java")).findAny(); assertThat(file).isNotEmpty(); CompilationUnit compilationUnit = StaticJavaParser.parse(file.orElseThrow()); List types = compilationUnit.findAll(ClassOrInterfaceDeclaration.class); @@ -815,31 +672,53 @@ void verifyAPINormalization() throws Exception { assertThat(types.get(0).getExtendedTypes(0).getName()).isEqualTo(new SimpleName("Mammal")); } - private Optional getRegisterProviderAnnotation(ClassOrInterfaceDeclaration declaration, - String annotationValue) { - return declaration.getAnnotations() + @Test + void verifyDynamicUrlAnnotation() throws Exception { + List generatedFiles = createGeneratorWrapperReactive("petstore-openapi.json") + .withUseDynamicUrl(true) + .generate("org.dynamic.url") .stream() - .filter(annotationExpr -> "RegisterProvider".equals(annotationExpr.getNameAsString()) && - annotationExpr instanceof SingleMemberAnnotationExpr && - annotationValue.equals(((SingleMemberAnnotationExpr) annotationExpr).getMemberValue() - .toString())) - .findFirst(); + .filter(file -> file.getPath().endsWith("PetApi.java")).toList(); + + assertThat(generatedFiles).isNotEmpty(); + for (File file : generatedFiles) { + try { + CompilationUnit compilationUnit = StaticJavaParser.parse(file); + var positiveFounds = compilationUnit.findAll(MethodDeclaration.class).stream() + .filter(c -> c.getNameAsString().equals("findPetsByStatus")).filter(c -> { + assertMarkerAnnotationParameter(c.getParameterByName("dynUrl"), "Url"); + return true; + }).count(); + assertThat(positiveFounds).isEqualTo(1); + } catch (FileNotFoundException e) { + throw new RuntimeException(e.getMessage()); + } + } } private List generateRestClientFiles() throws URISyntaxException { - OpenApiClientGeneratorWrapper generatorWrapper = createGeneratorWrapper("simple-openapi.json") - .withCircuitBreakerConfig(Map.of( - "org.openapitools.client.api.DefaultApi", List.of("opThatDoesNotExist", "byeMethodGet"))); + OpenApiClientGeneratorWrapper generatorWrapper = createGeneratorWrapper("simple-openapi.json").withCircuitBreakerConfig( + Map.of("org.openapitools.client.api.DefaultApi", List.of("opThatDoesNotExist", "byeMethodGet"))); return generatorWrapper.generate("org.openapitools.client"); } + private OpenApiClientGeneratorWrapper createGeneratorWrapperReactive(String specFileName) throws URISyntaxException { + final Path openApiSpec = getOpenApiSpecPath(specFileName); + return new OpenApiReactiveClientGeneratorWrapper(openApiSpec, getOpenApiTargetPath(openApiSpec), false, true); + } + private OpenApiClientGeneratorWrapper createGeneratorWrapper(String specFileName) throws URISyntaxException { - final Path openApiSpec = Path - .of(requireNonNull(this.getClass().getResource(String.format("/openapi/%s", specFileName))).toURI()); - final Path targetPath = Paths.get(getTargetDir(), "openapi-gen"); + final Path openApiSpec = getOpenApiSpecPath(specFileName); + return new OpenApiClassicClientGeneratorWrapper(openApiSpec, getOpenApiTargetPath(openApiSpec), false, true); + } + + private Path getOpenApiSpecPath(String specFileName) throws URISyntaxException { + return Path.of(requireNonNull(this.getClass().getResource(String.format("/openapi/%s", specFileName))).toURI()); + } - return new OpenApiClassicClientGeneratorWrapper(openApiSpec, targetPath, false, true); + private Path getOpenApiTargetPath(Path openApiSpec) throws URISyntaxException { + return Paths.get(getTargetDir(), "openapi-gen"); } private String getTargetDir() throws URISyntaxException { @@ -848,7 +727,39 @@ private String getTargetDir() throws URISyntaxException { private Optional findVariableByName(List fields, String name) { return fields.stream().map(field -> field.getVariable(0)) - .filter((VariableDeclarator variable) -> name.equals(variable.getName().asString())) - .findFirst(); + .filter((VariableDeclarator variable) -> name.equals(variable.getName().asString())).findFirst(); + } + + private void verifyApiAdditionalHTTPHeaders(File file) { + try { + CompilationUnit compilationUnit = StaticJavaParser.parse(file); + compilationUnit.findAll(MethodDeclaration.class).forEach(c -> { + assertParameter(c.getParameterByName("correlationId"), "String", Map.of("HeaderParam", "\"x-correlation-id\"")); + assertParameter(c.getParameterByName("headerValue"), "String", Map.of("HeaderParam", "\"jaxrs-style-header\"")); + assertParameter(c.getParameterByName("stream"), "String", Map.of("PathParam", "\"stream\"")); + }); + } catch (FileNotFoundException e) { + throw new RuntimeException(e.getMessage()); + } + } + + private void assertParameter(Optional optionalParameter, String type, Map annotations) { + assertThat(optionalParameter).isPresent(); + var parameter = optionalParameter.orElseThrow(); + assertThat(parameter.getTypeAsString()).isEqualTo(type); + annotations.forEach((annotationName, annotationValue) -> { + var parameterAnnotation = parameter.getAnnotationByName(annotationName); + assertThat(parameterAnnotation).isPresent(); + assertThat(parameterAnnotation.get().asSingleMemberAnnotationExpr().getMemberValue().toString()) + .hasToString(annotationValue); + }); + } + + private void assertMarkerAnnotationParameter(Optional optionalParameter, String annotationName) { + assertThat(optionalParameter).isPresent(); + var parameter = optionalParameter.orElseThrow(); + var parameterAnnotation = parameter.getAnnotationByName(annotationName); + assertThat(parameterAnnotation).isPresent(); + assertThat(parameterAnnotation.get().asMarkerAnnotationExpr()).isNotNull(); } } diff --git a/docs/modules/ROOT/pages/client.adoc b/docs/modules/ROOT/pages/client.adoc index 4e8ecd509..e2b6350e8 100644 --- a/docs/modules/ROOT/pages/client.adoc +++ b/docs/modules/ROOT/pages/client.adoc @@ -199,6 +199,11 @@ quarkus.openapi-generator.codegen.spec.my_openapi_yaml.serializable-model=true include::./includes/equals-hashcode.adoc[leveloffset=+1, opts=optional] +[[dynamic-url]] +== Dynamic base URLs + +include::./includes/dynamic-url.adoc[leveloffset=+1, opts=optional] + == Known Limitations === Supported Arguments diff --git a/docs/modules/ROOT/pages/includes/dynamic-url.adoc b/docs/modules/ROOT/pages/includes/dynamic-url.adoc new file mode 100644 index 000000000..f4b36df11 --- /dev/null +++ b/docs/modules/ROOT/pages/includes/dynamic-url.adoc @@ -0,0 +1,16 @@ + +If you need the generated `RestClient` to target different server URLs at runtime—rather than relying solely on the static URL from the application configuration—you can enable dynamic base URL support. + +To do so, set the following property in your configuration: + +[source,properties] +---- +quarkus.openapi-generator.codegen.spec.my_openapi_yaml.use-dynamic-url=true +---- + +When this property is enabled and `quarkus-rest-client` is present on the classpath, the generator will include a method parameter annotated with `@io.quarkus.rest.client.reactive.Url`. This allows your application to supply the target URL dynamically at runtime. + +This feature is particularly useful when integrating with multiple instances of the same API or switching endpoints based on contextual information. + +For more details, refer to the official Quarkus documentation: +https://quarkus.io/version/3.20/guides/rest-client#dynamic-base-urls diff --git a/docs/modules/ROOT/pages/includes/quarkus-openapi-generator.adoc b/docs/modules/ROOT/pages/includes/quarkus-openapi-generator.adoc index bcb8f8e72..5886094b1 100644 --- a/docs/modules/ROOT/pages/includes/quarkus-openapi-generator.adoc +++ b/docs/modules/ROOT/pages/includes/quarkus-openapi-generator.adoc @@ -1279,6 +1279,27 @@ endif::add-copy-button-to-env-var[] |boolean | +a|icon:lock[title=Fixed at build time] [[quarkus-openapi-generator_quarkus-openapi-generator-codegen-spec-spec-item-use-dynamic-url]] [.property-path]##link:#quarkus-openapi-generator_quarkus-openapi-generator-codegen-spec-spec-item-use-dynamic-url[`quarkus.openapi-generator.codegen.spec."spec-item".use-dynamic-url`]## +ifdef::add-copy-button-to-config-props[] +config_property_copy_button:+++quarkus.openapi-generator.codegen.spec."spec-item".use-dynamic-url+++[] +endif::add-copy-button-to-config-props[] + + +[.description] +-- +Whether to enable Dynamic URLs on APIs methods. By enabling this property every method on `RestClients` will be annotated with `io.quarkus.rest.client.reactive.Url`. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_OPENAPI_GENERATOR_CODEGEN_SPEC__SPEC_ITEM__USE_DYNAMIC_URL+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_OPENAPI_GENERATOR_CODEGEN_SPEC__SPEC_ITEM__USE_DYNAMIC_URL+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +|`false` + a| [[quarkus-openapi-generator_quarkus-openapi-generator-item-configs-auth-auth-configs-token-propagation]] [.property-path]##link:#quarkus-openapi-generator_quarkus-openapi-generator-item-configs-auth-auth-configs-token-propagation[`quarkus.openapi-generator."item-configs".auth."auth-configs".token-propagation`]## ifdef::add-copy-button-to-config-props[] config_property_copy_button:+++quarkus.openapi-generator."item-configs".auth."auth-configs".token-propagation+++[] diff --git a/docs/modules/ROOT/pages/includes/quarkus-openapi-generator_quarkus.openapi-generator.adoc b/docs/modules/ROOT/pages/includes/quarkus-openapi-generator_quarkus.openapi-generator.adoc index bcb8f8e72..5886094b1 100644 --- a/docs/modules/ROOT/pages/includes/quarkus-openapi-generator_quarkus.openapi-generator.adoc +++ b/docs/modules/ROOT/pages/includes/quarkus-openapi-generator_quarkus.openapi-generator.adoc @@ -1279,6 +1279,27 @@ endif::add-copy-button-to-env-var[] |boolean | +a|icon:lock[title=Fixed at build time] [[quarkus-openapi-generator_quarkus-openapi-generator-codegen-spec-spec-item-use-dynamic-url]] [.property-path]##link:#quarkus-openapi-generator_quarkus-openapi-generator-codegen-spec-spec-item-use-dynamic-url[`quarkus.openapi-generator.codegen.spec."spec-item".use-dynamic-url`]## +ifdef::add-copy-button-to-config-props[] +config_property_copy_button:+++quarkus.openapi-generator.codegen.spec."spec-item".use-dynamic-url+++[] +endif::add-copy-button-to-config-props[] + + +[.description] +-- +Whether to enable Dynamic URLs on APIs methods. By enabling this property every method on `RestClients` will be annotated with `io.quarkus.rest.client.reactive.Url`. + + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_OPENAPI_GENERATOR_CODEGEN_SPEC__SPEC_ITEM__USE_DYNAMIC_URL+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_OPENAPI_GENERATOR_CODEGEN_SPEC__SPEC_ITEM__USE_DYNAMIC_URL+++` +endif::add-copy-button-to-env-var[] +-- +|boolean +|`false` + a| [[quarkus-openapi-generator_quarkus-openapi-generator-item-configs-auth-auth-configs-token-propagation]] [.property-path]##link:#quarkus-openapi-generator_quarkus-openapi-generator-item-configs-auth-auth-configs-token-propagation[`quarkus.openapi-generator."item-configs".auth."auth-configs".token-propagation`]## ifdef::add-copy-button-to-config-props[] config_property_copy_button:+++quarkus.openapi-generator."item-configs".auth."auth-configs".token-propagation+++[] diff --git a/docs/pom.xml b/docs/pom.xml index cc9044aa8..c9723d939 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -9,7 +9,7 @@ ../pom.xml quarkus-openapi-generator-docs - Quarkus - OpenAPI Generator - Client - Documentation + Quarkus - OpenAPI Generator - Documentation