ashleyfrieze
3 years ago
committed by
GitHub
11 changed files with 756 additions and 0 deletions
@ -0,0 +1,43 @@
@@ -0,0 +1,43 @@
|
||||
Jackson Jr Codec |
||||
=================== |
||||
|
||||
This module adds support for encoding and decoding JSON via Jackson Jr. This |
||||
is a significantly smaller and faster version of Jackson, which may be of benefit |
||||
in smaller runtime containers. |
||||
|
||||
Add `JacksonJrEncoder` and/or `JacksonJrDecoder` to your `Feign.Builder` like so: |
||||
|
||||
```java |
||||
GitHub github = Feign.builder() |
||||
.encoder(new JacksonJrEncoder()) |
||||
.decoder(new JacksonJrDecoder()) |
||||
.target(GitHub.class, "https://api.github.com"); |
||||
``` |
||||
|
||||
If you want to customize the `JSON` object that is used, provide it to the `JacksonJrEncoder` and `JacksonJrDecoder`: |
||||
|
||||
```java |
||||
JSON json = Json.builder() |
||||
.register(JacksonAnnotationExtension.builder() |
||||
.withVisibility(JsonAutoDetect.Value.defaultVisibility()) |
||||
.build()) |
||||
.build(); |
||||
|
||||
GitHub github = Feign.builder() |
||||
.encoder(new JacksonJrEncoder(json)) |
||||
.decoder(new JacksonJrDecoder(json)) |
||||
.target(GitHub.class, "https://api.github.com"); |
||||
``` |
||||
|
||||
Customisation is also possible by passing `JacksonJrExtension` objects |
||||
into the constructor of the `JacksonJrEncoder` or `JacksonJrDecoder`: |
||||
|
||||
```java |
||||
List<JacksonJrExtension> extensions = singletonList(JacksonAnnotationExtension.builder() |
||||
.withVisibility(JsonAutoDetect.Value.defaultVisibility()) |
||||
.build()); |
||||
GitHub github = Feign.builder() |
||||
.encoder(new JacksonJrEncoder(extensions)) |
||||
.decoder(new JacksonJrDecoder(extensions)) |
||||
.target(GitHub.class, "https://api.github.com"); |
||||
``` |
@ -0,0 +1,58 @@
@@ -0,0 +1,58 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<!-- |
||||
|
||||
Copyright 2012-2021 The Feign Authors |
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except |
||||
in compliance with the License. You may obtain a copy of the License at |
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License |
||||
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
||||
or implied. See the License for the specific language governing permissions and limitations under |
||||
the License. |
||||
|
||||
--> |
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
||||
<modelVersion>4.0.0</modelVersion> |
||||
|
||||
<parent> |
||||
<groupId>io.github.openfeign</groupId> |
||||
<artifactId>parent</artifactId> |
||||
<version>11.3-SNAPSHOT</version> |
||||
</parent> |
||||
|
||||
<artifactId>feign-jackson-jr</artifactId> |
||||
<name>Feign Jackson Jr</name> |
||||
<description>Feign Jackson Jr</description> |
||||
|
||||
<properties> |
||||
<main.basedir>${project.basedir}/..</main.basedir> |
||||
</properties> |
||||
|
||||
<dependencies> |
||||
<dependency> |
||||
<groupId>${project.groupId}</groupId> |
||||
<artifactId>feign-core</artifactId> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>com.fasterxml.jackson.jr</groupId> |
||||
<artifactId>jackson-jr-objects</artifactId> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>com.fasterxml.jackson.jr</groupId> |
||||
<artifactId>jackson-jr-annotation-support</artifactId> |
||||
<scope>test</scope> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>${project.groupId}</groupId> |
||||
<artifactId>feign-core</artifactId> |
||||
<type>test-jar</type> |
||||
<scope>test</scope> |
||||
</dependency> |
||||
</dependencies> |
||||
</project> |
@ -0,0 +1,107 @@
@@ -0,0 +1,107 @@
|
||||
/** |
||||
* Copyright 2012-2021 The Feign Authors |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except |
||||
* in compliance with the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License |
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
||||
* or implied. See the License for the specific language governing permissions and limitations under |
||||
* the License. |
||||
*/ |
||||
package feign.jackson.jr; |
||||
|
||||
import com.fasterxml.jackson.jr.ob.JSON; |
||||
import com.fasterxml.jackson.jr.ob.JSONObjectException; |
||||
import com.fasterxml.jackson.jr.ob.JacksonJrExtension; |
||||
import feign.Response; |
||||
import feign.codec.DecodeException; |
||||
import feign.codec.Decoder; |
||||
import java.io.BufferedReader; |
||||
import java.io.IOException; |
||||
import java.io.Reader; |
||||
import java.lang.reflect.ParameterizedType; |
||||
import java.lang.reflect.Type; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* A {@link Decoder} that uses Jackson Jr to convert objects to String or byte representation. |
||||
*/ |
||||
public class JacksonJrDecoder extends JacksonJrMapper implements Decoder { |
||||
|
||||
@FunctionalInterface |
||||
interface Transformer { |
||||
Object apply(JSON mapper, Reader reader) throws IOException; |
||||
} |
||||
|
||||
public JacksonJrDecoder() { |
||||
super(); |
||||
} |
||||
|
||||
/** |
||||
* Construct with a custom {@link JSON} to use for decoding |
||||
* |
||||
* @param mapper the mapper to use |
||||
*/ |
||||
public JacksonJrDecoder(JSON mapper) { |
||||
super(mapper); |
||||
} |
||||
|
||||
/** |
||||
* Construct with a series of {@link JacksonJrExtension} objects that are registered into the |
||||
* {@link JSON} |
||||
* |
||||
* @param iterable the source of the extensions |
||||
*/ |
||||
public JacksonJrDecoder(Iterable<JacksonJrExtension> iterable) { |
||||
super(iterable); |
||||
} |
||||
|
||||
@Override |
||||
public Object decode(Response response, Type type) throws IOException { |
||||
|
||||
Transformer transformer = findTransformer(response, type); |
||||
|
||||
if (response.body() == null) { |
||||
return null; |
||||
} |
||||
Reader reader = response.body().asReader(response.charset()); |
||||
if (!reader.markSupported()) { |
||||
reader = new BufferedReader(reader, 1); |
||||
} |
||||
try { |
||||
// Read the first byte to see if we have any data
|
||||
reader.mark(1); |
||||
if (reader.read() == -1) { |
||||
return null; // Eagerly returning null avoids "No content to map due to end-of-input"
|
||||
} |
||||
reader.reset(); |
||||
return transformer.apply(mapper, reader); |
||||
} catch (JSONObjectException e) { |
||||
if (e.getCause() instanceof IOException) { |
||||
throw (IOException) e.getCause(); |
||||
} |
||||
throw e; |
||||
} |
||||
} |
||||
|
||||
private static Transformer findTransformer(Response response, Type type) { |
||||
if (type instanceof Class) { |
||||
return (mapper, reader) -> mapper.beanFrom((Class<?>) type, reader); |
||||
} |
||||
if (type instanceof ParameterizedType) { |
||||
Type rawType = ((ParameterizedType) type).getRawType(); |
||||
Type[] parameterType = ((ParameterizedType) type).getActualTypeArguments(); |
||||
if (rawType.equals(List.class)) { |
||||
return (mapper, reader) -> mapper.listOfFrom((Class<?>) parameterType[0], reader); |
||||
} |
||||
if (rawType.equals(Map.class)) { |
||||
return (mapper, reader) -> mapper.mapOfFrom((Class<?>) parameterType[1], reader); |
||||
} |
||||
} |
||||
throw new DecodeException(500, "Cannot decode type: " + type.getTypeName(), response.request()); |
||||
} |
||||
} |
@ -0,0 +1,65 @@
@@ -0,0 +1,65 @@
|
||||
/** |
||||
* Copyright 2012-2021 The Feign Authors |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except |
||||
* in compliance with the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License |
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
||||
* or implied. See the License for the specific language governing permissions and limitations under |
||||
* the License. |
||||
*/ |
||||
package feign.jackson.jr; |
||||
|
||||
import java.io.IOException; |
||||
import java.lang.reflect.Type; |
||||
import com.fasterxml.jackson.jr.ob.JSON; |
||||
import com.fasterxml.jackson.jr.ob.JacksonJrExtension; |
||||
import feign.RequestTemplate; |
||||
import feign.codec.EncodeException; |
||||
import feign.codec.Encoder; |
||||
import static java.lang.String.format; |
||||
|
||||
/** |
||||
* A {@link Encoder} that uses Jackson Jr to convert objects to String or byte representation. |
||||
*/ |
||||
public class JacksonJrEncoder extends JacksonJrMapper implements Encoder { |
||||
|
||||
public JacksonJrEncoder() { |
||||
super(); |
||||
} |
||||
|
||||
/** |
||||
* Construct with a custom {@link JSON} to use for encoding |
||||
* |
||||
* @param mapper the mapper to use |
||||
*/ |
||||
public JacksonJrEncoder(JSON mapper) { |
||||
super(mapper); |
||||
} |
||||
|
||||
/** |
||||
* Construct with a series of {@link JacksonJrExtension} objects that are registered into the |
||||
* {@link JSON} |
||||
* |
||||
* @param iterable the source of the extensions |
||||
*/ |
||||
public JacksonJrEncoder(Iterable<JacksonJrExtension> iterable) { |
||||
super(iterable); |
||||
} |
||||
|
||||
@Override |
||||
public void encode(Object object, Type bodyType, RequestTemplate template) { |
||||
try { |
||||
if (bodyType == byte[].class) { |
||||
template.body(mapper.asBytes(object), null); |
||||
} else { |
||||
template.body(mapper.asString(object)); |
||||
} |
||||
} catch (IOException e) { |
||||
throw new EncodeException(e.getMessage(), e); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,49 @@
@@ -0,0 +1,49 @@
|
||||
/** |
||||
* Copyright 2012-2021 The Feign Authors |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except |
||||
* in compliance with the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License |
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
||||
* or implied. See the License for the specific language governing permissions and limitations under |
||||
* the License. |
||||
*/ |
||||
package feign.jackson.jr; |
||||
|
||||
import com.fasterxml.jackson.jr.ob.JSON; |
||||
import com.fasterxml.jackson.jr.ob.JacksonJrExtension; |
||||
|
||||
/** |
||||
* Common implementation of the holder of a {@link JSON}, DRYing out construction. |
||||
*/ |
||||
abstract class JacksonJrMapper { |
||||
protected final JSON mapper; |
||||
|
||||
protected JacksonJrMapper() { |
||||
this(JSON.std); |
||||
} |
||||
|
||||
/** |
||||
* Construct with a custom {@link JSON} to use for decoding/encoding |
||||
* |
||||
* @param mapper the mapper to use |
||||
*/ |
||||
protected JacksonJrMapper(JSON mapper) { |
||||
this.mapper = mapper; |
||||
} |
||||
|
||||
/** |
||||
* Construct with a series of {@link JacksonJrExtension} objects that are registered into the |
||||
* {@link JSON} |
||||
* |
||||
* @param iterable the source of the extensions |
||||
*/ |
||||
protected JacksonJrMapper(Iterable<JacksonJrExtension> iterable) { |
||||
JSON.Builder builder = JSON.builder(); |
||||
iterable.forEach(builder::register); |
||||
this.mapper = builder.build(); |
||||
} |
||||
} |
@ -0,0 +1,285 @@
@@ -0,0 +1,285 @@
|
||||
/** |
||||
* Copyright 2012-2021 The Feign Authors |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except |
||||
* in compliance with the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License |
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
||||
* or implied. See the License for the specific language governing permissions and limitations under |
||||
* the License. |
||||
*/ |
||||
package feign.jackson.jr; |
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference; |
||||
import com.fasterxml.jackson.jr.ob.JSON; |
||||
import feign.Request; |
||||
import feign.Request.HttpMethod; |
||||
import feign.RequestTemplate; |
||||
import feign.Response; |
||||
import feign.Util; |
||||
import org.junit.Test; |
||||
import java.io.IOException; |
||||
import java.nio.charset.StandardCharsets; |
||||
import java.time.LocalDate; |
||||
import java.util.*; |
||||
import static feign.Util.UTF_8; |
||||
import static feign.assertj.FeignAssertions.assertThat; |
||||
import static java.util.Collections.singletonList; |
||||
import static org.junit.Assert.assertEquals; |
||||
import static org.junit.Assert.assertNull; |
||||
|
||||
public class JacksonCodecTest { |
||||
|
||||
private static final String DATES_JSON = "[\"2020-01-02\",\"2021-02-03\"]"; |
||||
|
||||
@Test |
||||
public void encodesMapObjectNumericalValuesAsInteger() { |
||||
Map<String, Object> map = new LinkedHashMap<>(); |
||||
map.put("foo", 1); |
||||
|
||||
RequestTemplate template = new RequestTemplate(); |
||||
new JacksonJrEncoder().encode(map, map.getClass(), template); |
||||
|
||||
assertThat(template).hasBody("{\"foo\":1}"); |
||||
} |
||||
|
||||
@Test |
||||
public void encodesMapObjectNumericalValuesToByteArray() { |
||||
Map<String, Object> map = new LinkedHashMap<>(); |
||||
map.put("foo", 1); |
||||
|
||||
RequestTemplate template = new RequestTemplate(); |
||||
new JacksonJrEncoder().encode(map, byte[].class, template); |
||||
|
||||
assertThat(template).hasBody("{\"foo\":1}"); |
||||
} |
||||
|
||||
@Test |
||||
public void encodesFormParams() { |
||||
Map<String, Object> form = new LinkedHashMap<String, Object>(); |
||||
form.put("foo", 1); |
||||
form.put("bar", Arrays.asList(2, 3)); |
||||
|
||||
RequestTemplate template = new RequestTemplate(); |
||||
new JacksonJrEncoder().encode(form, new TypeReference<Map<String, ?>>() {}.getType(), template); |
||||
|
||||
assertThat(template).hasBody("{\"foo\":1,\"bar\":[2,3]}"); |
||||
} |
||||
|
||||
@Test |
||||
public void decodes() throws Exception { |
||||
List<Zone> zones = new LinkedList<>(); |
||||
zones.add(new Zone("denominator.io.")); |
||||
zones.add(new Zone("denominator.io.", "ABCD")); |
||||
|
||||
String zonesJson = |
||||
"[{\"name\":\"denominator.io.\"},{\"name\":\"denominator.io.\",\"id\":\"ABCD\"}]"; |
||||
|
||||
Response response = Response.builder() |
||||
.status(200) |
||||
.reason("OK") |
||||
.request(Request.create(HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8)) |
||||
.headers(Collections.emptyMap()) |
||||
.body(zonesJson, UTF_8) |
||||
.build(); |
||||
assertEquals(zones, |
||||
new JacksonJrDecoder().decode(response, new TypeReference<List<Zone>>() {}.getType())); |
||||
} |
||||
|
||||
@Test |
||||
public void nullBodyDecodesToNull() throws Exception { |
||||
Response response = Response.builder() |
||||
.status(204) |
||||
.reason("OK") |
||||
.request(Request.create(HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8)) |
||||
.headers(Collections.emptyMap()) |
||||
.build(); |
||||
assertNull(new JacksonJrDecoder().decode(response, String.class)); |
||||
} |
||||
|
||||
@Test |
||||
public void emptyBodyDecodesToNull() throws Exception { |
||||
Response response = Response.builder() |
||||
.status(204) |
||||
.reason("OK") |
||||
.request(Request.create(HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8)) |
||||
.headers(Collections.emptyMap()) |
||||
.body(new byte[0]) |
||||
.build(); |
||||
assertNull(new JacksonJrDecoder().decode(response, String.class)); |
||||
} |
||||
|
||||
@Test |
||||
public void customDecoder() throws Exception { |
||||
JacksonJrDecoder decoder = new JacksonJrDecoder( |
||||
singletonList(new JavaLocalDateExtension())); |
||||
|
||||
List<LocalDate> dates = new LinkedList<>(); |
||||
dates.add(LocalDate.of(2020, 1, 2)); |
||||
dates.add(LocalDate.of(2021, 2, 3)); |
||||
|
||||
Response response = Response.builder() |
||||
.status(200) |
||||
.reason("OK") |
||||
.request(Request.create(HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8)) |
||||
.headers(Collections.emptyMap()) |
||||
.body(DATES_JSON, UTF_8) |
||||
.build(); |
||||
assertEquals(dates, |
||||
decoder.decode(response, new TypeReference<List<LocalDate>>() {}.getType())); |
||||
} |
||||
|
||||
@Test |
||||
public void customDecoderExpressedAsMapper() throws Exception { |
||||
JSON mapper = JSON.builder() |
||||
.register(new JavaLocalDateExtension()) |
||||
.build(); |
||||
JacksonJrDecoder decoder = new JacksonJrDecoder(mapper); |
||||
|
||||
List<LocalDate> dates = new LinkedList<>(); |
||||
dates.add(LocalDate.of(2020, 1, 2)); |
||||
dates.add(LocalDate.of(2021, 2, 3)); |
||||
|
||||
Response response = Response.builder() |
||||
.status(200) |
||||
.reason("OK") |
||||
.request(Request.create(HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8)) |
||||
.headers(Collections.emptyMap()) |
||||
.body(DATES_JSON, UTF_8) |
||||
.build(); |
||||
assertEquals(dates, |
||||
decoder.decode(response, new TypeReference<List<LocalDate>>() {}.getType())); |
||||
} |
||||
|
||||
@Test |
||||
public void customEncoder() { |
||||
JacksonJrEncoder encoder = new JacksonJrEncoder( |
||||
singletonList(new JavaLocalDateExtension())); |
||||
|
||||
List<LocalDate> dates = new LinkedList<>(); |
||||
dates.add(LocalDate.of(2020, 1, 2)); |
||||
dates.add(LocalDate.of(2021, 2, 3)); |
||||
|
||||
RequestTemplate template = new RequestTemplate(); |
||||
encoder.encode(dates, new TypeReference<List<LocalDate>>() {}.getType(), template); |
||||
|
||||
assertThat(template).hasBody(DATES_JSON); |
||||
} |
||||
|
||||
@Test |
||||
public void decoderCharset() throws IOException { |
||||
Zone zone = new Zone("denominator.io.", "ÁÉÍÓÚÀÈÌÒÙÄËÏÖÜÑ"); |
||||
|
||||
Map<String, Collection<String>> headers = new HashMap<String, Collection<String>>(); |
||||
headers.put("Content-Type", Arrays.asList("application/json;charset=ISO-8859-1")); |
||||
|
||||
Response response = Response.builder() |
||||
.status(200) |
||||
.reason("OK") |
||||
.request(Request.create(HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8)) |
||||
.headers(headers) |
||||
.body(new String("" //
|
||||
+ "{" + System.lineSeparator() |
||||
+ " \"name\" : \"DENOMINATOR.IO.\"," + System.lineSeparator() |
||||
+ " \"id\" : \"ÁÉÍÓÚÀÈÌÒÙÄËÏÖÜÑ\"" + System.lineSeparator() |
||||
+ "}").getBytes(StandardCharsets.ISO_8859_1)) |
||||
.build(); |
||||
assertEquals(zone.getId(), |
||||
((Zone) new JacksonJrDecoder().decode(response, Zone.class)) |
||||
.getId()); |
||||
} |
||||
|
||||
@Test |
||||
public void decodesToMap() throws Exception { |
||||
String json = "{\"name\":\"jim\",\"id\":12}"; |
||||
|
||||
Response response = Response.builder() |
||||
.status(200) |
||||
.reason("OK") |
||||
.request(Request.create(HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8)) |
||||
.headers(Collections.emptyMap()) |
||||
.body(json, UTF_8) |
||||
.build(); |
||||
|
||||
Map<String, Object> map = (Map<String, Object>) new JacksonJrDecoder() |
||||
.decode(response, new TypeReference<Map<String, Object>>() {}.getType()); |
||||
|
||||
assertEquals(12, map.get("id")); |
||||
assertEquals("jim", map.get("name")); |
||||
} |
||||
|
||||
public static class Zone { |
||||
|
||||
private String name; |
||||
private String id; |
||||
|
||||
public Zone() { |
||||
// for reflective instantiation.
|
||||
} |
||||
|
||||
public Zone(String name) { |
||||
this.name = name; |
||||
} |
||||
|
||||
public Zone(String name, String id) { |
||||
this.name = name; |
||||
this.id = id; |
||||
} |
||||
|
||||
public String getName() { |
||||
return name; |
||||
} |
||||
|
||||
public void setName(String name) { |
||||
this.name = name; |
||||
} |
||||
|
||||
public String getId() { |
||||
return id; |
||||
} |
||||
|
||||
public void setId(String id) { |
||||
this.id = id; |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(Object o) { |
||||
if (this == o) { |
||||
return true; |
||||
} |
||||
if (o == null || getClass() != o.getClass()) { |
||||
return false; |
||||
} |
||||
Zone zone = (Zone) o; |
||||
return Objects.equals(name, zone.name) && Objects.equals(id, zone.id); |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
return Objects.hash(name, id); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return "Zone{" + |
||||
"name='" + name + '\'' + |
||||
", id='" + id + '\'' + |
||||
'}'; |
||||
} |
||||
} |
||||
|
||||
/** Enabled via {@link feign.Feign.Builder#decode404()} */ |
||||
@Test |
||||
public void notFoundDecodesToNull() throws Exception { |
||||
Response response = Response.builder() |
||||
.status(404) |
||||
.reason("NOT FOUND") |
||||
.request(Request.create(HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8)) |
||||
.headers(Collections.emptyMap()) |
||||
.build(); |
||||
assertThat((byte[]) new JacksonJrDecoder().decode(response, byte[].class)).isNull(); |
||||
} |
||||
} |
@ -0,0 +1,74 @@
@@ -0,0 +1,74 @@
|
||||
/** |
||||
* Copyright 2012-2021 The Feign Authors |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except |
||||
* in compliance with the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License |
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
||||
* or implied. See the License for the specific language governing permissions and limitations under |
||||
* the License. |
||||
*/ |
||||
package feign.jackson.jr; |
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator; |
||||
import com.fasterxml.jackson.core.JsonParser; |
||||
import com.fasterxml.jackson.jr.ob.JacksonJrExtension; |
||||
import com.fasterxml.jackson.jr.ob.api.ExtensionContext; |
||||
import com.fasterxml.jackson.jr.ob.api.ReaderWriterProvider; |
||||
import com.fasterxml.jackson.jr.ob.api.ValueReader; |
||||
import com.fasterxml.jackson.jr.ob.api.ValueWriter; |
||||
import com.fasterxml.jackson.jr.ob.impl.JSONReader; |
||||
import com.fasterxml.jackson.jr.ob.impl.JSONWriter; |
||||
import java.io.IOException; |
||||
import java.time.LocalDate; |
||||
import java.time.format.DateTimeFormatter; |
||||
|
||||
/** |
||||
* Adapted from https://www.andersaaberg.dk/2020/enable-support-for-java-time-with-jackson-jr/ This
|
||||
* adds Java Time support to Jackson JR |
||||
*/ |
||||
public class JavaLocalDateExtension extends JacksonJrExtension { |
||||
private static class LocalDateReaderWriterProvider extends ReaderWriterProvider { |
||||
@Override |
||||
public ValueReader findValueReader(JSONReader readContext, Class<?> type) { |
||||
return type == LocalDate.class ? new LocalDateValueReader() : null; |
||||
} |
||||
|
||||
@Override |
||||
public ValueWriter findValueWriter(JSONWriter writeContext, Class<?> type) { |
||||
return type == LocalDate.class ? new LocalDateValueWriter() : null; |
||||
} |
||||
} |
||||
|
||||
private static class LocalDateValueReader extends ValueReader { |
||||
protected LocalDateValueReader() { |
||||
super(LocalDate.class); |
||||
} |
||||
|
||||
@Override |
||||
public Object read(JSONReader reader, JsonParser p) throws IOException { |
||||
return LocalDate.parse(p.getText(), DateTimeFormatter.ISO_LOCAL_DATE); |
||||
} |
||||
} |
||||
|
||||
private static class LocalDateValueWriter implements ValueWriter { |
||||
@Override |
||||
public void writeValue(JSONWriter context, JsonGenerator g, Object value) throws IOException { |
||||
context.writeValue(((LocalDate) value).format(DateTimeFormatter.ISO_LOCAL_DATE)); |
||||
} |
||||
|
||||
@Override |
||||
public Class<?> valueType() { |
||||
return LocalDate.class; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
protected void register(ExtensionContext ctxt) { |
||||
ctxt.insertProvider(new LocalDateReaderWriterProvider()); |
||||
} |
||||
} |
||||
|
@ -0,0 +1,58 @@
@@ -0,0 +1,58 @@
|
||||
/** |
||||
* Copyright 2012-2021 The Feign Authors |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except |
||||
* in compliance with the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License |
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
||||
* or implied. See the License for the specific language governing permissions and limitations under |
||||
* the License. |
||||
*/ |
||||
package feign.jackson.jr.examples; |
||||
|
||||
import java.util.List; |
||||
import feign.Feign; |
||||
import feign.Param; |
||||
import feign.RequestLine; |
||||
import feign.jackson.jr.JacksonJrDecoder; |
||||
|
||||
/** |
||||
* adapted from {@code com.example.retrofit.GitHubClient} |
||||
*/ |
||||
public class GitHubExample { |
||||
|
||||
public static void main(String... args) { |
||||
GitHub github = Feign.builder() |
||||
.decoder(new JacksonJrDecoder()) |
||||
.target(GitHub.class, "https://api.github.com"); |
||||
|
||||
System.out.println("Let's fetch and print a list of the contributors to this library."); |
||||
List<Contributor> contributors = github.contributors("netflix", "feign"); |
||||
for (Contributor contributor : contributors) { |
||||
System.out.println(contributor.login + " (" + contributor.contributions + ")"); |
||||
} |
||||
} |
||||
|
||||
interface GitHub { |
||||
|
||||
@RequestLine("GET /repos/{owner}/{repo}/contributors") |
||||
List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo); |
||||
} |
||||
|
||||
static class Contributor { |
||||
|
||||
private String login; |
||||
private int contributions; |
||||
|
||||
void setLogin(String login) { |
||||
this.login = login; |
||||
} |
||||
|
||||
void setContributions(int contributions) { |
||||
this.contributions = contributions; |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue