From 183a5a119ce2985cfe637113985b87e817ea9daf Mon Sep 17 00:00:00 2001
From: Adrian Cole
Date: Sat, 31 Jan 2015 12:00:48 -0800
Subject: [PATCH] Makes body parameter type explicit
Feign has `MethodMetadata.bodyType()`, but never passed it to encoders.
Encoders that register type adapters need to do so based on the
interface desired as opposed to the implementation class. This change
breaks api compatibility for < 8.x, by requiring an additional arg
on `Encoder.encode`.
see https://github.com/square/retrofit/issues/713
---
CHANGELOG.md | 1 +
core/src/main/java/feign/MethodMetadata.java | 1 +
core/src/main/java/feign/ReflectiveFeign.java | 4 +-
core/src/main/java/feign/Types.java | 5 +
core/src/main/java/feign/codec/Encoder.java | 16 +-
.../test/java/feign/DefaultContractTest.java | 8 +
.../src/test/java/feign/FeignBuilderTest.java | 7 +-
core/src/test/java/feign/FeignTest.java | 29 ++-
.../feign/assertj/RequestTemplateAssert.java | 7 +-
.../java/feign/codec/DefaultEncoderTest.java | 6 +-
.../src/main/java/feign/gson/GsonEncoder.java | 5 +-
.../test/java/feign/gson/GsonCodecTest.java | 32 ++-
.../java/feign/jackson/JacksonEncoder.java | 7 +-
.../java/feign/jackson/JacksonCodecTest.java | 56 ++++-
.../java/feign/jaxb/JAXBContextFactory.java | 154 ++++++-------
.../src/main/java/feign/jaxb/JAXBDecoder.java | 35 +--
.../src/main/java/feign/jaxb/JAXBEncoder.java | 32 +--
.../test/java/feign/jaxb/JAXBCodecTest.java | 218 ++++++++++--------
.../feign/jaxb/JAXBContextFactoryTest.java | 70 +++---
.../jaxb/examples/AWSSignatureVersion4.java | 217 +++++++++--------
.../java/feign/jaxb/examples/IAMExample.java | 82 +++----
.../feign/jaxb/examples/package-info.java | 3 +-
.../java/feign/jaxrs/JAXRSContractTest.java | 10 +-
23 files changed, 561 insertions(+), 444 deletions(-)
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");