8000 Supports custom expansion of template parameters via Param.Expander by codefromthecrypt · Pull Request #152 · OpenFeign/feign · GitHub
[go: up one dir, main page]

Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
### Version 7.1
* Introduces feign.@Param to annotate template parameters. Users must migrate from `javax.inject.@Named` to `feign.@Param` before updating to Feign 8.0.
* Supports custom expansion via `@Param(value = "name", expander = CustomExpander.class)`
* Adds OkHttp integration
* Allows multiple headers with the same name.
* Ensures Accept headers default to `*/*`
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,16 @@ Where possible, Feign configuration uses normal Dagger conventions. For example
};
}
```

#### Custom Parameter Expansion
Parameters annotated with `Param` expand based on their `toString`. By
specifying a custom `Param.Expander`, users can control this behavior,
for example formatting dates.

```java
@RequestLine("GET /?since={date}") Result list(@Param(value = "date", expander = DateToMillis.class) Date date);
```

#### Logging
You can log the http messages going to and from the target by setting up a `Logger`. Here's the easiest way to do that:
```java
Expand Down
10 changes: 8 additions & 2 deletions core/src/main/java/feign/Contract.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public interface Contract {
*/
List<MethodMetadata> parseAndValidatateMetadata(Class<?> declaring);

public static abstract class BaseContract implements Contract {
abstract class BaseContract implements Contract {

@Override public List<MethodMetadata> parseAndValidatateMetadata(Class<?> declaring) {
List<MethodMetadata> metadata = new ArrayList<MethodMetadata>();
Expand Down Expand Up @@ -119,7 +119,7 @@ protected void nameParam(MethodMetadata data, String name, int i) {
}
}

static class Default extends BaseContract {
class Default extends BaseContract {

@Override
protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) {
Expand Down Expand Up @@ -173,6 +173,12 @@ protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[
checkState(emptyToNull(name) != null,
"%s annotation was empty on param %s.", annotationType.getSimpleName(), paramIndex);
nameParam(data, name, paramIndex);
if (annotationType == Param.class) {
Class<? extends Param.Expander> expander = ((Param) annotation).expander();
if (expander != Param.ToStringExpander.class) {
data.indexToExpanderClass().put(paramIndex, expander);
}
}
isHttpAnnotation = true;
String varName = '{' + name + '}';
if (data.template().url().indexOf(varName) == -1 &&
Expand Down
11 changes: 7 additions & 4 deletions core/src/main/java/feign/MethodMetadata.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package feign;

import feign.Param.Expander;
import java.io.Serializable;
import java.lang.reflect.Type;
import java.util.ArrayList;
Expand All @@ -36,6 +37,8 @@ public final class MethodMetadata implements Serializable {
private RequestTemplate template = new RequestTemplate();
private List<String> formParams = new ArrayList<String>();
private Map<Integer, Collection<String>> indexToName = new LinkedHashMap<Integer, Collection<String>>();
private Map<Integer, Class<? extends Expander>> indexToExpanderClass =
new LinkedHashMap<Integer, Class<? extends Expander>>();

/**
* @see Feign#configKey(java.lang.reflect.Method)
Expand All @@ -49,9 +52,6 @@ MethodMetadata configKey(String configKey) {
return this;
}

/**
* Method return type.
*/
public Type returnType() {
return returnType;
}
Expand Down Expand Up @@ -100,6 +100,9 @@ public Map<Integer, Collection<String>> indexToName() {
return indexToName;
}

private static final long serialVersionUID = 1L;
public Map<Integer, Class<? extends Expander>> indexToExpanderClass() {
return indexToExpanderClass;
}

private static final long serialVersionUID = 1L;
}
17 changes: 16 additions & 1 deletion core/src/main/java/feign/Param.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,24 @@
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/** The name of a template variable applied to {@link Headers}, {@linkplain RequestLine} or {@linkplain Body} */
/** A named template parameter applied to {@link Headers}, {@linkplain RequestLine} or {@linkplain Body} */
@Retention(RUNTIME)
@java.lang.annotation.Target(PARAMETER)
public @interface Param {
/** The name of the template parameter. */
String value();

/** How to expand the value of this parameter, if {@link ToStringExpander} isn't adequate. */
Class<? extends Expander> expander() default ToStringExpander.class;

interface Expander {
/** Expands the value into a string. Does not accept or return null. */
String expand(Object value);
}

final class ToStringExpander implements Expander {
@Override public String expand(Object value) {
return value.toString();
}
}
}
16 changes: 16 additions & 0 deletions core/src/main/java/feign/ReflectiveFeign.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import dagger.Provides;
import feign.InvocationHandlerFactory.MethodHandler;
import feign.Param.Expander;
import feign.Request.Options;
import feign.codec.Decoder;
import feign.codec.EncodeException;
Expand Down Expand Up @@ -158,9 +159,20 @@ public Map<String, MethodHandler> apply(Target key) {

private static class BuildTemplateByResolvingArgs implements RequestTemplate.Factory {
protected final MethodMetadata metadata;
private final Map<Integer, Expander> indexToExpander = new LinkedHashMap<Integer, Expander>();

private BuildTemplateByResolvingArgs(MethodMetadata metadata) {
this.metadata = metadata;
if (metadata.indexToExpanderClass().isEmpty()) return;
for (Entry<Integer, Class<? extends Expander>> indexToExpanderClass : metadata.indexToExpanderClass().entrySet()) {
try {
indexToExpander.put(indexToExpanderClass.getKey(), indexToExpanderClass.getValue().newInstance());
} catch (InstantiationException e) {
throw new IllegalStateException(e);
} catch (IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
}

@Override public RequestTemplate create(Object[] argv) {
Expand All @@ -172,8 +184,12 @@ private BuildTemplateByResolvingArgs(MethodMetadata metadata) {
}
Map<String, Object> varBuilder = new LinkedHashMap<String, Object>();
for (Entry<Integer, Collection<String>> entry : metadata.indexToName().entrySet()) {
int i = entry.getKey();
Object value = argv[entry.getKey()];
if (value != null) { // Null values are skipped.
if (indexToExpander.containsKey(i)) {
value = indexToExpander.get(i).expand(value);
}
for (String name : entry.getValue())
varBuilder.put(name, value);
}
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/feign/RequestTemplate.java
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public RequestTemplate(RequestTemplate toCopy) {
}

/**
* Resolves any templated variables in the requests path, query, or headers
* Resolves any template parameters in the requests path, query, or headers
* against the supplied unencoded arguments.
* <br>
* <br><br><b>relationship to JAXRS 2.0</b><br>
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/feign/codec/Encoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public interface Encoder {
/**
* Default implementation of {@code Encoder}.
*/
public class Default implements Encoder {
class Default implements Encoder {
@Override
public void encode(Object object, RequestTemplate template) throws EncodeException {
if (object instanceof String) {
Expand Down
18 changes: 18 additions & 0 deletions core/src/test/java/feign/DefaultContractTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import com.google.gson.reflect.TypeToken;
import java.net.URI;
import java.util.Date;
import java.util.List;
import javax.inject.Named;
import org.junit.Rule;
Expand Down Expand Up @@ -247,6 +248,23 @@ interface HeaderParams {
.containsExactly(entry(0, asList("Auth-Token")));
}

interface CustomExpander {
@RequestLine("POST /?date={date}") void date(@Param(value = "date", expander = DateToMillis.class) Date date);
}

class DateToMillis implements Param.Expander {
@Override public String expand(Object value) {
return String.valueOf(((Date) value).getTime());
}
}

@Test public void customExpander() throws Exception {
MethodMetadata md = contract.parseAndValidatateMetadata(CustomExpander.class.getDeclaredMethod("date", Date.class));

assertThat(md.indexToExpanderClass())
.containsExactly(entry(0, DateToMillis.class));
}

// TODO: remove all of below in 8.x

interface WithPathAndQueryParamsAnnotatedWithNamed {
Expand Down
21 changes: 21 additions & 0 deletions core/src/test/java/feign/FeignTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import java.lang.reflect.Type;
import java.net.URI;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import javax.inject.Singleton;
Expand Down Expand Up @@ -70,6 +71,14 @@ void login(

@RequestLine("GET /?1={1}&2={2}") Response queryParams(@Param("1") String one, @Param("2") Iterable<String> twos);

@RequestLine("POST /?date={date}") void expand(@Param(value = "date", expander = DateToMillis.class) Date date);

class DateToMillis implements Param.Expander {
@Override public String expand(Object value) {
return String.valueOf(((Date) value).getTime());
}
}

@dagger.Module(injects = Feign.class, addsTo = Feign.Defaults.class)
static class Module {
@Provides Decoder defaultDecoder() {
Expand Down Expand Up @@ -224,6 +233,18 @@ public void multipleInterceptor() throws IOException, InterruptedException {
.hasHeaders("X-Forwarded-For: origin.host.com", "User-Agent: Feign");
}

@Test public void customExpander() throws Exception {
server.enqueue(new MockResponse());

TestInterface api =
Feign.create(TestInterface.class, "http://localhost:" + server.getPort(), new TestInterface.Module());

api.expand(new Date(1234l));

assertThat(server.takeRequest())
.hasPath("/?date=1234");
}

@Test public void toKeyMethodFormatsAsExpected() throws Exception {
assertEquals("TestInterface#post()", Feign.configKey(TestInterface.class.getDeclaredMethod("post")));
assertEquals("TestInterface#uriParam(String,URI,String)",
Expand Down
0