diff --git a/client-runtime/src/main/java/com/microsoft/rest/v3/RestProxy.java b/client-runtime/src/main/java/com/microsoft/rest/v3/RestProxy.java index df63b0e8a4..bbe3707d14 100644 --- a/client-runtime/src/main/java/com/microsoft/rest/v3/RestProxy.java +++ b/client-runtime/src/main/java/com/microsoft/rest/v3/RestProxy.java @@ -445,13 +445,13 @@ private Mono handleRestResponseReturnType(HttpDecodedResponse response, Swagg // entityType = ? extends RestResponse Constructor> responseConstructor = getRestResponseConstructor(entityType); - Type[] deserializedTypes = TypeUtil.getTypeArguments(TypeUtil.getSuperType(entityType, RestResponse.class)); + Type[] genericArgTypes = TypeUtil.getTypeArguments(TypeUtil.getSuperType(entityType, RestResponse.class)); HttpHeaders responseHeaders = response.sourceResponse().headers(); Object deserializedHeaders = response.decodedHeaders().block(); - Type bodyType = deserializedTypes[1]; - if (TypeUtil.isTypeOrSubTypeOf(bodyType, Void.class)) { + Type genericArg2Type = genericArgTypes[1]; + if (TypeUtil.isTypeOrSubTypeOf(genericArg2Type, Void.class)) { // entityType = ? extends RestResponse asyncResult = response.sourceResponse().body().ignoreElements() .then(Mono.just(responseConstructor.newInstance(response.sourceResponse().request(), responseStatusCode, deserializedHeaders, responseHeaders.toMap(), null))); @@ -462,7 +462,7 @@ private Mono handleRestResponseReturnType(HttpDecodedResponse response, Swagg // entityType = ? extends RestResponse> // entityType = ? extends RestResponse // entityType = ? extends RestResponse - asyncResult = handleBodyReturnType(response, methodParser, bodyType) + asyncResult = handleBodyReturnType(response, methodParser, genericArg2Type) .map((Function>) bodyAsObject -> { try { return responseConstructor.newInstance(response.sourceResponse().request(), responseStatusCode, deserializedHeaders, rawHeaders, bodyAsObject); @@ -506,6 +506,7 @@ protected final Mono handleBodyReturnType(final HttpDecodedResponse response, if (httpMethod == HttpMethod.HEAD && (TypeUtil.isTypeOrSubTypeOf(entityType, Boolean.TYPE) || TypeUtil.isTypeOrSubTypeOf(entityType, Boolean.class))) { boolean isSuccess = (responseStatusCode / 100) == 2; + // Mono asyncResult = Mono.just(isSuccess); } else if (TypeUtil.isTypeOrSubTypeOf(entityType, byte[].class)) { // Mono @@ -518,6 +519,9 @@ protected final Mono handleBodyReturnType(final HttpDecodedResponse response, } else if (FluxUtil.isFluxByteBuf(entityType)) { // Mono> asyncResult = Mono.just(response.sourceResponse().body()); + } else if (TypeUtil.isTypeOrSubTypeOf(entityType, Mono.class)) { + // Mono> + asyncResult = Mono.just(response.decodedBody()); } else { // Mono asyncResult = response.decodedBody(); @@ -551,7 +555,7 @@ public final Object handleRestReturnType(Mono asyncHttpDeco // ProxyMethod ReturnType: Mono result = asyncExpectedResponse.then(); } else { - // ProxyMethod ReturnType: Mono> + // ProxyMethod ReturnType: Mono> or Mono result = asyncExpectedResponse.flatMap(response -> handleRestResponseReturnType(response, methodParser, monoTypeParam)); } diff --git a/client-runtime/src/main/java/com/microsoft/rest/v3/serializer/HttpResponseBodyDecoder.java b/client-runtime/src/main/java/com/microsoft/rest/v3/serializer/HttpResponseBodyDecoder.java index 8677f5ddf4..494138e096 100644 --- a/client-runtime/src/main/java/com/microsoft/rest/v3/serializer/HttpResponseBodyDecoder.java +++ b/client-runtime/src/main/java/com/microsoft/rest/v3/serializer/HttpResponseBodyDecoder.java @@ -289,56 +289,123 @@ private static Object convertToResultType(Object wireResponse, Type resultType, } /** - * Get the {@link Type} of the REST API 'returned entity'. + * Process {@link HttpResponseDecodeData#returnType()} and return the {@link Type} of the REST API 'returned entity'. * * In the declaration of a java proxy method corresponding to the REST API, the 'returned entity' can be: * - * 1. emission value of the reactor publisher returned by proxy method + * 0. type of the model value returned by proxy method + * + * e.g. {@code Foo getFoo(args);} + * where Foo is the REST API 'returned entity'. + * + * 1. OR type of the model value emitted from Mono returned by proxy method * * e.g. {@code Mono getFoo(args);} - * {@code Flux getFoos(args);} * where Foo is the REST API 'returned entity'. * - * 2. OR content (body) of {@link RestResponse} emitted by the reactor publisher returned from proxy method + * 2. OR content model type of {@link RestResponse} emitted by the Mono returned from proxy method * * e.g. {@code Mono> getFoo(args);} - * {@code Flux> getFoos(args);} * where Foo is the REST API return entity. * + * 3. OR type of content model value of body publisher of {@link RestResponse} emitted from Mono returned by proxy method + * + * e.g. {@code Mono>> getFoo(args);} + * where Foo is the REST API 'return entity'. + * + * 4. OR content model type of {@link RestResponse} returned from proxy method + * + * e.g. {@code RestResponse getFoo(args);} + * where Foo is the REST API return entity. + * + * 5. OR type of content model value of body publisher of {@link RestResponse} returned by proxy method + * + * e.g. {@code RestResponse> getFoo(args);} + * where Foo is the REST API 'return entity'. + * + * 6. OR type of the model value of OperationStatus returned by proxy method + * + * e.g. {@code OperationStatus createFoo(args);} + * where Foo is the REST API 'returned entity'. + * + * 7. OR type of the model value of OperationStatus emitted from Mono returned by proxy method + * + * e.g. {@code Mono> createFoo(args);} + * where Foo is the REST API 'returned entity'. + * + * 8. OR type of the model value of OperationStatus emitted from Flux returned by proxy method + * + * e.g. {@code Flux> createFoo(args);} + * where Foo is the REST API 'returned entity'. + * + * 9. For all other cases {@link Type} returned from {@link HttpResponseDecodeData#returnType()} will returned + * + * TODO: anuchan case 6, 7 and 8 shouldn't be here + * * @return the entity type. */ private static Type extractEntityTypeFromReturnType(HttpResponseDecodeData decodeData) { Type token = decodeData.returnType(); - if (token != null) { + if (token == null) { + return null; + } else { if (TypeUtil.isTypeOrSubTypeOf(token, Mono.class)) { - token = TypeUtil.getTypeArgument(token); + return extractEntityTypeFromMonoReturnType(token); } else if (TypeUtil.isTypeOrSubTypeOf(token, Flux.class)) { - Type t = TypeUtil.getTypeArgument(token); - try { - // TODO: anuchan - unwrap OperationStatus a different way - // Check for OperationStatus - if (TypeUtil.isTypeOrSubTypeOf(t, Class.forName("com.microsoft.azure.v3.OperationStatus"))) { - token = t; - } - } catch (ClassNotFoundException ignored) { - } + return extractEntityTypeFromFluxReturnType(token); + } else if (TypeUtil.isTypeOrSubTypeOf(token, RestResponse.class)) { + return extractEntityTypeFromRestResponseReturnType(token); + } else if (isOperationStatusType(token)) { + return TypeUtil.getTypeArgument(token); + } else { + return token; } + } + } - if (TypeUtil.isTypeOrSubTypeOf(token, RestResponse.class)) { - token = TypeUtil.getSuperType(token, RestResponse.class); - token = TypeUtil.getTypeArguments(token)[1]; - } + private static Type extractEntityTypeFromMonoReturnType(Type token) { + token = TypeUtil.getTypeArgument(token); + if (TypeUtil.isTypeOrSubTypeOf(token, RestResponse.class)) { + return extractEntityTypeFromRestResponseReturnType(token); + } else if (isOperationStatusType(token)) { + return TypeUtil.getTypeArgument(token); + } else { + return token; + } + } - try { - // TODO: anuchan - unwrap OperationStatus a different way - if (TypeUtil.isTypeOrSubTypeOf(token, Class.forName("com.microsoft.azure.v3.OperationStatus"))) { - // Get Type of 'T' from OperationStatus - token = TypeUtil.getTypeArgument(token); - } - } catch (Exception ignored) { + private static Type extractEntityTypeFromRestResponseReturnType(Type token) { + token = TypeUtil.getSuperType(token, RestResponse.class); + token = TypeUtil.getTypeArguments(token)[1]; + if (TypeUtil.isTypeOrSubTypeOf(token, Mono.class)) { + token = TypeUtil.getTypeArgument(token); + if (isOperationStatusType(token)) { + return TypeUtil.getTypeArgument(token); + } else { + return token; } + } else if (isOperationStatusType(token)) { + return TypeUtil.getTypeArgument(token); + } else { + return token; + } + } + + private static Type extractEntityTypeFromFluxReturnType(Type token) { + Type t = TypeUtil.getTypeArgument(token); + if (isOperationStatusType(t)) { + return TypeUtil.getTypeArgument(t); + } else { + return token; } - return token; + } + + private static boolean isOperationStatusType(Type token) { + try { + return TypeUtil.isTypeOrSubTypeOf(token, Class.forName("com.microsoft.azure.v3.OperationStatus")); + } catch (Exception ignored) { + } + return false; } /** diff --git a/client-runtime/src/test/java/com/microsoft/rest/v3/RestProxyTests.java b/client-runtime/src/test/java/com/microsoft/rest/v3/RestProxyTests.java index 97ddb2e39a..44385019b9 100644 --- a/client-runtime/src/test/java/com/microsoft/rest/v3/RestProxyTests.java +++ b/client-runtime/src/test/java/com/microsoft/rest/v3/RestProxyTests.java @@ -34,6 +34,7 @@ import org.junit.Test; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; import java.nio.channels.AsynchronousFileChannel; import java.nio.file.Files; @@ -43,6 +44,7 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; +import java.util.function.Function; import static org.junit.Assert.*; @@ -1136,6 +1138,9 @@ private interface Service20 { @GET("bytes/100") RestResponse,Void> getBytes100OnlyRawHeaders(); + @GET("bytes/100") + Mono,Void>> getBytes100OnlyRawHeadersAsync(); + @GET("bytes/100") RestResponse getBytes100BodyAndHeaders(); @@ -1145,6 +1150,15 @@ private interface Service20 { @PUT("put") RestResponse putBodyAndHeaders(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) String body); + @PUT("put") + Mono> putBodyAndHeadersAsync(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) String body); + + @PUT("put") + Mono>> putBodyAndHeadersAsyncContentAsync(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) String body); + + @PUT("put") + RestResponse> putBodyAndHeadersContentAsync(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) String body); + @GET("bytes/100") RestResponse getBytesOnlyStatus(); } @@ -1205,6 +1219,21 @@ public void service20GetBytesOnlyHeaders() { assertNotEquals(0, response.headers().size()); } + @Test + public void service20GetBytesOnlyHeadersAsync() { + final Mono, Void>> asyncResponse = createService(Service20.class) + .getBytes100OnlyRawHeadersAsync(); + + asyncResponse.doOnNext(response -> { + assertNotNull(response); + assertEquals(200, response.statusCode()); + assertNotNull(response.headers()); + assertNotEquals(0, response.headers().size()); + }) + .switchIfEmpty(Mono.error(new RuntimeException("asyncResponse should emit the response but it didn't."))) + .block(); + } + @Test public void service20PutOnlyHeaders() { final RestResponse response = createService(Service20.class) @@ -1244,6 +1273,90 @@ public void service20PutBodyAndHeaders() { assertNotEquals(0, headers.xProcessedTime); } + @Test + public void service20PutBodyAndHeadersAsync() { + final Mono> asyncResponse = createService(Service20.class) + .putBodyAndHeadersAsync("body string"); + // + asyncResponse.doOnNext(response -> { + assertNotNull(response); + + assertEquals(200, response.statusCode()); + + final HttpBinJSON body = response.body(); + assertNotNull(body); + assertMatchWithHttpOrHttps("httpbin.org/put", body.url); + assertEquals("body string", body.data); + + final HttpBinHeaders headers = response.headers(); + assertNotNull(headers); + assertEquals(true, headers.accessControlAllowCredentials); + assertEquals("keep-alive", headers.connection.toLowerCase()); + assertNotNull(headers.date); + // assertEquals("1.1 vegur", headers.via); + assertNotEquals(0, headers.xProcessedTime); + }) + .switchIfEmpty(Mono.error(new RuntimeException("asyncResponse should emit the response but it didn't."))) + .block(); + } + + @Test + public void service20PutBodyAndHeadersAsyncContentAsync() { + final Mono>> asyncResponse = createService(Service20.class) + .putBodyAndHeadersAsyncContentAsync("body string"); + // + asyncResponse.flatMap(response -> { + assertNotNull(response); + assertEquals(200, response.statusCode()); + // + final Mono asyncBody = response.body(); + return asyncBody.flatMap(body -> { + assertNotNull(body); + assertMatchWithHttpOrHttps("httpbin.org/put", body.url); + assertEquals("body string", body.data); + return Mono.just(response.headers()); + }) + .switchIfEmpty(Mono.error(new RuntimeException("asyncResponse.body should emit the content but it didn't."))); + }) + .switchIfEmpty(Mono.error(new RuntimeException("asyncResponse should emit the response but it didn't."))) + .doOnNext(hdrs -> { + final HttpBinHeaders headers = hdrs; + assertNotNull(headers); + assertEquals(true, headers.accessControlAllowCredentials); + assertEquals("keep-alive", headers.connection.toLowerCase()); + assertNotNull(headers.date); + // assertEquals("1.1 vegur", headers.via); + assertNotEquals(0, headers.xProcessedTime); + }) + .block(); + } + + @Test + public void service20PutBodyAndHeadersContentAsync() { + final RestResponse> response = createService(Service20.class) + .putBodyAndHeadersContentAsync("body string"); + + assertNotNull(response); + assertEquals(200, response.statusCode()); + + final Mono asyncBody = response.body(); + asyncBody.doOnNext(body -> { + assertNotNull(body); + assertMatchWithHttpOrHttps("httpbin.org/put", body.url); + assertEquals("body string", body.data); + }) + .switchIfEmpty(Mono.error(new RuntimeException("response.body should emit the content but it didn't."))) + .block(); + + final HttpBinHeaders headers = response.headers(); + assertNotNull(headers); + assertEquals(true, headers.accessControlAllowCredentials); + assertEquals("keep-alive", headers.connection.toLowerCase()); + assertNotNull(headers.date); + // assertEquals("1.1 vegur", headers.via); + assertNotEquals(0, headers.xProcessedTime); + } + @Host("http://httpbin.org") interface UnexpectedOKService { @GET("/bytes/1024")