diff --git a/CHANGELOG.md b/CHANGELOG.md
index aa2bd3a9..61f3ae94 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
### Version 8.0
* Removes Dagger 1.x Dependency
* Removes support for parameters annotated with `javax.inject.@Named`. Use `feign.@Param` instead.
+* Makes body parameter type explicit.
### 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.
diff --git a/core/src/main/java/feign/MethodMetadata.java b/core/src/main/java/feign/MethodMetadata.java
index bca3678c..61bbc38a 100644
--- a/core/src/main/java/feign/MethodMetadata.java
+++ b/core/src/main/java/feign/MethodMetadata.java
@@ -79,6 +79,7 @@ public final class MethodMetadata implements Serializable {
return this;
}
+ /** Type corresponding to {@link #bodyIndex()}. */
public Type bodyType() {
return bodyType;
}
diff --git a/core/src/main/java/feign/ReflectiveFeign.java b/core/src/main/java/feign/ReflectiveFeign.java
index 541bd5f6..bbb1ac0d 100644
--- a/core/src/main/java/feign/ReflectiveFeign.java
+++ b/core/src/main/java/feign/ReflectiveFeign.java
@@ -201,7 +201,7 @@ public class ReflectiveFeign extends Feign {
formVariables.put(entry.getKey(), entry.getValue());
}
try {
- encoder.encode(formVariables, mutable);
+ encoder.encode(formVariables, Types.MAP_STRING_WILDCARD, mutable);
} catch (EncodeException e) {
throw e;
} catch (RuntimeException e) {
@@ -224,7 +224,7 @@ public class ReflectiveFeign extends Feign {
Object body = argv[metadata.bodyIndex()];
checkArgument(body != null, "Body parameter %s was null", metadata.bodyIndex());
try {
- encoder.encode(body, mutable);
+ encoder.encode(body, metadata.bodyType(), mutable);
} catch (EncodeException e) {
throw e;
} catch (RuntimeException e) {
diff --git a/core/src/main/java/feign/Types.java b/core/src/main/java/feign/Types.java
index bfdc00fd..39755775 100644
--- a/core/src/main/java/feign/Types.java
+++ b/core/src/main/java/feign/Types.java
@@ -23,6 +23,7 @@ import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.Arrays;
+import java.util.Map;
import java.util.NoSuchElementException;
/**
@@ -32,6 +33,10 @@ import java.util.NoSuchElementException;
* @author Jesse Wilson
*/
final class Types {
+ /** Type literal for {@code Map}. */
+ static final Type MAP_STRING_WILDCARD = new ParameterizedTypeImpl(null, Map.class, String.class,
+ new WildcardTypeImpl(new Type[] { Object.class }, new Type[] { }));
+
private static final Type[] EMPTY_TYPE_ARRAY = new Type[0];
private Types() {
diff --git a/core/src/main/java/feign/codec/Encoder.java b/core/src/main/java/feign/codec/Encoder.java
index f9ba93fe..b34c5524 100644
--- a/core/src/main/java/feign/codec/Encoder.java
+++ b/core/src/main/java/feign/codec/Encoder.java
@@ -16,6 +16,7 @@
package feign.codec;
import feign.RequestTemplate;
+import java.lang.reflect.Type;
import static java.lang.String.format;
@@ -40,8 +41,8 @@ import static java.lang.String.format;
* }
*
* @Override
- * public void encode(Object object, RequestTemplate template) {
- * template.body(gson.toJson(object));
+ * public void encode(Object object, Type bodyType, RequestTemplate template) {
+ * template.body(gson.toJson(object, bodyType));
* }
* }
*
@@ -59,24 +60,25 @@ import static java.lang.String.format;
*
*/
public interface Encoder {
+
/**
* Converts objects to an appropriate representation in the template.
*
* @param object what to encode as the request body.
+ * @param bodyType the type the object should be encoded as. {@code Map}, if form encoding.
* @param template the request template to populate.
* @throws EncodeException when encoding failed due to a checked exception.
*/
- void encode(Object object, RequestTemplate template) throws EncodeException;
+ void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException;
/**
* Default implementation of {@code Encoder}.
*/
class Default implements Encoder {
- @Override
- public void encode(Object object, RequestTemplate template) throws EncodeException {
- if (object instanceof String) {
+ @Override public void encode(Object object, Type bodyType, RequestTemplate template) {
+ if (bodyType == String.class) {
template.body(object.toString());
- } else if (object instanceof byte[]) {
+ } else if (bodyType == byte[].class) {
template.body((byte[]) object, null);
} else if (object != null) {
throw new EncodeException(format("%s is not a type supported by this encoder.", object.getClass()));
diff --git a/core/src/test/java/feign/DefaultContractTest.java b/core/src/test/java/feign/DefaultContractTest.java
index 739d9884..12e7bba0 100644
--- a/core/src/test/java/feign/DefaultContractTest.java
+++ b/core/src/test/java/feign/DefaultContractTest.java
@@ -231,6 +231,14 @@ public class DefaultContractTest {
);
}
+ /** Body type is only for the body param. */
+ @Test public void formParamsDoesNotSetBodyType() throws Exception {
+ MethodMetadata md = contract.parseAndValidatateMetadata(FormParams.class.getDeclaredMethod("login", String.class,
+ String.class, String.class));
+
+ assertThat(md.bodyType()).isNull();
+ }
+
interface HeaderParams {
@RequestLine("POST /")
@Headers({"Auth-Token: {Auth-Token}", "Auth-Token: Foo"})
diff --git a/core/src/test/java/feign/FeignBuilderTest.java b/core/src/test/java/feign/FeignBuilderTest.java
index 5020e2b2..63d452ea 100644
--- a/core/src/test/java/feign/FeignBuilderTest.java
+++ b/core/src/test/java/feign/FeignBuilderTest.java
@@ -18,10 +18,7 @@ package feign;
import com.squareup.okhttp.mockwebserver.MockResponse;
import com.squareup.okhttp.mockwebserver.rule.MockWebServerRule;
import feign.codec.Decoder;
-import feign.codec.EncodeException;
import feign.codec.Encoder;
-import org.junit.Rule;
-
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
@@ -29,6 +26,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
+import org.junit.Rule;
import org.junit.Test;
import static feign.assertj.MockWebServerAssertions.assertThat;
@@ -63,8 +61,7 @@ public class FeignBuilderTest {
String url = "http://localhost:" + server.getPort();
Encoder encoder = new Encoder() {
- @Override
- public void encode(Object object, RequestTemplate template) throws EncodeException {
+ @Override public void encode(Object object, Type bodyType, RequestTemplate template) {
template.body(object.toString());
}
};
diff --git a/core/src/test/java/feign/FeignTest.java b/core/src/test/java/feign/FeignTest.java
index 939190a5..47e5e091 100644
--- a/core/src/test/java/feign/FeignTest.java
+++ b/core/src/test/java/feign/FeignTest.java
@@ -16,11 +16,13 @@
package feign;
import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
import com.squareup.okhttp.mockwebserver.MockResponse;
import com.squareup.okhttp.mockwebserver.SocketPolicy;
import com.squareup.okhttp.mockwebserver.rule.MockWebServerRule;
import feign.Target.HardCodedTarget;
import feign.codec.Decoder;
+import feign.codec.EncodeException;
import feign.codec.Encoder;
import feign.codec.ErrorDecoder;
import feign.codec.StringDecoder;
@@ -31,6 +33,7 @@ import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
@@ -137,6 +140,26 @@ public class FeignTest {
.hasBody("[netflix, denominator, password]");
}
+ /** The type of a parameter value may not be the desired type to encode as. Prefer the interface type. */
+ @Test public void bodyTypeCorrespondsWithParameterType() throws IOException, InterruptedException {
+ server.enqueue(new MockResponse().setBody("foo"));
+
+ final AtomicReference encodedType = new AtomicReference();
+ TestInterface api = new TestInterfaceBuilder()
+ .encoder(new Encoder.Default() {
+ @Override public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
+ encodedType.set(bodyType);
+ }
+ })
+ .target("http://localhost:" + server.getPort());
+
+ api.body(Arrays.asList("netflix", "denominator", "password"));
+
+ server.takeRequest();
+
+ assertThat(encodedType.get()).isEqualTo(new TypeToken>(){}.getType());
+ }
+
@Test public void postGZIPEncodedBodyParam() throws IOException, InterruptedException {
server.enqueue(new MockResponse().setBody("foo"));
@@ -348,7 +371,7 @@ public class FeignTest {
private final Feign.Builder delegate = new Feign.Builder()
.decoder(new Decoder.Default())
.encoder(new Encoder() {
- @Override public void encode(Object object, RequestTemplate template) {
+ @Override public void encode(Object object, Type bodyType, RequestTemplate template) {
if (object instanceof Map) {
template.body(new Gson().toJson(object));
} else {
@@ -362,8 +385,8 @@ public class FeignTest {
return this;
}
- TestInterfaceBuilder client(Client client) {
- delegate.client(client);
+ TestInterfaceBuilder encoder(Encoder encoder) {
+ delegate.encoder(encoder);
return this;
}
diff --git a/core/src/test/java/feign/assertj/RequestTemplateAssert.java b/core/src/test/java/feign/assertj/RequestTemplateAssert.java
index 82832220..b2145ae7 100644
--- a/core/src/test/java/feign/assertj/RequestTemplateAssert.java
+++ b/core/src/test/java/feign/assertj/RequestTemplateAssert.java
@@ -46,7 +46,12 @@ public final class RequestTemplateAssert extends AbstractAssert", actual.bodyTemplate());
+ }
+ objects.assertEqual(info, new String(actual.body(), UTF_8), utf8Expected);
+ return this;
}
public RequestTemplateAssert hasBody(byte[] expected) {
diff --git a/core/src/test/java/feign/codec/DefaultEncoderTest.java b/core/src/test/java/feign/codec/DefaultEncoderTest.java
index 1b643aa9..71d33674 100644
--- a/core/src/test/java/feign/codec/DefaultEncoderTest.java
+++ b/core/src/test/java/feign/codec/DefaultEncoderTest.java
@@ -34,14 +34,14 @@ public class DefaultEncoderTest {
@Test public void testEncodesStrings() throws Exception {
String content = "This is my content";
RequestTemplate template = new RequestTemplate();
- encoder.encode(content, template);
+ encoder.encode(content, String.class, template);
assertEquals(content, new String(template.body(), UTF_8));
}
@Test public void testEncodesByteArray() throws Exception {
byte[] content = {12, 34, 56};
RequestTemplate template = new RequestTemplate();
- encoder.encode(content, template);
+ encoder.encode(content, byte[].class, template);
assertTrue(Arrays.equals(content, template.body()));
}
@@ -49,6 +49,6 @@ public class DefaultEncoderTest {
thrown.expect(EncodeException.class);
thrown.expectMessage("is not a type supported by this encoder.");
- encoder.encode(new Date(), new RequestTemplate());
+ encoder.encode(new Date(), Date.class, new RequestTemplate());
}
}
diff --git a/gson/src/main/java/feign/gson/GsonEncoder.java b/gson/src/main/java/feign/gson/GsonEncoder.java
index 57e1b54c..a01772b6 100644
--- a/gson/src/main/java/feign/gson/GsonEncoder.java
+++ b/gson/src/main/java/feign/gson/GsonEncoder.java
@@ -19,6 +19,7 @@ import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import feign.RequestTemplate;
import feign.codec.Encoder;
+import java.lang.reflect.Type;
import java.util.Collections;
public class GsonEncoder implements Encoder {
@@ -36,7 +37,7 @@ public class GsonEncoder implements Encoder {
this.gson = gson;
}
- @Override public void encode(Object object, RequestTemplate template) {
- template.body(gson.toJson(object));
+ @Override public void encode(Object object, Type bodyType, RequestTemplate template) {
+ template.body(gson.toJson(object, bodyType));
}
}
diff --git a/gson/src/test/java/feign/gson/GsonCodecTest.java b/gson/src/test/java/feign/gson/GsonCodecTest.java
index dab2824d..c3b1ba3b 100644
--- a/gson/src/test/java/feign/gson/GsonCodecTest.java
+++ b/gson/src/test/java/feign/gson/GsonCodecTest.java
@@ -43,7 +43,7 @@ public class GsonCodecTest {
map.put("foo", 1);
RequestTemplate template = new RequestTemplate();
- new GsonEncoder().encode(map, template);
+ new GsonEncoder().encode(map, map.getClass(), template);
assertThat(template).hasBody("" //
+ "{\n" //
@@ -68,7 +68,7 @@ public class GsonCodecTest {
form.put("bar", Arrays.asList(2, 3));
RequestTemplate template = new RequestTemplate();
- new GsonEncoder().encode(form, template);
+ new GsonEncoder().encode(form, new TypeToken
*/
public class JAXBDecoder implements Decoder {
- private final JAXBContextFactory jaxbContextFactory;
+ private final JAXBContextFactory jaxbContextFactory;
- public JAXBDecoder(JAXBContextFactory jaxbContextFactory) {
- this.jaxbContextFactory = jaxbContextFactory;
- }
+ public JAXBDecoder(JAXBContextFactory jaxbContextFactory) {
+ this.jaxbContextFactory = jaxbContextFactory;
+ }
- @Override
- public Object decode(Response response, Type type) throws IOException, FeignException {
- try {
- Unmarshaller unmarshaller = jaxbContextFactory.createUnmarshaller((Class) type);
- return unmarshaller.unmarshal(response.body().asInputStream());
- } catch (JAXBException e) {
- throw new DecodeException(e.toString(), e);
- } finally {
- if(response.body() != null) {
- response.body().close();
- }
- }
+ @Override public Object decode(Response response, Type type) throws IOException {
+ if (!(type instanceof Class)) {
+ throw new UnsupportedOperationException("JAXB only supports decoding raw types. Found " + type);
+ }
+ try {
+ Unmarshaller unmarshaller = jaxbContextFactory.createUnmarshaller((Class) type);
+ return unmarshaller.unmarshal(response.body().asInputStream());
+ } catch (JAXBException e) {
+ throw new DecodeException(e.toString(), e);
+ } finally {
+ if (response.body() != null) {
+ response.body().close();
+ }
}
+ }
}
diff --git a/jaxb/src/main/java/feign/jaxb/JAXBEncoder.java b/jaxb/src/main/java/feign/jaxb/JAXBEncoder.java
index 4b7801cb..79c546ef 100644
--- a/jaxb/src/main/java/feign/jaxb/JAXBEncoder.java
+++ b/jaxb/src/main/java/feign/jaxb/JAXBEncoder.java
@@ -19,6 +19,8 @@ import feign.RequestTemplate;
import feign.codec.EncodeException;
import feign.codec.Encoder;
import java.io.StringWriter;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
@@ -43,21 +45,23 @@ import javax.xml.bind.Marshaller;
*
*/
public class JAXBEncoder implements Encoder {
- private final JAXBContextFactory jaxbContextFactory;
+ private final JAXBContextFactory jaxbContextFactory;
- public JAXBEncoder(JAXBContextFactory jaxbContextFactory) {
- this.jaxbContextFactory = jaxbContextFactory;
- }
+ public JAXBEncoder(JAXBContextFactory jaxbContextFactory) {
+ this.jaxbContextFactory = jaxbContextFactory;
+ }
- @Override
- public void encode(Object object, RequestTemplate template) throws EncodeException {
- try {
- Marshaller marshaller = jaxbContextFactory.createMarshaller(object.getClass());
- StringWriter stringWriter = new StringWriter();
- marshaller.marshal(object, stringWriter);
- template.body(stringWriter.toString());
- } catch (JAXBException e) {
- throw new EncodeException(e.toString(), e);
- }
+ @Override public void encode(Object object, Type bodyType, RequestTemplate template) {
+ if (!(bodyType instanceof Class)) {
+ throw new UnsupportedOperationException("JAXB only supports encoding raw types. Found " + bodyType);
+ }
+ try {
+ Marshaller marshaller = jaxbContextFactory.createMarshaller((Class) bodyType);
+ StringWriter stringWriter = new StringWriter();
+ marshaller.marshal(object, stringWriter);
+ template.body(stringWriter.toString());
+ } catch (JAXBException e) {
+ throw new EncodeException(e.toString(), e);
}
+ }
}
diff --git a/jaxb/src/test/java/feign/jaxb/JAXBCodecTest.java b/jaxb/src/test/java/feign/jaxb/JAXBCodecTest.java
index 30bb99ff..051e644f 100644
--- a/jaxb/src/test/java/feign/jaxb/JAXBCodecTest.java
+++ b/jaxb/src/test/java/feign/jaxb/JAXBCodecTest.java
@@ -18,148 +18,170 @@ package feign.jaxb;
import feign.RequestTemplate;
import feign.Response;
import feign.codec.Encoder;
+import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Collections;
+import java.util.Map;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
import static feign.Util.UTF_8;
import static feign.assertj.FeignAssertions.assertThat;
import static org.junit.Assert.assertEquals;
public class JAXBCodecTest {
+ @Rule public final ExpectedException thrown = ExpectedException.none();
- @XmlRootElement
- @XmlAccessorType(XmlAccessType.FIELD)
- static class MockObject {
-
- @XmlElement
- private String value;
-
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof MockObject) {
- MockObject other = (MockObject) obj;
- return value.equals(other.value);
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return value != null ? value.hashCode() : 0;
- }
- }
+ @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) static class MockObject {
- @Test
- public void encodesXml() throws Exception {
- MockObject mock = new MockObject();
- mock.value = "Test";
+ @XmlElement private String value;
- RequestTemplate template = new RequestTemplate();
- new JAXBEncoder(new JAXBContextFactory.Builder().build()).encode(mock, template);
+ @Override public boolean equals(Object obj) {
+ if (obj instanceof MockObject) {
+ MockObject other = (MockObject) obj;
+ return value.equals(other.value);
+ }
+ return false;
+ }
- assertThat(template).hasBody(
- "Test");
+ @Override public int hashCode() {
+ return value != null ? value.hashCode() : 0;
}
+ }
- @Test
- public void encodesXmlWithCustomJAXBEncoding() throws Exception {
- JAXBContextFactory jaxbContextFactory = new JAXBContextFactory.Builder()
- .withMarshallerJAXBEncoding("UTF-16")
- .build();
+ @Test public void encodesXml() throws Exception {
+ MockObject mock = new MockObject();
+ mock.value = "Test";
- Encoder encoder = new JAXBEncoder(jaxbContextFactory);
+ RequestTemplate template = new RequestTemplate();
+ new JAXBEncoder(new JAXBContextFactory.Builder().build()).encode(mock, MockObject.class, template);
- MockObject mock = new MockObject();
- mock.value = "Test";
+ assertThat(template).hasBody(
+ "Test");
+ }
- RequestTemplate template = new RequestTemplate();
- encoder.encode(mock, template);
+ @Test public void doesntEncodeParameterizedTypes() throws Exception {
+ thrown.expect(UnsupportedOperationException.class);
+ thrown.expectMessage("JAXB only supports encoding raw types. Found java.util.Map");
- assertThat(template).hasBody("Test");
+ class ParameterizedHolder {
+ Map field;
}
+ Type parameterized = ParameterizedHolder.class.getDeclaredField("field").getGenericType();
- @Test
- public void encodesXmlWithCustomJAXBSchemaLocation() throws Exception {
- JAXBContextFactory jaxbContextFactory = new JAXBContextFactory.Builder()
- .withMarshallerSchemaLocation("http://apihost http://apihost/schema.xsd")
- .build();
+ RequestTemplate template = new RequestTemplate();
+ new JAXBEncoder(new JAXBContextFactory.Builder().build()).encode(Collections.emptyMap(), parameterized, template);
+ }
- Encoder encoder = new JAXBEncoder(jaxbContextFactory);
+ @Test public void encodesXmlWithCustomJAXBEncoding() throws Exception {
+ JAXBContextFactory jaxbContextFactory =
+ new JAXBContextFactory.Builder().withMarshallerJAXBEncoding("UTF-16").build();
- MockObject mock = new MockObject();
- mock.value = "Test";
+ Encoder encoder = new JAXBEncoder(jaxbContextFactory);
- RequestTemplate template = new RequestTemplate();
- encoder.encode(mock, template);
+ MockObject mock = new MockObject();
+ mock.value = "Test";
- assertThat(template).hasBody("" +
- "Test");
- }
+ RequestTemplate template = new RequestTemplate();
+ encoder.encode(mock, MockObject.class, template);
- @Test
- public void encodesXmlWithCustomJAXBNoNamespaceSchemaLocation() throws Exception {
- JAXBContextFactory jaxbContextFactory = new JAXBContextFactory.Builder()
- .withMarshallerNoNamespaceSchemaLocation("http://apihost/schema.xsd")
- .build();
+ assertThat(template).hasBody("Test");
+ }
- Encoder encoder = new JAXBEncoder(jaxbContextFactory);
+ @Test public void encodesXmlWithCustomJAXBSchemaLocation() throws Exception {
+ JAXBContextFactory jaxbContextFactory =
+ new JAXBContextFactory.Builder().withMarshallerSchemaLocation("http://apihost http://apihost/schema.xsd")
+ .build();
- MockObject mock = new MockObject();
- mock.value = "Test";
+ Encoder encoder = new JAXBEncoder(jaxbContextFactory);
- RequestTemplate template = new RequestTemplate();
- encoder.encode(mock, template);
+ MockObject mock = new MockObject();
+ mock.value = "Test";
- assertThat(template).hasBody("" +
- "Test");
- }
+ RequestTemplate template = new RequestTemplate();
+ encoder.encode(mock, MockObject.class, template);
- @Test
- public void encodesXmlWithCustomJAXBFormattedOutput() {
- JAXBContextFactory jaxbContextFactory = new JAXBContextFactory.Builder()
- .withMarshallerFormattedOutput(true)
- .build();
+ assertThat(template).hasBody("" +
+ "Test");
+ }
- Encoder encoder = new JAXBEncoder(jaxbContextFactory);
+ @Test public void encodesXmlWithCustomJAXBNoNamespaceSchemaLocation() throws Exception {
+ JAXBContextFactory jaxbContextFactory =
+ new JAXBContextFactory.Builder().withMarshallerNoNamespaceSchemaLocation("http://apihost/schema.xsd").build();
- MockObject mock = new MockObject();
- mock.value = "Test";
+ Encoder encoder = new JAXBEncoder(jaxbContextFactory);
- RequestTemplate template = new RequestTemplate();
- encoder.encode(mock, template);
+ MockObject mock = new MockObject();
+ mock.value = "Test";
- String NEWLINE = System.getProperty("line.separator");
+ RequestTemplate template = new RequestTemplate();
+ encoder.encode(mock, MockObject.class, template);
- assertThat(template).hasBody(new StringBuilder()
- .append("").append(NEWLINE)
- .append("").append(NEWLINE)
- .append(" Test").append(NEWLINE)
- .append("").append(NEWLINE).toString());
- }
+ assertThat(template).hasBody("" +
+ "Test");
+ }
+
+ @Test public void encodesXmlWithCustomJAXBFormattedOutput() {
+ JAXBContextFactory jaxbContextFactory =
+ new JAXBContextFactory.Builder().withMarshallerFormattedOutput(true).build();
+
+ Encoder encoder = new JAXBEncoder(jaxbContextFactory);
+
+ MockObject mock = new MockObject();
+ mock.value = "Test";
- @Test
- public void decodesXml() throws Exception {
- MockObject mock = new MockObject();
- mock.value = "Test";
+ RequestTemplate template = new RequestTemplate();
+ encoder.encode(mock, MockObject.class, template);
- String mockXml = "" +
- "Test";
+ String NEWLINE = System.getProperty("line.separator");
- Response response =
- Response.create(200, "OK", Collections.>emptyMap(), mockXml, UTF_8);
+ assertThat(template).hasBody(
+ new StringBuilder().append("")
+ .append(NEWLINE)
+ .append("")
+ .append(NEWLINE)
+ .append(" Test")
+ .append(NEWLINE)
+ .append("")
+ .append(NEWLINE)
+ .toString());
+ }
- JAXBDecoder decoder = new JAXBDecoder(new JAXBContextFactory.Builder().build());
+ @Test public void decodesXml() throws Exception {
+ MockObject mock = new MockObject();
+ mock.value = "Test";
- assertEquals(mock, decoder.decode(response, MockObject.class));
+ String mockXml = ""
+ + "Test";
+
+ Response response = Response.create(200, "OK", Collections.>emptyMap(), mockXml, UTF_8);
+
+ JAXBDecoder decoder = new JAXBDecoder(new JAXBContextFactory.Builder().build());
+
+ assertEquals(mock, decoder.decode(response, MockObject.class));
+ }
+
+ @Test public void doesntDecodeParameterizedTypes() throws Exception {
+ thrown.expect(UnsupportedOperationException.class);
+ thrown.expectMessage("JAXB only supports decoding raw types. Found java.util.Map");
+
+ class ParameterizedHolder {
+ Map field;
}
+ Type parameterized = ParameterizedHolder.class.getDeclaredField("field").getGenericType();
+
+ Response response = Response.create(200, "OK", Collections.>emptyMap(), "", UTF_8);
+
+ new JAXBDecoder(new JAXBContextFactory.Builder().build()).decode(response, parameterized);
+ }
}
diff --git a/jaxb/src/test/java/feign/jaxb/JAXBContextFactoryTest.java b/jaxb/src/test/java/feign/jaxb/JAXBContextFactoryTest.java
index b9ffbd30..4410a666 100644
--- a/jaxb/src/test/java/feign/jaxb/JAXBContextFactoryTest.java
+++ b/jaxb/src/test/java/feign/jaxb/JAXBContextFactoryTest.java
@@ -22,55 +22,41 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class JAXBContextFactoryTest {
- @Test
- public void buildsMarshallerWithJAXBEncodingProperty() throws Exception {
- JAXBContextFactory factory = new JAXBContextFactory.Builder()
- .withMarshallerJAXBEncoding("UTF-16")
- .build();
+ @Test public void buildsMarshallerWithJAXBEncodingProperty() throws Exception {
+ JAXBContextFactory factory = new JAXBContextFactory.Builder().withMarshallerJAXBEncoding("UTF-16").build();
- Marshaller marshaller = factory.createMarshaller(Object.class);
- assertEquals("UTF-16", marshaller.getProperty(Marshaller.JAXB_ENCODING));
- }
+ Marshaller marshaller = factory.createMarshaller(Object.class);
+ assertEquals("UTF-16", marshaller.getProperty(Marshaller.JAXB_ENCODING));
+ }
- @Test
- public void buildsMarshallerWithSchemaLocationProperty() throws Exception {
- JAXBContextFactory factory = new JAXBContextFactory.Builder()
- .withMarshallerSchemaLocation("http://apihost http://apihost/schema.xsd")
- .build();
+ @Test public void buildsMarshallerWithSchemaLocationProperty() throws Exception {
+ JAXBContextFactory factory =
+ new JAXBContextFactory.Builder().withMarshallerSchemaLocation("http://apihost http://apihost/schema.xsd")
+ .build();
- Marshaller marshaller = factory.createMarshaller(Object.class);
- assertEquals("http://apihost http://apihost/schema.xsd",
- marshaller.getProperty(Marshaller.JAXB_SCHEMA_LOCATION));
- }
+ Marshaller marshaller = factory.createMarshaller(Object.class);
+ assertEquals("http://apihost http://apihost/schema.xsd", marshaller.getProperty(Marshaller.JAXB_SCHEMA_LOCATION));
+ }
- @Test
- public void buildsMarshallerWithNoNamespaceSchemaLocationProperty() throws Exception {
- JAXBContextFactory factory = new JAXBContextFactory.Builder()
- .withMarshallerNoNamespaceSchemaLocation("http://apihost/schema.xsd")
- .build();
+ @Test public void buildsMarshallerWithNoNamespaceSchemaLocationProperty() throws Exception {
+ JAXBContextFactory factory =
+ new JAXBContextFactory.Builder().withMarshallerNoNamespaceSchemaLocation("http://apihost/schema.xsd").build();
- Marshaller marshaller = factory.createMarshaller(Object.class);
- assertEquals("http://apihost/schema.xsd",
- marshaller.getProperty(Marshaller.JAXB_NO_NAMESPACE_SCHEMA_LOCATION));
- }
+ Marshaller marshaller = factory.createMarshaller(Object.class);
+ assertEquals("http://apihost/schema.xsd", marshaller.getProperty(Marshaller.JAXB_NO_NAMESPACE_SCHEMA_LOCATION));
+ }
- @Test
- public void buildsMarshallerWithFormattedOutputProperty() throws Exception {
- JAXBContextFactory factory = new JAXBContextFactory.Builder()
- .withMarshallerFormattedOutput(true)
- .build();
+ @Test public void buildsMarshallerWithFormattedOutputProperty() throws Exception {
+ JAXBContextFactory factory = new JAXBContextFactory.Builder().withMarshallerFormattedOutput(true).build();
- Marshaller marshaller = factory.createMarshaller(Object.class);
- assertTrue((Boolean) marshaller.getProperty(Marshaller.JAXB_FORMATTED_OUTPUT));
- }
+ Marshaller marshaller = factory.createMarshaller(Object.class);
+ assertTrue((Boolean) marshaller.getProperty(Marshaller.JAXB_FORMATTED_OUTPUT));
+ }
- @Test
- public void buildsMarshallerWithFragmentProperty() throws Exception {
- JAXBContextFactory factory = new JAXBContextFactory.Builder()
- .withMarshallerFragment(true)
- .build();
+ @Test public void buildsMarshallerWithFragmentProperty() throws Exception {
+ JAXBContextFactory factory = new JAXBContextFactory.Builder().withMarshallerFragment(true).build();
- Marshaller marshaller = factory.createMarshaller(Object.class);
- assertTrue((Boolean) marshaller.getProperty(Marshaller.JAXB_FRAGMENT));
- }
+ Marshaller marshaller = factory.createMarshaller(Object.class);
+ assertTrue((Boolean) marshaller.getProperty(Marshaller.JAXB_FRAGMENT));
+ }
}
diff --git a/jaxb/src/test/java/feign/jaxb/examples/AWSSignatureVersion4.java b/jaxb/src/test/java/feign/jaxb/examples/AWSSignatureVersion4.java
index aaacbe71..683638fd 100644
--- a/jaxb/src/test/java/feign/jaxb/examples/AWSSignatureVersion4.java
+++ b/jaxb/src/test/java/feign/jaxb/examples/AWSSignatureVersion4.java
@@ -30,133 +30,132 @@ import static feign.Util.UTF_8;
// http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
public class AWSSignatureVersion4 {
- String region = "us-east-1";
- String service = "iam";
- String accessKey;
- String secretKey;
-
- public AWSSignatureVersion4(String accessKey, String secretKey) {
- this.accessKey = accessKey;
- this.secretKey = secretKey;
- }
-
- public Request apply(RequestTemplate input) {
- if (!input.headers().isEmpty()) throw new UnsupportedOperationException("headers not supported");
- if (input.body() != null) throw new UnsupportedOperationException("body not supported");
-
- String host = URI.create(input.url()).getHost();
-
- String timestamp;
- synchronized (iso8601) {
- timestamp = iso8601.format(new Date());
- }
-
- String credentialScope = String.format("%s/%s/%s/%s", timestamp.substring(0, 8), region, service, "aws4_request");
-
- input.query("X-Amz-Algorithm", "AWS4-HMAC-SHA256");
- input.query("X-Amz-Credential", accessKey + "/" + credentialScope);
- input.query("X-Amz-Date", timestamp);
- input.query("X-Amz-SignedHeaders", "host");
- input.header("Host", host);
-
- String canonicalString = canonicalString(input, host);
- String toSign = toSign(timestamp, credentialScope, canonicalString);
+ String region = "us-east-1";
+ String service = "iam";
+ String accessKey;
+ String secretKey;
- byte[] signatureKey = signatureKey(secretKey, timestamp);
- String signature = hex(hmacSHA256(toSign, signatureKey));
+ public AWSSignatureVersion4(String accessKey, String secretKey) {
+ this.accessKey = accessKey;
+ this.secretKey = secretKey;
+ }
- input.query("X-Amz-Signature", signature);
+ public Request apply(RequestTemplate input) {
+ if (!input.headers().isEmpty()) throw new UnsupportedOperationException("headers not supported");
+ if (input.body() != null) throw new UnsupportedOperationException("body not supported");
- return input.request();
- }
+ String host = URI.create(input.url()).getHost();
- byte[] signatureKey(String secretKey, String timestamp) {
- byte[] kSecret = ("AWS4" + secretKey).getBytes(UTF_8);
- byte[] kDate = hmacSHA256(timestamp.substring(0, 8), kSecret);
- byte[] kRegion = hmacSHA256(region, kDate);
- byte[] kService = hmacSHA256(service, kRegion);
- byte[] kSigning = hmacSHA256("aws4_request", kService);
- return kSigning;
+ String timestamp;
+ synchronized (iso8601) {
+ timestamp = iso8601.format(new Date());
}
- static byte[] hmacSHA256(String data, byte[] key) {
- try {
- String algorithm = "HmacSHA256";
- Mac mac = Mac.getInstance(algorithm);
- mac.init(new SecretKeySpec(key, algorithm));
- return mac.doFinal(data.getBytes(UTF_8));
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
+ String credentialScope = String.format("%s/%s/%s/%s", timestamp.substring(0, 8), region, service, "aws4_request");
+
+ input.query("X-Amz-Algorithm", "AWS4-HMAC-SHA256");
+ input.query("X-Amz-Credential", accessKey + "/" + credentialScope);
+ input.query("X-Amz-Date", timestamp);
+ input.query("X-Amz-SignedHeaders", "host");
+ input.header("Host", host);
+
+ String canonicalString = canonicalString(input, host);
+ String toSign = toSign(timestamp, credentialScope, canonicalString);
+
+ byte[] signatureKey = signatureKey(secretKey, timestamp);
+ String signature = hex(hmacSHA256(toSign, signatureKey));
+
+ input.query("X-Amz-Signature", signature);
+
+ return input.request();
+ }
+
+ byte[] signatureKey(String secretKey, String timestamp) {
+ byte[] kSecret = ("AWS4" + secretKey).getBytes(UTF_8);
+ byte[] kDate = hmacSHA256(timestamp.substring(0, 8), kSecret);
+ byte[] kRegion = hmacSHA256(region, kDate);
+ byte[] kService = hmacSHA256(service, kRegion);
+ byte[] kSigning = hmacSHA256("aws4_request", kService);
+ return kSigning;
+ }
+
+ static byte[] hmacSHA256(String data, byte[] key) {
+ try {
+ String algorithm = "HmacSHA256";
+ Mac mac = Mac.getInstance(algorithm);
+ mac.init(new SecretKeySpec(key, algorithm));
+ return mac.doFinal(data.getBytes(UTF_8));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
}
+ }
- private static final String EMPTY_STRING_HASH = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
+ private static final String EMPTY_STRING_HASH = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
- private static String canonicalString(RequestTemplate input, String host) {
- StringBuilder canonicalRequest = new StringBuilder();
- // HTTPRequestMethod + '\n' +
- canonicalRequest.append(input.method()).append('\n');
+ private static String canonicalString(RequestTemplate input, String host) {
+ StringBuilder canonicalRequest = new StringBuilder();
+ // HTTPRequestMethod + '\n' +
+ canonicalRequest.append(input.method()).append('\n');
- // CanonicalURI + '\n' +
- canonicalRequest.append(URI.create(input.url()).getPath()).append('\n');
+ // CanonicalURI + '\n' +
+ canonicalRequest.append(URI.create(input.url()).getPath()).append('\n');
- // CanonicalQueryString + '\n' +
- canonicalRequest.append(input.queryLine().substring(1));
- canonicalRequest.append('\n');
+ // CanonicalQueryString + '\n' +
+ canonicalRequest.append(input.queryLine().substring(1));
+ canonicalRequest.append('\n');
- // CanonicalHeaders + '\n' +
- canonicalRequest.append("host:").append(host).append('\n');
+ // CanonicalHeaders + '\n' +
+ canonicalRequest.append("host:").append(host).append('\n');
- canonicalRequest.append('\n');
+ canonicalRequest.append('\n');
- // SignedHeaders + '\n' +
- canonicalRequest.append("host").append('\n');
+ // SignedHeaders + '\n' +
+ canonicalRequest.append("host").append('\n');
- // HexEncode(Hash(Payload))
- String bodyText =
- input.charset() != null && input.body() != null ? new String(input.body(), input.charset()) : null;
- if (bodyText != null) {
- canonicalRequest.append(hex(sha256(bodyText)));
- } else {
- canonicalRequest.append(EMPTY_STRING_HASH);
- }
- return canonicalRequest.toString();
+ // HexEncode(Hash(Payload))
+ String bodyText =
+ input.charset() != null && input.body() != null ? new String(input.body(), input.charset()) : null;
+ if (bodyText != null) {
+ canonicalRequest.append(hex(sha256(bodyText)));
+ } else {
+ canonicalRequest.append(EMPTY_STRING_HASH);
}
-
- private static String toSign(String timestamp, String credentialScope, String canonicalRequest) {
- StringBuilder toSign = new StringBuilder();
- // Algorithm + '\n' +
- toSign.append("AWS4-HMAC-SHA256").append('\n');
- // RequestDate + '\n' +
- toSign.append(timestamp).append('\n');
- // CredentialScope + '\n' +
- toSign.append(credentialScope).append('\n');
- // HexEncode(Hash(CanonicalRequest))
- toSign.append(hex(sha256(canonicalRequest)));
- return toSign.toString();
+ return canonicalRequest.toString();
+ }
+
+ private static String toSign(String timestamp, String credentialScope, String canonicalRequest) {
+ StringBuilder toSign = new StringBuilder();
+ // Algorithm + '\n' +
+ toSign.append("AWS4-HMAC-SHA256").append('\n');
+ // RequestDate + '\n' +
+ toSign.append(timestamp).append('\n');
+ // CredentialScope + '\n' +
+ toSign.append(credentialScope).append('\n');
+ // HexEncode(Hash(CanonicalRequest))
+ toSign.append(hex(sha256(canonicalRequest)));
+ return toSign.toString();
+ }
+
+ private static String hex(byte[] data) {
+ StringBuilder result = new StringBuilder(data.length * 2);
+ for (byte b : data) {
+ result.append(String.format("%02x", b & 0xff));
}
-
-
- private static String hex(byte[] data) {
- StringBuilder result = new StringBuilder(data.length * 2);
- for (byte b : data) {
- result.append(String.format("%02x", b & 0xff));
- }
- return result.toString();
+ return result.toString();
+ }
+
+ static byte[] sha256(String data) {
+ try {
+ MessageDigest digest = MessageDigest.getInstance("SHA-256");
+ return digest.digest(data.getBytes(UTF_8));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
}
+ }
- static byte[] sha256(String data) {
- try {
- MessageDigest digest = MessageDigest.getInstance("SHA-256");
- return digest.digest(data.getBytes(UTF_8));
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- private static final SimpleDateFormat iso8601 = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
+ private static final SimpleDateFormat iso8601 = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
- static {
- iso8601.setTimeZone(TimeZone.getTimeZone("GMT"));
- }
+ static {
+ iso8601.setTimeZone(TimeZone.getTimeZone("GMT"));
+ }
}
diff --git a/jaxb/src/test/java/feign/jaxb/examples/IAMExample.java b/jaxb/src/test/java/feign/jaxb/examples/IAMExample.java
index cdf64245..e8443ffd 100644
--- a/jaxb/src/test/java/feign/jaxb/examples/IAMExample.java
+++ b/jaxb/src/test/java/feign/jaxb/examples/IAMExample.java
@@ -30,61 +30,53 @@ import javax.xml.bind.annotation.XmlType;
public class IAMExample {
- interface IAM {
- @RequestLine("GET /?Action=GetUser&Version=2010-05-08") GetUserResponse userResponse();
- }
-
- public static void main(String... args) {
- IAM iam = Feign.builder()
- .decoder(new JAXBDecoder(new JAXBContextFactory.Builder().build()))
- .target(new IAMTarget(args[0], args[1]));
-
- GetUserResponse response = iam.userResponse();
- System.out.println("UserId: " + response.result.user.id);
- }
-
- static class IAMTarget extends AWSSignatureVersion4 implements Target {
+ interface IAM {
+ @RequestLine("GET /?Action=GetUser&Version=2010-05-08") GetUserResponse userResponse();
+ }
- @Override public Class type() {
- return IAM.class;
- }
+ public static void main(String... args) {
+ IAM iam = Feign.builder()
+ .decoder(new JAXBDecoder(new JAXBContextFactory.Builder().build()))
+ .target(new IAMTarget(args[0], args[1]));
- @Override public String name() {
- return "iam";
- }
+ GetUserResponse response = iam.userResponse();
+ System.out.println("UserId: " + response.result.user.id);
+ }
- @Override public String url() {
- return "https://iam.amazonaws.com";
- }
+ static class IAMTarget extends AWSSignatureVersion4 implements Target {
- private IAMTarget(String accessKey, String secretKey) {
- super(accessKey, secretKey);
- }
+ @Override public Class type() {
+ return IAM.class;
+ }
- @Override public Request apply(RequestTemplate in) {
- in.insert(0, url());
- return super.apply(in);
- }
+ @Override public String name() {
+ return "iam";
}
- @XmlRootElement(name = "GetUserResponse", namespace = "https://iam.amazonaws.com/doc/2010-05-08/")
- @XmlAccessorType(XmlAccessType.FIELD)
- static class GetUserResponse {
- @XmlElement(name = "GetUserResult")
- private GetUserResult result;
+ @Override public String url() {
+ return "https://iam.amazonaws.com";
}
- @XmlAccessorType(XmlAccessType.FIELD)
- @XmlType(name = "GetUserResult")
- static class GetUserResult {
- @XmlElement(name = "User")
- private User user;
+ private IAMTarget(String accessKey, String secretKey) {
+ super(accessKey, secretKey);
}
- @XmlAccessorType(XmlAccessType.FIELD)
- @XmlType(name = "User")
- static class User {
- @XmlElement(name = "UserId")
- private String id;
+ @Override public Request apply(RequestTemplate in) {
+ in.insert(0, url());
+ return super.apply(in);
}
+ }
+
+ @XmlRootElement(name = "GetUserResponse", namespace = "https://iam.amazonaws.com/doc/2010-05-08/")
+ @XmlAccessorType(XmlAccessType.FIELD) static class GetUserResponse {
+ @XmlElement(name = "GetUserResult") private GetUserResult result;
+ }
+
+ @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "GetUserResult") static class GetUserResult {
+ @XmlElement(name = "User") private User user;
+ }
+
+ @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "User") static class User {
+ @XmlElement(name = "UserId") private String id;
+ }
}
diff --git a/jaxb/src/test/java/feign/jaxb/examples/package-info.java b/jaxb/src/test/java/feign/jaxb/examples/package-info.java
index 0038947a..d52c85ad 100644
--- a/jaxb/src/test/java/feign/jaxb/examples/package-info.java
+++ b/jaxb/src/test/java/feign/jaxb/examples/package-info.java
@@ -13,5 +13,4 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-@javax.xml.bind.annotation.XmlSchema(namespace = "https://iam.amazonaws.com/doc/2010-05-08/", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
-package feign.jaxb.examples;
+@javax.xml.bind.annotation.XmlSchema(namespace = "https://iam.amazonaws.com/doc/2010-05-08/", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED) package feign.jaxb.examples;
diff --git a/jaxrs/src/test/java/feign/jaxrs/JAXRSContractTest.java b/jaxrs/src/test/java/feign/jaxrs/JAXRSContractTest.java
index e3cb2872..a4b28678 100644
--- a/jaxrs/src/test/java/feign/jaxrs/JAXRSContractTest.java
+++ b/jaxrs/src/test/java/feign/jaxrs/JAXRSContractTest.java
@@ -44,7 +44,7 @@ import static java.util.Arrays.asList;
import static org.assertj.core.data.MapEntry.entry;
/**
- * Tests interfaces defined per {@link feign.jaxrs.JAXRSContract} are interpreted into expected {@link feign
+ * Tests interfaces defined per {@link JAXRSContract} are interpreted into expected {@link feign
* .RequestTemplate template}
* instances.
*/
@@ -332,6 +332,14 @@ public class JAXRSContractTest {
);
}
+ /** Body type is only for the body param. */
+ @Test public void formParamsDoesNotSetBodyType() throws Exception {
+ MethodMetadata md = contract.parseAndValidatateMetadata(FormParams.class.getDeclaredMethod("login", String.class,
+ String.class, String.class));
+
+ assertThat(md.bodyType()).isNull();
+ }
+
@Test public void emptyFormParam() throws Exception {
thrown.expect(IllegalStateException.class);
thrown.expectMessage("FormParam.value() was empty on parameter 0");