[java][Micronaut] generator fixes (#11803)

* Fix ConfigurableAuthorization package name import to come from configuration

* Fix Micronaut @Consumes and @Produces annotations, allow multiple content types

* Fix security schemes rendering and Micronaut @Authentication pairing with application settings when special characters are present

* Updated samples

* Code review feedback

* Add xml to dependencies and fix tests

Co-authored-by: Andriy Dmytruk <andriy.dmytruk@oracle.com>
This commit is contained in:
Miroslav Oujeský 2022-05-20 07:24:48 +02:00 committed by GitHub
parent c3976a4a4d
commit 3f788d3d77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 151 additions and 91 deletions

View File

@ -1,5 +1,8 @@
package org.openapitools.codegen.languages;
import com.google.common.collect.ImmutableMap;
import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Template;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.media.Schema;
import org.apache.commons.lang3.StringUtils;
@ -15,6 +18,8 @@ import org.openapitools.codegen.model.OperationsMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.Writer;
import java.util.*;
import java.util.stream.Collectors;
@ -692,4 +697,17 @@ public abstract class JavaMicronautAbstractCodegen extends AbstractJavaCodegen i
}
return escapeText(text).replaceAll("'", "\\'");
}
@Override
protected ImmutableMap.Builder<String, Mustache.Lambda> addMustacheLambdas() {
return super.addMustacheLambdas()
.put("replaceDotsWithUnderscore", new ReplaceDotsWithUnderscoreLambda());
}
private static class ReplaceDotsWithUnderscoreLambda implements Mustache.Lambda {
@Override
public void execute(final Template.Fragment fragment, final Writer writer) throws IOException {
writer.write(fragment.execute().replace('.', '_'));
}
}
}

View File

@ -48,10 +48,12 @@ public interface {{classname}} {
{{#operation}}
{{>common/operationAnnotations}}{{!
}} @{{#lambda.pascalcase}}{{#lambda.lowercase}}{{httpMethod}}{{/lambda.lowercase}}{{/lambda.pascalcase}}(uri="{{{path}}}")
{{#vendorExtensions.x-content-type}}
@Produces(value={"{{vendorExtensions.x-content-type}}"})
{{/vendorExtensions.x-content-type}}
@Consumes(value={"{{vendorExtensions.x-accepts}}"})
{{#hasProduces}}
{{#produces}}{{#-first}}@Consumes({{openbrace}}{{/-first}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{#-last}}{{closebrace}}){{/-last}}{{/produces}}
{{/hasProduces}}
{{#hasConsumes}}
{{#consumes}}{{#-first}}@Produces({{openbrace}}{{/-first}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{#-last}}{{closebrace}}){{/-last}}{{/consumes}}
{{/hasConsumes}}
{{!auth methods}}
{{#configureAuth}}
{{#authMethods}}

View File

@ -4,6 +4,7 @@ package {{invokerPackage}}.auth;
import io.micronaut.aop.MethodInvocationContext;
import io.micronaut.core.annotation.AnnotationValue;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.naming.NameUtils;
import io.micronaut.core.util.CollectionUtils;
import io.micronaut.core.util.StringUtils;
import io.micronaut.http.MutableHttpRequest;
@ -47,26 +48,6 @@ public class AuthorizationBinder implements AnnotatedClientRequestBinder<Authori
}
private String configurationName(String name) {
StringBuilder result = new StringBuilder();
boolean wasCapital = false;
for (int i = 0; i < name.length(); ++i) {
char c = name.charAt(i);
if (c == '_') {
result.append('-');
wasCapital = true;
} else if (Character.isUpperCase(c)) {
if (wasCapital) {
result.append(Character.toLowerCase(c));
} else {
result.append('-');
result.append(Character.toLowerCase(c));
}
wasCapital = true;
} else {
result.append(c);
}
}
return result.toString();
return NameUtils.hyphenate(name.replace('.', '_'), true);
}
}

View File

@ -20,7 +20,7 @@ import io.micronaut.security.oauth2.client.clientcredentials.propagation.ClientC
import io.micronaut.security.oauth2.client.clientcredentials.propagation.ClientCredentialsTokenPropagator;
import io.micronaut.security.oauth2.configuration.OauthClientConfiguration;
import io.micronaut.security.oauth2.endpoint.token.response.TokenResponse;
import org.openapitools.auth.configuration.ConfigurableAuthorization;
import {{invokerPackage}}.auth.configuration.ConfigurableAuthorization;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@ -10,7 +10,7 @@ micronaut:
security:
oauth2:
clients:{{#oauthMethods}}
{{{name}}}:
{{#lambda.replaceDotsWithUnderscore}}{{{name}}}{{/lambda.replaceDotsWithUnderscore}}:
grant-type: {{#isCode}}authorization_code{{/isCode}}{{#isImplicit}}implicit{{/isImplicit}}{{#isPassword}}password{{/isPassword}}{{#isApplication}}client_credentials{{/isApplication}}
scopes: [{{#scopes}}"{{{scope}}}"{{^-last}}, {{/-last}}{{/scopes}}]{{!authorization url}}{{#authorizationUrl}}
authorization:

View File

@ -215,4 +215,28 @@ public class MicronautClientCodegenTest extends AbstractMicronautCodegenTest {
assertFileContains(modelPath + "Order.java", "public Order()");
assertFileNotContainsRegex(modelPath + "Order.java", "public Order\\([^)]+\\)");
}
@Test
public void doGenerateMultipleContentTypes() {
JavaMicronautClientCodegen codegen = new JavaMicronautClientCodegen();
String outputPath = generateFiles(codegen, "src/test/resources/3_0/micronaut/content-type.yaml", CodegenConstants.APIS);
// body and response content types should be properly annotated using @Consumes and @Produces micronaut annotations
String apiPath = outputPath + "src/main/java/org/openapitools/api/";
assertFileContains(apiPath + "DefaultApi.java", "@Consumes({\"application/vnd.oracle.resource+json; type=collection\", \"application/vnd.oracle.resource+json; type=error\"})");
assertFileContains(apiPath + "DefaultApi.java", "@Produces({\"application/vnd.oracle.resource+json; type=singular\"})");
}
@Test
public void doGenerateOauth2InApplicationConfig() {
JavaMicronautClientCodegen codegen = new JavaMicronautClientCodegen();
codegen.additionalProperties().put(JavaMicronautClientCodegen.OPT_CONFIGURE_AUTH, "true");
String outputPath = generateFiles(codegen, "src/test/resources/3_0/micronaut/oauth2.yaml", CodegenConstants.SUPPORTING_FILES);
// micronaut yaml property names shouldn't contain any dots
String resourcesPath = outputPath + "src/main/resources/";
assertFileContains(resourcesPath + "application.yml", "OAuth_2_0_Client_Credentials:");
}
}

View File

@ -0,0 +1,29 @@
openapi: 3.0.2
info:
title: info
description: info
version: 0.1.0
paths:
/example/api:
post:
summary: summary
description: description
requestBody:
content:
application/vnd.oracle.resource+json; type=singular:
schema:
type: object
responses:
200:
description: response
content:
application/vnd.oracle.resource+json; type=collection:
schema:
type: object
default:
description: error
content:
application/vnd.oracle.resource+json; type=error:
schema:
type: object

View File

@ -0,0 +1,25 @@
openapi: 3.0.2
info:
title: info
description: info
version: 0.1.0
paths:
/example/api:
get:
summary: summary
description: description
responses:
200:
description: response
components:
securitySchemes:
OAuth_2.0_Client_Credentials:
type: oauth2
description: OAuth 2.0 - Client Credentials
flows:
clientCredentials:
tokenUrl: "https://example.com/token"
scopes:
scope: scope description

View File

@ -21,3 +21,6 @@
#docs/*.md
# Then explicitly reverse the ignore rule for a single file:
#!docs/README.md
pom.xml
build.gradle

View File

@ -3,7 +3,6 @@
.mvn/wrapper/maven-wrapper.jar
.mvn/wrapper/maven-wrapper.properties
README.md
build.gradle
docs/apis/AnotherFakeApi.md
docs/apis/FakeApi.md
docs/apis/FakeClassnameTags123Api.md
@ -66,7 +65,6 @@ gradlew
gradlew.bat
mvnw
mvnw.bat
pom.xml
settings.gradle
src/main/java/org/openapitools/api/AnotherFakeApi.java
src/main/java/org/openapitools/api/FakeApi.java

View File

@ -27,6 +27,7 @@ dependencies {
implementation("io.micronaut:micronaut-http-client")
implementation("io.micronaut:micronaut-runtime")
implementation("io.micronaut:micronaut-validation")
implementation("io.micronaut.xml:micronaut-jackson-xml")
implementation("io.micronaut.security:micronaut-security")
implementation("io.micronaut.security:micronaut-security-oauth2")
implementation("io.micronaut.reactor:micronaut-reactor")

View File

@ -42,6 +42,11 @@
<artifactId>micronaut-validation</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.micronaut.xml</groupId>
<artifactId>micronaut-jackson-xml</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-inject-groovy</artifactId>

View File

@ -38,8 +38,8 @@ public interface AnotherFakeApi {
* @return ModelClient
*/
@Patch(uri="/another-fake/dummy")
@Produces(value={"application/json"})
@Consumes(value={"application/json"})
@Consumes({"application/json"})
@Produces({"application/json"})
Mono<ModelClient> call123testSpecialTags(
@Body @NotNull @Valid ModelClient _body
);

View File

@ -45,8 +45,7 @@ public interface FakeApi {
* @param xmlItem XmlItem Body (required)
*/
@Post(uri="/fake/create_xml_item")
@Produces(value={"application/xml"})
@Consumes(value={"application/json"})
@Produces({"application/xml", "application/xml; charset=utf-8", "application/xml; charset=utf-16", "text/xml", "text/xml; charset=utf-8", "text/xml; charset=utf-16"})
Mono<Void> createXmlItem(
@Body @NotNull @Valid XmlItem xmlItem
);
@ -58,8 +57,7 @@ public interface FakeApi {
* @return Boolean
*/
@Post(uri="/fake/outer/boolean")
@Produces(value={"*/*"})
@Consumes(value={"*/*"})
Mono<Boolean> fakeOuterBooleanSerialize(
@Body @Nullable Boolean _body
);
@ -71,8 +69,7 @@ public interface FakeApi {
* @return OuterComposite
*/
@Post(uri="/fake/outer/composite")
@Produces(value={"*/*"})
@Consumes(value={"*/*"})
Mono<OuterComposite> fakeOuterCompositeSerialize(
@Body @Nullable @Valid OuterComposite _body
);
@ -84,8 +81,7 @@ public interface FakeApi {
* @return BigDecimal
*/
@Post(uri="/fake/outer/number")
@Produces(value={"*/*"})
@Consumes(value={"*/*"})
Mono<BigDecimal> fakeOuterNumberSerialize(
@Body @Nullable BigDecimal _body
);
@ -97,8 +93,7 @@ public interface FakeApi {
* @return String
*/
@Post(uri="/fake/outer/string")
@Produces(value={"*/*"})
@Consumes(value={"*/*"})
Mono<String> fakeOuterStringSerialize(
@Body @Nullable String _body
);
@ -109,8 +104,7 @@ public interface FakeApi {
* @param _body (required)
*/
@Put(uri="/fake/body-with-file-schema")
@Produces(value={"application/json"})
@Consumes(value={"application/json"})
@Produces({"application/json"})
Mono<Void> testBodyWithFileSchema(
@Body @NotNull @Valid FileSchemaTestClass _body
);
@ -122,8 +116,7 @@ public interface FakeApi {
* @param _body (required)
*/
@Put(uri="/fake/body-with-query-params")
@Produces(value={"application/json"})
@Consumes(value={"application/json"})
@Produces({"application/json"})
Mono<Void> testBodyWithQueryParams(
@QueryValue(value="query") @NotNull String query,
@Body @NotNull @Valid User _body
@ -137,8 +130,8 @@ public interface FakeApi {
* @return ModelClient
*/
@Patch(uri="/fake")
@Produces(value={"application/json"})
@Consumes(value={"application/json"})
@Consumes({"application/json"})
@Produces({"application/json"})
Mono<ModelClient> testClientModel(
@Body @NotNull @Valid ModelClient _body
);
@ -163,8 +156,7 @@ public interface FakeApi {
* @param paramCallback None (optional)
*/
@Post(uri="/fake")
@Produces(value={"application/x-www-form-urlencoded"})
@Consumes(value={"application/json"})
@Produces({"application/x-www-form-urlencoded"})
Mono<Void> testEndpointParameters(
@NotNull @DecimalMin("32.1") @DecimalMax("543.2") BigDecimal number,
@NotNull @DecimalMin("67.8") @DecimalMax("123.4") Double _double,
@ -196,8 +188,7 @@ public interface FakeApi {
* @param enumFormString Form parameter enum test (string) (optional, default to -efg)
*/
@Get(uri="/fake")
@Produces(value={"application/x-www-form-urlencoded"})
@Consumes(value={"application/json"})
@Produces({"application/x-www-form-urlencoded"})
Mono<Void> testEnumParameters(
@Header(name="enum_header_string_array") @Nullable List<String> enumHeaderStringArray,
@Header(name="enum_header_string", defaultValue="-efg") @Nullable String enumHeaderString,
@ -221,7 +212,6 @@ public interface FakeApi {
* @param int64Group Integer in group parameters (optional)
*/
@Delete(uri="/fake")
@Consumes(value={"application/json"})
Mono<Void> testGroupParameters(
@QueryValue(value="required_string_group") @NotNull Integer requiredStringGroup,
@Header(name="required_boolean_group") @NotNull Boolean requiredBooleanGroup,
@ -237,8 +227,7 @@ public interface FakeApi {
* @param param request body (required)
*/
@Post(uri="/fake/inline-additionalProperties")
@Produces(value={"application/json"})
@Consumes(value={"application/json"})
@Produces({"application/json"})
Mono<Void> testInlineAdditionalProperties(
@Body @NotNull Map<String, String> param
);
@ -250,8 +239,7 @@ public interface FakeApi {
* @param param2 field2 (required)
*/
@Get(uri="/fake/jsonFormData")
@Produces(value={"application/x-www-form-urlencoded"})
@Consumes(value={"application/json"})
@Produces({"application/x-www-form-urlencoded"})
Mono<Void> testJsonFormData(
@NotNull String param,
@NotNull String param2
@ -267,7 +255,6 @@ public interface FakeApi {
* @param context (required)
*/
@Put(uri="/fake/test-query-parameters")
@Consumes(value={"application/json"})
Mono<Void> testQueryParameterCollectionFormat(
@QueryValue(value="pipe") @NotNull List<String> pipe,
@QueryValue(value="ioutil") @NotNull List<String> ioutil,

View File

@ -38,8 +38,8 @@ public interface FakeClassnameTags123Api {
* @return ModelClient
*/
@Patch(uri="/fake_classname_test")
@Produces(value={"application/json"})
@Consumes(value={"application/json"})
@Consumes({"application/json"})
@Produces({"application/json"})
Mono<ModelClient> testClassname(
@Body @NotNull @Valid ModelClient _body
);

View File

@ -39,8 +39,7 @@ public interface PetApi {
* @param _body Pet object that needs to be added to the store (required)
*/
@Post(uri="/pet")
@Produces(value={"application/json"})
@Consumes(value={"application/json"})
@Produces({"application/json", "application/xml"})
Mono<Void> addPet(
@Body @NotNull @Valid Pet _body
);
@ -52,7 +51,6 @@ public interface PetApi {
* @param apiKey (optional)
*/
@Delete(uri="/pet/{petId}")
@Consumes(value={"application/json"})
Mono<Void> deletePet(
@PathVariable(name="petId") @NotNull Long petId,
@Header(name="api_key") @Nullable String apiKey
@ -66,7 +64,7 @@ public interface PetApi {
* @return List&lt;Pet&gt;
*/
@Get(uri="/pet/findByStatus")
@Consumes(value={"application/json"})
@Consumes({"application/xml", "application/json"})
Mono<List<Pet>> findPetsByStatus(
@QueryValue(value="status") @NotNull List<String> status
);
@ -79,7 +77,7 @@ public interface PetApi {
* @return Set&lt;Pet&gt;
*/
@Get(uri="/pet/findByTags")
@Consumes(value={"application/json"})
@Consumes({"application/xml", "application/json"})
Mono<Set<Pet>> findPetsByTags(
@QueryValue(value="tags") @NotNull Set<String> tags
);
@ -92,7 +90,7 @@ public interface PetApi {
* @return Pet
*/
@Get(uri="/pet/{petId}")
@Consumes(value={"application/json"})
@Consumes({"application/xml", "application/json"})
Mono<Pet> getPetById(
@PathVariable(name="petId") @NotNull Long petId
);
@ -103,8 +101,7 @@ public interface PetApi {
* @param _body Pet object that needs to be added to the store (required)
*/
@Put(uri="/pet")
@Produces(value={"application/json"})
@Consumes(value={"application/json"})
@Produces({"application/json", "application/xml"})
Mono<Void> updatePet(
@Body @NotNull @Valid Pet _body
);
@ -117,8 +114,7 @@ public interface PetApi {
* @param status Updated status of the pet (optional)
*/
@Post(uri="/pet/{petId}")
@Produces(value={"application/x-www-form-urlencoded"})
@Consumes(value={"application/json"})
@Produces({"application/x-www-form-urlencoded"})
Mono<Void> updatePetWithForm(
@PathVariable(name="petId") @NotNull Long petId,
@Nullable String name,
@ -134,8 +130,8 @@ public interface PetApi {
* @return ModelApiResponse
*/
@Post(uri="/pet/{petId}/uploadImage")
@Produces(value={"multipart/form-data"})
@Consumes(value={"application/json"})
@Consumes({"application/json"})
@Produces({"multipart/form-data"})
Mono<ModelApiResponse> uploadFile(
@PathVariable(name="petId") @NotNull Long petId,
@Nullable String additionalMetadata,
@ -151,8 +147,8 @@ public interface PetApi {
* @return ModelApiResponse
*/
@Post(uri="/fake/{petId}/uploadImageWithRequiredFile")
@Produces(value={"multipart/form-data"})
@Consumes(value={"application/json"})
@Consumes({"application/json"})
@Produces({"multipart/form-data"})
Mono<ModelApiResponse> uploadFileWithRequiredFile(
@PathVariable(name="petId") @NotNull Long petId,
@NotNull File requiredFile,

View File

@ -37,7 +37,6 @@ public interface StoreApi {
* @param orderId ID of the order that needs to be deleted (required)
*/
@Delete(uri="/store/order/{order_id}")
@Consumes(value={"application/json"})
Mono<Void> deleteOrder(
@PathVariable(name="order_id") @NotNull String orderId
);
@ -49,7 +48,7 @@ public interface StoreApi {
* @return Map&lt;String, Integer&gt;
*/
@Get(uri="/store/inventory")
@Consumes(value={"application/json"})
@Consumes({"application/json"})
Mono<Map<String, Integer>> getInventory();
/**
@ -60,7 +59,7 @@ public interface StoreApi {
* @return Order
*/
@Get(uri="/store/order/{order_id}")
@Consumes(value={"application/json"})
@Consumes({"application/xml", "application/json"})
Mono<Order> getOrderById(
@PathVariable(name="order_id") @NotNull @Min(1L) @Max(5L) Long orderId
);
@ -72,8 +71,7 @@ public interface StoreApi {
* @return Order
*/
@Post(uri="/store/order")
@Produces(value={"*/*"})
@Consumes(value={"application/json"})
@Consumes({"application/xml", "application/json"})
Mono<Order> placeOrder(
@Body @NotNull @Valid Order _body
);

View File

@ -38,8 +38,6 @@ public interface UserApi {
* @param _body Created user object (required)
*/
@Post(uri="/user")
@Produces(value={"*/*"})
@Consumes(value={"application/json"})
Mono<Void> createUser(
@Body @NotNull @Valid User _body
);
@ -50,8 +48,6 @@ public interface UserApi {
* @param _body List of user object (required)
*/
@Post(uri="/user/createWithArray")
@Produces(value={"*/*"})
@Consumes(value={"application/json"})
Mono<Void> createUsersWithArrayInput(
@Body @NotNull List<User> _body
);
@ -62,8 +58,6 @@ public interface UserApi {
* @param _body List of user object (required)
*/
@Post(uri="/user/createWithList")
@Produces(value={"*/*"})
@Consumes(value={"application/json"})
Mono<Void> createUsersWithListInput(
@Body @NotNull List<User> _body
);
@ -75,7 +69,6 @@ public interface UserApi {
* @param username The name that needs to be deleted (required)
*/
@Delete(uri="/user/{username}")
@Consumes(value={"application/json"})
Mono<Void> deleteUser(
@PathVariable(name="username") @NotNull String username
);
@ -87,7 +80,7 @@ public interface UserApi {
* @return User
*/
@Get(uri="/user/{username}")
@Consumes(value={"application/json"})
@Consumes({"application/xml", "application/json"})
Mono<User> getUserByName(
@PathVariable(name="username") @NotNull String username
);
@ -100,7 +93,7 @@ public interface UserApi {
* @return String
*/
@Get(uri="/user/login")
@Consumes(value={"application/json"})
@Consumes({"application/xml", "application/json"})
Mono<String> loginUser(
@QueryValue(value="username") @NotNull String username,
@QueryValue(value="password") @NotNull String password
@ -111,7 +104,6 @@ public interface UserApi {
*
*/
@Get(uri="/user/logout")
@Consumes(value={"application/json"})
Mono<Void> logoutUser();
/**
@ -122,8 +114,6 @@ public interface UserApi {
* @param _body Updated user object (required)
*/
@Put(uri="/user/{username}")
@Produces(value={"*/*"})
@Consumes(value={"application/json"})
Mono<Void> updateUser(
@PathVariable(name="username") @NotNull String username,
@Body @NotNull @Valid User _body

View File

@ -8,6 +8,7 @@ import org.openapitools.model.Tag
import org.openapitools.model.Pet
import io.micronaut.test.extensions.spock.annotation.MicronautTest
import spock.lang.Ignore
import spock.lang.Specification
import jakarta.inject.Inject
@ -48,7 +49,7 @@ class PetApiSpec extends Specification {
then:
var e = thrown(HttpClientResponseException.class)
e.getMessage() == "Pet not found"
e.getMessage().contains("Pet not found")
e.getStatus() == HttpStatus.NOT_FOUND
}
@ -74,6 +75,7 @@ class PetApiSpec extends Specification {
* Finds Pets by status
* Multiple status values can be provided with comma separated strings
*/
@Ignore("Issue reported in https://github.com/micronaut-projects/micronaut-jackson-xml/issues/175")
void "findPetsByStatus() test"() {
given:
Pet pet = new Pet()
@ -230,6 +232,7 @@ class PetApiSpec extends Specification {
notThrown()
}
@Ignore("Issue reported in https://github.com/micronaut-projects/micronaut-jackson-xml/issues/175")
void "findPetByTags() test"() {
given:
Tag tag = new Tag().name("cute").id(2L)