Skip to content

Commit

Permalink
Support kotlin coroutines (#1706)
Browse files Browse the repository at this point in the history
* Support kotlin coroutines

Resolves: #1565

Inspired by PlaytikaOSS/feign-reactive#486

## TODO

- [ ] Separate Kotlin support module
- [ ] Enhance test case
- [ ] Refactoring
- [ ] Clean up pom.xml

* Apply optional dependency to kotlin support related dependency

* Seperate Kotlin support module

* Remove unused code from ClassUtils.java

* Remove unused code from ClassUtils.java

* Refactor KotlinDetector

* Move ClassUtils location into KotlinDetector

* Move KotlinDetector location

* Format code

* First attempt to move kotlin work to it's own isolated module

* Coroutine Feign using AyncFeign

* Coroutine Feign using AyncFeign

* Refactor suspending function  detect logic

- Remove KotlinDetector.java
- Add Method.isSuspend extension function

* Cleanup CoroutineFeignTest test code format

* Fix suspend function contract parsing error when using http body

* Rename test names to be meaningful

* Add Github Example With Coroutine

- Copy of GithubExample

* Remove unnecessary dependency

https://github.com/OpenFeign/feign/pull/1706/files#r965389041

Co-authored-by: Marvin Froeder <velo.br@gmail.com>
Co-authored-by: Marvin Froeder <velo@users.noreply.github.com>
  • Loading branch information
3 people committed Sep 14, 2022
1 parent fbd4731 commit 39ed8ef
Show file tree
Hide file tree
Showing 18 changed files with 1,209 additions and 30 deletions.
1 change: 0 additions & 1 deletion core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
<scope>test</scope>
</dependency>

Expand Down
18 changes: 9 additions & 9 deletions core/src/main/java/feign/AsyncInvocation.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,45 +20,45 @@
* A specific invocation of an APU
*/
@Experimental
class AsyncInvocation<C> {
public class AsyncInvocation<C> {

private final C context;
private final MethodInfo methodInfo;
private final long startNanos;
private CompletableFuture<Response> responseFuture;

AsyncInvocation(C context, MethodInfo methodInfo) {
public AsyncInvocation(C context, MethodInfo methodInfo) {
super();
this.context = context;
this.methodInfo = methodInfo;
this.startNanos = System.nanoTime();
}

C context() {
public C context() {
return context;
}

String configKey() {
public String configKey() {
return methodInfo.configKey();
}

long startNanos() {
public long startNanos() {
return startNanos;
}

Type underlyingType() {
public Type underlyingType() {
return methodInfo.underlyingReturnType();
}

boolean isAsyncReturnType() {
public boolean isAsyncReturnType() {
return methodInfo.isAsyncReturnType();
}

void setResponseFuture(CompletableFuture<Response> responseFuture) {
public void setResponseFuture(CompletableFuture<Response> responseFuture) {
this.responseFuture = responseFuture;
}

CompletableFuture<Response> responseFuture() {
public CompletableFuture<Response> responseFuture() {
return responseFuture;
}
}
20 changes: 11 additions & 9 deletions core/src/main/java/feign/AsyncResponseHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
* handling
*/
@Experimental
class AsyncResponseHandler {
public class AsyncResponseHandler {

private static final long MAX_RESPONSE_BUFFER_SIZE = 8192L;

Expand All @@ -41,8 +41,9 @@ class AsyncResponseHandler {

private final ResponseInterceptor responseInterceptor;

AsyncResponseHandler(Level logLevel, Logger logger, Decoder decoder, ErrorDecoder errorDecoder,
boolean dismiss404, boolean closeAfterDecode, ResponseInterceptor responseInterceptor) {
public AsyncResponseHandler(Level logLevel, Logger logger, Decoder decoder,
ErrorDecoder errorDecoder, boolean dismiss404, boolean closeAfterDecode,
ResponseInterceptor responseInterceptor) {
super();
this.logLevel = logLevel;
this.logger = logger;
Expand All @@ -54,14 +55,15 @@ class AsyncResponseHandler {
}

boolean isVoidType(Type returnType) {
return Void.class == returnType || void.class == returnType;
return Void.class == returnType || void.class == returnType
|| returnType.getTypeName().equals("kotlin.Unit");
}

void handleResponse(CompletableFuture<Object> resultFuture,
String configKey,
Response response,
Type returnType,
long elapsedTime) {
public void handleResponse(CompletableFuture<Object> resultFuture,
String configKey,
Response response,
Type returnType,
long elapsedTime) {
// copied fairly liberally from SynchronousMethodHandler
boolean shouldClose = true;

Expand Down
4 changes: 4 additions & 0 deletions core/src/main/java/feign/Contract.java
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ protected MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method me
data.ignoreParamater(i);
}

if ("kotlin.coroutines.Continuation".equals(parameterTypes[i].getName())) {
data.ignoreParamater(i);
}

if (parameterTypes[i] == URI.class) {
data.urlIndex(i);
} else if (!isHttpAnnotation
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/feign/Feign.java
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ public Builder addCapability(Capability capability) {
/**
* Internal - used to indicate that the decoder should be immediately called
*/
Builder forceDecoding() {
public /* FIXME should not be public */ Builder forceDecoding() {
this.forceDecoding = true;
return this;
}
Expand Down
16 changes: 9 additions & 7 deletions core/src/main/java/feign/Logger.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@
*/
package feign;

import static feign.Util.UTF_8;
import static feign.Util.decodeOrDefault;
import static feign.Util.valuesOrEmpty;
import static java.util.Objects.nonNull;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.logging.FileHandler;
import java.util.logging.LogRecord;
import java.util.logging.SimpleFormatter;
import static feign.Util.*;
import static java.util.Objects.nonNull;

/**
* Simple logging abstraction for debug messages. Adapted from {@code retrofit.RestAdapter.Log}.
Expand Down Expand Up @@ -137,10 +139,10 @@ protected Response logAndRebufferResponse(String configKey,
return response;
}

protected IOException logIOException(String configKey,
Level logLevel,
IOException ioe,
long elapsedTime) {
public IOException logIOException(String configKey,
Level logLevel,
IOException ioe,
long elapsedTime) {
log(configKey, "<--- ERROR %s: %s (%sms)", ioe.getClass().getSimpleName(), ioe.getMessage(),
elapsedTime);
if (logLevel.ordinal() >= Level.FULL.ordinal()) {
Expand Down Expand Up @@ -217,7 +219,7 @@ public JavaLogger() {

/**
* Constructor for JavaLogger class
*
*
* @param loggerName a name for the logger. This should be a dot-separated name and should
* normally be based on the package name or class name of the subsystem, such as java.net
* or javax.swing
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/java/feign/MethodInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@
import java.util.concurrent.CompletableFuture;

@Experimental
class MethodInfo {
public class MethodInfo {
private final String configKey;
private final Type underlyingReturnType;
private final boolean asyncReturnType;

MethodInfo(String configKey, Type underlyingReturnType, boolean asyncReturnType) {
protected MethodInfo(String configKey, Type underlyingReturnType, boolean asyncReturnType) {
this.configKey = configKey;
this.underlyingReturnType = underlyingReturnType;
this.asyncReturnType = asyncReturnType;
Expand Down
8 changes: 7 additions & 1 deletion core/src/main/java/feign/Types.java
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ static Type getSupertype(Type context, Class<?> contextRawType, Class<?> superty
getGenericSupertype(context, contextRawType, supertype));
}

static Type resolve(Type context, Class<?> contextRawType, Type toResolve) {
public static Type resolve(Type context, Class<?> contextRawType, Type toResolve) {
// This implementation is made a little more complicated in an attempt to avoid object-creation.
while (true) {
if (toResolve instanceof TypeVariable) {
Expand Down Expand Up @@ -350,14 +350,17 @@ static final class ParameterizedTypeImpl implements ParameterizedType {
}
}

@Override
public Type[] getActualTypeArguments() {
return typeArguments.clone();
}

@Override
public Type getRawType() {
return rawType;
}

@Override
public Type getOwnerType() {
return ownerType;
}
Expand Down Expand Up @@ -395,6 +398,7 @@ private static final class GenericArrayTypeImpl implements GenericArrayType {
this.componentType = componentType;
}

@Override
public Type getGenericComponentType() {
return componentType;
}
Expand Down Expand Up @@ -454,10 +458,12 @@ static final class WildcardTypeImpl implements WildcardType {
}
}

@Override
public Type[] getUpperBounds() {
return new Type[] {upperBound};
}

@Override
public Type[] getLowerBounds() {
return lowerBound != null ? new Type[] {lowerBound} : EMPTY_TYPE_ARRAY;
}
Expand Down
10 changes: 10 additions & 0 deletions example-github-with-coroutine/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
GitHub Example With Coroutine
===================

This is an example of a simple json client.

=== Building example with Gradle
Install and run `gradle` to produce `build/github`

=== Building example with Maven
Install and run `mvn` to produce `target/github`
Loading

0 comments on commit 39ed8ef

Please sign in to comment.