ashleyfrieze
4 years ago
committed by
GitHub
11 changed files with 756 additions and 0 deletions
@ -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 @@ |
|||||||
|
<?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 @@ |
|||||||
|
/** |
||||||
|
* 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 @@ |
|||||||
|
/** |
||||||
|
* 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 @@ |
|||||||
|
/** |
||||||
|
* 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 @@ |
|||||||
|
/** |
||||||
|
* 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 @@ |
|||||||
|
/** |
||||||
|
* 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 @@ |
|||||||
|
/** |
||||||
|
* 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