Browse Source

Supports custom expansion of template parameters via Param.Expander

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);
```

Closes #122
pull/152/head
Adrian Cole 10 years ago
parent
commit
3bddf1f0aa
  1. 1
      CHANGELOG.md
  2. 10
      README.md
  3. 10
      core/src/main/java/feign/Contract.java
  4. 11
      core/src/main/java/feign/MethodMetadata.java
  5. 17
      core/src/main/java/feign/Param.java
  6. 16
      core/src/main/java/feign/ReflectiveFeign.java
  7. 2
      core/src/main/java/feign/RequestTemplate.java
  8. 2
      core/src/main/java/feign/codec/Encoder.java
  9. 18
      core/src/test/java/feign/DefaultContractTest.java
  10. 21
      core/src/test/java/feign/FeignTest.java

1
CHANGELOG.md

@ -1,5 +1,6 @@ @@ -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 `*/*`

10
README.md

@ -228,6 +228,16 @@ Where possible, Feign configuration uses normal Dagger conventions. For example @@ -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

10
core/src/main/java/feign/Contract.java

@ -38,7 +38,7 @@ public interface Contract { @@ -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>();
@ -119,7 +119,7 @@ public interface Contract { @@ -119,7 +119,7 @@ public interface Contract {
}
}
static class Default extends BaseContract {
class Default extends BaseContract {
@Override
protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) {
@ -173,6 +173,12 @@ public interface Contract { @@ -173,6 +173,12 @@ public interface Contract {
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 &&

11
core/src/main/java/feign/MethodMetadata.java

@ -15,6 +15,7 @@ @@ -15,6 +15,7 @@
*/
package feign;
import feign.Param.Expander;
import java.io.Serializable;
import java.lang.reflect.Type;
import java.util.ArrayList;
@ -36,6 +37,8 @@ public final class MethodMetadata implements Serializable { @@ -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)
@ -49,9 +52,6 @@ public final class MethodMetadata implements Serializable { @@ -49,9 +52,6 @@ public final class MethodMetadata implements Serializable {
return this;
}
/**
* Method return type.
*/
public Type returnType() {
return returnType;
}
@ -100,6 +100,9 @@ public final class MethodMetadata implements Serializable { @@ -100,6 +100,9 @@ public final class MethodMetadata implements Serializable {
return indexToName;
}
private static final long serialVersionUID = 1L;
public Map<Integer, Class<? extends Expander>> indexToExpanderClass() {
return indexToExpanderClass;
}
private static final long serialVersionUID = 1L;
}

17
core/src/main/java/feign/Param.java

@ -20,9 +20,24 @@ import java.lang.annotation.Retention; @@ -20,9 +20,24 @@ import java.lang.annotation.Retention;
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
core/src/main/java/feign/ReflectiveFeign.java

@ -17,6 +17,7 @@ package feign; @@ -17,6 +17,7 @@ package feign;
import dagger.Provides;
import feign.InvocationHandlerFactory.MethodHandler;
import feign.Param.Expander;
import feign.Request.Options;
import feign.codec.Decoder;
import feign.codec.EncodeException;
@ -158,9 +159,20 @@ public class ReflectiveFeign extends Feign { @@ -158,9 +159,20 @@ public class ReflectiveFeign extends Feign {
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) {
@ -172,8 +184,12 @@ public class ReflectiveFeign extends Feign { @@ -172,8 +184,12 @@ public class ReflectiveFeign extends Feign {
}
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);
}

2
core/src/main/java/feign/RequestTemplate.java

@ -80,7 +80,7 @@ public final class RequestTemplate implements Serializable { @@ -80,7 +80,7 @@ public final class RequestTemplate implements Serializable {
}
/**
* 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>

2
core/src/main/java/feign/codec/Encoder.java

@ -71,7 +71,7 @@ public interface Encoder { @@ -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) {

18
core/src/test/java/feign/DefaultContractTest.java

@ -17,6 +17,7 @@ package feign; @@ -17,6 +17,7 @@ package feign;
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;
@ -247,6 +248,23 @@ public class DefaultContractTest { @@ -247,6 +248,23 @@ public class DefaultContractTest {
.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 {

21
core/src/test/java/feign/FeignTest.java

@ -30,6 +30,7 @@ import java.io.IOException; @@ -30,6 +30,7 @@ import java.io.IOException;
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;
@ -70,6 +71,14 @@ public class FeignTest { @@ -70,6 +71,14 @@ public class FeignTest {
@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() {
@ -224,6 +233,18 @@ public class FeignTest { @@ -224,6 +233,18 @@ public class FeignTest {
.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)",

Loading…
Cancel
Save