Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding support for RestResponse<.., Mono<T>> (lazy body). #567

Open
wants to merge 1 commit into
base: v3
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -445,13 +445,13 @@ private Mono<?> handleRestResponseReturnType(HttpDecodedResponse response, Swagg
// entityType = ? extends RestResponse<THeaders, TBody>
Constructor<? extends RestResponse<?, ?>> 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<THeaders, Void>
asyncResult = response.sourceResponse().body().ignoreElements()
.then(Mono.just(responseConstructor.newInstance(response.sourceResponse().request(), responseStatusCode, deserializedHeaders, responseHeaders.toMap(), null)));
Expand All @@ -462,7 +462,7 @@ private Mono<?> handleRestResponseReturnType(HttpDecodedResponse response, Swagg
// entityType = ? extends RestResponse<THeaders, Flux<ByteBuf>>
// entityType = ? extends RestResponse<THeaders, Boolean>
// entityType = ? extends RestResponse<THeaders, VirtualMachine>
asyncResult = handleBodyReturnType(response, methodParser, bodyType)
asyncResult = handleBodyReturnType(response, methodParser, genericArg2Type)
.map((Function<Object, RestResponse<?, ?>>) bodyAsObject -> {
try {
return responseConstructor.newInstance(response.sourceResponse().request(), responseStatusCode, deserializedHeaders, rawHeaders, bodyAsObject);
Expand Down Expand Up @@ -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<Boolean>
asyncResult = Mono.just(isSuccess);
} else if (TypeUtil.isTypeOrSubTypeOf(entityType, byte[].class)) {
// Mono<byte[]>
Expand All @@ -518,6 +519,9 @@ protected final Mono<?> handleBodyReturnType(final HttpDecodedResponse response,
} else if (FluxUtil.isFluxByteBuf(entityType)) {
// Mono<Flux<ByteBuf>>
asyncResult = Mono.just(response.sourceResponse().body());
} else if (TypeUtil.isTypeOrSubTypeOf(entityType, Mono.class)) {
// Mono<Mono<Object>>
asyncResult = Mono.just(response.decodedBody());
} else {
// Mono<Object>
asyncResult = response.decodedBody();
Expand Down Expand Up @@ -551,7 +555,7 @@ public final Object handleRestReturnType(Mono<HttpDecodedResponse> asyncHttpDeco
// ProxyMethod ReturnType: Mono<Void>
result = asyncExpectedResponse.then();
} else {
// ProxyMethod ReturnType: Mono<? extends RestResponse<?, ?>>
// ProxyMethod ReturnType: Mono<? extends RestResponse<?, ?>> or Mono<Object>
result = asyncExpectedResponse.flatMap(response ->
handleRestResponseReturnType(response, methodParser, monoTypeParam));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Foo> getFoo(args);}
* {@code Flux<Foo> 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<RestResponse<headers, Foo>> getFoo(args);}
* {@code Flux<RestResponse<headers, Foo>> 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<RestResponse<headers, Mono<Foo>>> 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<headers, Foo> 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<headers, Mono<Foo>> 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<Foo> 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<OperationStatus<Foo>> 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<OperationStatus<Foo>> 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<T>
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;
}

/**
Expand Down
113 changes: 113 additions & 0 deletions client-runtime/src/test/java/com/microsoft/rest/v3/RestProxyTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.*;

Expand Down Expand Up @@ -1136,6 +1138,9 @@ private interface Service20 {
@GET("bytes/100")
RestResponse<Map<String, String>,Void> getBytes100OnlyRawHeaders();

@GET("bytes/100")
Mono<RestResponse<Map<String, String>,Void>> getBytes100OnlyRawHeadersAsync();

@GET("bytes/100")
RestResponse<HttpBinHeaders,byte[]> getBytes100BodyAndHeaders();

Expand All @@ -1145,6 +1150,15 @@ private interface Service20 {
@PUT("put")
RestResponse<HttpBinHeaders,HttpBinJSON> putBodyAndHeaders(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) String body);

@PUT("put")
Mono<RestResponse<HttpBinHeaders,HttpBinJSON>> putBodyAndHeadersAsync(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) String body);

@PUT("put")
Mono<RestResponse<HttpBinHeaders,Mono<HttpBinJSON>>> putBodyAndHeadersAsyncContentAsync(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) String body);

@PUT("put")
RestResponse<HttpBinHeaders,Mono<HttpBinJSON>> putBodyAndHeadersContentAsync(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) String body);

@GET("bytes/100")
RestResponse<Void, Void> getBytesOnlyStatus();
}
Expand Down Expand Up @@ -1205,6 +1219,21 @@ public void service20GetBytesOnlyHeaders() {
assertNotEquals(0, response.headers().size());
}

@Test
public void service20GetBytesOnlyHeadersAsync() {
final Mono<RestResponse<Map<String, String>, 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<HttpBinHeaders,Void> response = createService(Service20.class)
Expand Down Expand Up @@ -1244,6 +1273,90 @@ public void service20PutBodyAndHeaders() {
assertNotEquals(0, headers.xProcessedTime);
}

@Test
public void service20PutBodyAndHeadersAsync() {
final Mono<RestResponse<HttpBinHeaders,HttpBinJSON>> 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<RestResponse<HttpBinHeaders, Mono<HttpBinJSON>>> asyncResponse = createService(Service20.class)
.putBodyAndHeadersAsyncContentAsync("body string");
//
asyncResponse.flatMap(response -> {
assertNotNull(response);
assertEquals(200, response.statusCode());
//
final Mono<HttpBinJSON> 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<HttpBinHeaders, Mono<HttpBinJSON>> response = createService(Service20.class)
.putBodyAndHeadersContentAsync("body string");

assertNotNull(response);
assertEquals(200, response.statusCode());

final Mono<HttpBinJSON> 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")
Expand Down