package feign.jackson; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.std.StdSerializer; import org.junit.Test; import java.io.IOException; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import feign.RequestTemplate; import feign.Response; import static feign.Util.UTF_8; import static feign.assertj.FeignAssertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; public class JacksonCodecTest { private String zonesJson = ""// + "[\n"// + " {\n"// + " \"name\": \"denominator.io.\"\n"// + " },\n"// + " {\n"// + " \"name\": \"denominator.io.\",\n"// + " \"id\": \"ABCD\"\n"// + " }\n"// + "]\n"; @Test public void encodesMapObjectNumericalValuesAsInteger() throws Exception { Map<String, Object> map = new LinkedHashMap<String, Object>(); map.put("foo", 1); RequestTemplate template = new RequestTemplate(); new JacksonEncoder().encode(map, map.getClass(), template); assertThat(template).hasBody(""// + "{\n" // + " \"foo\" : 1\n" // + "}"); } @Test public void encodesFormParams() throws Exception { Map<String, Object> form = new LinkedHashMap<String, Object>(); form.put("foo", 1); form.put("bar", Arrays.asList(2, 3)); RequestTemplate template = new RequestTemplate(); new JacksonEncoder().encode(form, new TypeReference<Map<String, ?>>() { }.getType(), template); assertThat(template).hasBody(""// + "{\n" // + " \"foo\" : 1,\n" // + " \"bar\" : [ 2, 3 ]\n" // + "}"); } @Test public void decodes() throws Exception { List<Zone> zones = new LinkedList<Zone>(); zones.add(new Zone("denominator.io.")); zones.add(new Zone("denominator.io.", "ABCD")); Response response = Response.builder() .status(200) .reason("OK") .headers(Collections.<String, Collection<String>>emptyMap()) .body(zonesJson, UTF_8) .build(); assertEquals(zones, new JacksonDecoder().decode(response, new TypeReference<List<Zone>>() { }.getType())); } @Test public void nullBodyDecodesToNull() throws Exception { Response response = Response.builder() .status(204) .reason("OK") .headers(Collections.<String, Collection<String>>emptyMap()) .build(); assertNull(new JacksonDecoder().decode(response, String.class)); } @Test public void emptyBodyDecodesToNull() throws Exception { Response response = Response.builder() .status(204) .reason("OK") .headers(Collections.<String, Collection<String>>emptyMap()) .body(new byte[0]) .build(); assertNull(new JacksonDecoder().decode(response, String.class)); } @Test public void customDecoder() throws Exception { JacksonDecoder decoder = new JacksonDecoder( Arrays.<Module>asList( new SimpleModule().addDeserializer(Zone.class, new ZoneDeserializer()))); List<Zone> zones = new LinkedList<Zone>(); zones.add(new Zone("DENOMINATOR.IO.")); zones.add(new Zone("DENOMINATOR.IO.", "ABCD")); Response response = Response.builder() .status(200) .reason("OK") .headers(Collections.<String, Collection<String>>emptyMap()) .body(zonesJson, UTF_8) .build(); assertEquals(zones, decoder.decode(response, new TypeReference<List<Zone>>() { }.getType())); } @Test public void customEncoder() throws Exception { JacksonEncoder encoder = new JacksonEncoder( Arrays.<Module>asList(new SimpleModule().addSerializer(Zone.class, new ZoneSerializer()))); List<Zone> zones = new LinkedList<Zone>(); zones.add(new Zone("denominator.io.")); zones.add(new Zone("denominator.io.", "abcd")); RequestTemplate template = new RequestTemplate(); encoder.encode(zones, new TypeReference<List<Zone>>() { }.getType(), template); assertThat(template).hasBody("" // + "[ {\n" + " \"name\" : \"DENOMINATOR.IO.\"\n" + "}, {\n" + " \"name\" : \"DENOMINATOR.IO.\",\n" + " \"id\" : \"ABCD\"\n" + "} ]"); } static class Zone extends LinkedHashMap<String, Object> { private static final long serialVersionUID = 1L; Zone() { // for reflective instantiation. } Zone(String name) { this(name, null); } Zone(String name, String id) { put("name", name); if (id != null) { put("id", id); } } } static class ZoneDeserializer extends StdDeserializer<Zone> { public ZoneDeserializer() { super(Zone.class); } @Override public Zone deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { Zone zone = new Zone(); jp.nextToken(); while (jp.nextToken() != JsonToken.END_OBJECT) { String name = jp.getCurrentName(); String value = jp.getValueAsString(); if (value != null) { zone.put(name, value.toUpperCase()); } } return zone; } } static class ZoneSerializer extends StdSerializer<Zone> { public ZoneSerializer() { super(Zone.class); } @Override public void serialize(Zone value, JsonGenerator jgen, SerializerProvider provider) throws IOException { jgen.writeStartObject(); for (Map.Entry<String, Object> entry : value.entrySet()) { jgen.writeFieldName(entry.getKey()); jgen.writeString(entry.getValue().toString().toUpperCase()); } jgen.writeEndObject(); } } /** Enabled via {@link feign.Feign.Builder#decode404()} */ @Test public void notFoundDecodesToEmpty() throws Exception { Response response = Response.builder() .status(404) .reason("NOT FOUND") .headers(Collections.<String, Collection<String>>emptyMap()) .build(); assertThat((byte[]) new JacksonDecoder().decode(response, byte[].class)).isEmpty(); } }