Compare commits
34 Commits
Author | SHA1 | Date |
---|---|---|
Bob T Builder | 52792566a3 | 11 years ago |
Bob T Builder | a445e7906a | 11 years ago |
Adrian Cole | 7b40f422ff | 11 years ago |
Allen Wang | 2ccae7f1be | 11 years ago |
Bob T Builder | 4b012249a7 | 11 years ago |
Bob T Builder | 3d7a666cb1 | 11 years ago |
Adrian Cole | a616c9f899 | 11 years ago |
Matt Hurne | 2e296883c9 | 11 years ago |
David M. Carr | 42a74cfaed | 11 years ago |
Bob T Builder | ec93c04e3e | 11 years ago |
Bob T Builder | a6e4b1d91f | 11 years ago |
adriancole | f501ec7ccc | 11 years ago |
adriancole | 28ff01c66e | 11 years ago |
David M. Carr | 7f562f6a6a | 11 years ago |
David M. Carr | 206b6e8fe9 | 11 years ago |
Bob T Builder | 32ec00e01e | 11 years ago |
Bob T Builder | f3bc153e72 | 11 years ago |
adriancole | 821c71fe5b | 11 years ago |
adriancole | 44787e9f95 | 11 years ago |
adriancole | 54f1ef3c6a | 11 years ago |
Bob T Builder | c8f6bf5f77 | 11 years ago |
Bob T Builder | 77835d54f1 | 11 years ago |
adriancole | 68b54dcf65 | 11 years ago |
adriancole | eab2c3194e | 11 years ago |
adriancole | 72ebe88c0a | 11 years ago |
adriancole | b4302301a5 | 11 years ago |
Bob T Builder | 99388088b0 | 11 years ago |
Bob T Builder | 1f8bcc9bf3 | 11 years ago |
adriancole | d764e3433a | 11 years ago |
adriancole | 2bb796897b | 11 years ago |
adriancole | d105286981 | 11 years ago |
Bob T Builder | 5c3fd28f43 | 11 years ago |
Bob T Builder | dbb8d35fad | 11 years ago |
adriancole | 99e6747274 | 11 years ago |
50 changed files with 1034 additions and 350 deletions
@ -0,0 +1,69 @@
@@ -0,0 +1,69 @@
|
||||
/* |
||||
* Copyright 2013 Netflix, Inc. |
||||
* |
||||
* 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.auth; |
||||
|
||||
import feign.RequestInterceptor; |
||||
import feign.RequestTemplate; |
||||
import sun.misc.BASE64Encoder; |
||||
|
||||
import java.nio.charset.Charset; |
||||
|
||||
import static feign.Util.checkNotNull; |
||||
import static feign.Util.ISO_8859_1; |
||||
|
||||
/** |
||||
* An interceptor that adds the request header needed to use HTTP basic authentication. |
||||
*/ |
||||
public class BasicAuthRequestInterceptor implements RequestInterceptor { |
||||
private final String headerValue; |
||||
|
||||
/** |
||||
* Creates an interceptor that authenticates all requests with the specified username and password encoded using |
||||
* ISO-8859-1. |
||||
* |
||||
* @param username the username to use for authentication |
||||
* @param password the password to use for authentication |
||||
*/ |
||||
public BasicAuthRequestInterceptor(String username, String password) { |
||||
this(username, password, ISO_8859_1); |
||||
} |
||||
|
||||
/** |
||||
* Creates an interceptor that authenticates all requests with the specified username and password encoded using |
||||
* the specified charset. |
||||
* |
||||
* @param username the username to use for authentication |
||||
* @param password the password to use for authentication |
||||
* @param charset the charset to use when encoding the credentials |
||||
*/ |
||||
public BasicAuthRequestInterceptor(String username, String password, Charset charset) { |
||||
checkNotNull(username, "username"); |
||||
checkNotNull(password, "password"); |
||||
this.headerValue = "Basic " + base64Encode((username + ":" + password).getBytes(charset)); |
||||
} |
||||
|
||||
@Override public void apply(RequestTemplate template) { |
||||
template.header("Authorization", headerValue); |
||||
} |
||||
|
||||
/* |
||||
* This uses a Sun internal method; if we ever encounter a case where this method is not available, the appropriate |
||||
* response would be to pull the necessary portions of Guava's BaseEncoding class into Util. |
||||
*/ |
||||
private static String base64Encode(byte[] bytes) { |
||||
return new BASE64Encoder().encode(bytes); |
||||
} |
||||
} |
@ -0,0 +1,41 @@
@@ -0,0 +1,41 @@
|
||||
/* |
||||
* Copyright 2013 Netflix, Inc. |
||||
* |
||||
* 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.auth; |
||||
|
||||
import feign.RequestTemplate; |
||||
import org.testng.annotations.Test; |
||||
|
||||
import java.util.Collection; |
||||
import java.util.Collections; |
||||
|
||||
import static org.testng.Assert.assertEquals; |
||||
|
||||
/** |
||||
* Tests for {@link BasicAuthRequestInterceptor}. |
||||
*/ |
||||
public class BasicAuthRequestInterceptorTest { |
||||
/** |
||||
* Tests that request headers are added as expected. |
||||
*/ |
||||
@Test public void testAuthentication() { |
||||
RequestTemplate template = new RequestTemplate(); |
||||
BasicAuthRequestInterceptor interceptor = new BasicAuthRequestInterceptor("Aladdin", "open sesame"); |
||||
interceptor.apply(template); |
||||
Collection<String> actualValue = template.headers().get("Authorization"); |
||||
Collection<String> expectedValue = Collections.singletonList("Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="); |
||||
assertEquals(actualValue, expectedValue); |
||||
} |
||||
} |
@ -1 +1 @@
@@ -1 +1 @@
|
||||
version=5.0.0-SNAPSHOT |
||||
version=5.4.2-SNAPSHOT |
||||
|
@ -0,0 +1,54 @@
@@ -0,0 +1,54 @@
|
||||
/* |
||||
* Copyright 2013 Netflix, Inc. |
||||
* |
||||
* 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.gson; |
||||
|
||||
import com.google.gson.Gson; |
||||
import com.google.gson.InstanceCreator; |
||||
import com.google.gson.TypeAdapter; |
||||
import com.google.gson.internal.ConstructorConstructor; |
||||
import com.google.gson.internal.bind.MapTypeAdapterFactory; |
||||
import com.google.gson.reflect.TypeToken; |
||||
import com.google.gson.stream.JsonReader; |
||||
import com.google.gson.stream.JsonWriter; |
||||
|
||||
import java.io.IOException; |
||||
import java.lang.reflect.Type; |
||||
import java.util.Collections; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* Deals with scenario where Gson Object type treats all numbers as doubles. |
||||
*/ |
||||
public class DoubleToIntMapTypeAdapter extends TypeAdapter<Map<String, Object>> { |
||||
final static TypeToken<Map<String, Object>> token = new TypeToken<Map<String, Object>>() {}; |
||||
|
||||
private final TypeAdapter<Map<String, Object>> delegate = new MapTypeAdapterFactory(new ConstructorConstructor( |
||||
Collections.<Type, InstanceCreator<?>>emptyMap()), false).create(new Gson(), token); |
||||
|
||||
@Override public void write(JsonWriter out, Map<String, Object> value) throws IOException { |
||||
delegate.write(out, value); |
||||
} |
||||
|
||||
@Override public Map<String, Object> read(JsonReader in) throws IOException { |
||||
Map<String, Object> map = delegate.read(in); |
||||
for (Map.Entry<String, Object> entry : map.entrySet()) { |
||||
if (entry.getValue() instanceof Double) { |
||||
entry.setValue(Double.class.cast(entry.getValue()).intValue()); |
||||
} |
||||
} |
||||
return map; |
||||
} |
||||
} |
@ -0,0 +1,37 @@
@@ -0,0 +1,37 @@
|
||||
package feign.gson; |
||||
|
||||
import com.google.gson.Gson; |
||||
import feign.RequestTemplate; |
||||
import feign.Response; |
||||
import feign.codec.Decoder; |
||||
import feign.codec.Encoder; |
||||
|
||||
import javax.inject.Inject; |
||||
import java.io.IOException; |
||||
import java.lang.reflect.Type; |
||||
|
||||
/** |
||||
* @deprecated use {@link GsonEncoder} and {@link GsonDecoder} instead |
||||
*/ |
||||
@Deprecated |
||||
public class GsonCodec implements Encoder, Decoder { |
||||
private final GsonEncoder encoder; |
||||
private final GsonDecoder decoder; |
||||
|
||||
public GsonCodec() { |
||||
this(new Gson()); |
||||
} |
||||
|
||||
@Inject public GsonCodec(Gson gson) { |
||||
this.encoder = new GsonEncoder(gson); |
||||
this.decoder = new GsonDecoder(gson); |
||||
} |
||||
|
||||
@Override public void encode(Object object, RequestTemplate template) { |
||||
encoder.encode(object, template); |
||||
} |
||||
|
||||
@Override public Object decode(Response response, Type type) throws IOException { |
||||
return decoder.decode(response, type); |
||||
} |
||||
} |
@ -0,0 +1,56 @@
@@ -0,0 +1,56 @@
|
||||
/* |
||||
* Copyright 2013 Netflix, Inc. |
||||
* |
||||
* 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.gson; |
||||
|
||||
import com.google.gson.Gson; |
||||
import com.google.gson.JsonIOException; |
||||
import feign.Response; |
||||
import feign.codec.Decoder; |
||||
|
||||
import java.io.IOException; |
||||
import java.io.Reader; |
||||
import java.lang.reflect.Type; |
||||
|
||||
import static feign.Util.ensureClosed; |
||||
|
||||
public class GsonDecoder implements Decoder { |
||||
private final Gson gson; |
||||
|
||||
public GsonDecoder() { |
||||
this(new Gson()); |
||||
} |
||||
|
||||
public GsonDecoder(Gson gson) { |
||||
this.gson = gson; |
||||
} |
||||
|
||||
@Override public Object decode(Response response, Type type) throws IOException { |
||||
if (response.body() == null) { |
||||
return null; |
||||
} |
||||
Reader reader = response.body().asReader(); |
||||
try { |
||||
return gson.fromJson(reader, type); |
||||
} catch (JsonIOException e) { |
||||
if (e.getCause() != null && e.getCause() instanceof IOException) { |
||||
throw IOException.class.cast(e.getCause()); |
||||
} |
||||
throw e; |
||||
} finally { |
||||
ensureClosed(reader); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,36 @@
@@ -0,0 +1,36 @@
|
||||
/* |
||||
* Copyright 2013 Netflix, Inc. |
||||
* |
||||
* 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.gson; |
||||
|
||||
import com.google.gson.Gson; |
||||
import feign.RequestTemplate; |
||||
import feign.codec.Encoder; |
||||
|
||||
public class GsonEncoder implements Encoder { |
||||
private final Gson gson; |
||||
|
||||
public GsonEncoder() { |
||||
this(new Gson()); |
||||
} |
||||
|
||||
public GsonEncoder(Gson gson) { |
||||
this.gson = gson; |
||||
} |
||||
|
||||
@Override public void encode(Object object, RequestTemplate template) { |
||||
template.body(gson.toJson(object)); |
||||
} |
||||
} |
@ -0,0 +1,33 @@
@@ -0,0 +1,33 @@
|
||||
Jackson Codec |
||||
=================== |
||||
|
||||
This module adds support for encoding and decoding JSON via Jackson. |
||||
|
||||
Add `JacksonEncoder` and/or `JacksonDecoder` to your `Feign.Builder` like so: |
||||
|
||||
```java |
||||
GitHub github = Feign.builder() |
||||
.encoder(new JacksonEncoder()) |
||||
.decoder(new JacksonDecoder()) |
||||
.target(GitHub.class, "https://api.github.com"); |
||||
``` |
||||
|
||||
If you want to customize the `ObjectMapper` that is used, provide it to the `JacksonEncoder` and `JacksonDecoder`: |
||||
|
||||
```java |
||||
ObjectMapper mapper = new ObjectMapper() |
||||
.setSerializationInclusion(JsonInclude.Include.NON_NULL) |
||||
.configure(SerializationFeature.INDENT_OUTPUT, true) |
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); |
||||
|
||||
GitHub github = Feign.builder() |
||||
.encoder(new JacksonEncoder(mapper)) |
||||
.decoder(new JacksonDecoder(mapper)) |
||||
.target(GitHub.class, "https://api.github.com"); |
||||
``` |
||||
|
||||
Alternatively, you can add the encoder and decoder to your Dagger object graph using the provided `JacksonModule` like so: |
||||
|
||||
```java |
||||
GitHub github = Feign.create(GitHub.class, "https://api.github.com", new JacksonModule()); |
||||
``` |
@ -0,0 +1,53 @@
@@ -0,0 +1,53 @@
|
||||
/* |
||||
* Copyright 2013 Netflix, Inc. |
||||
* |
||||
* 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; |
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature; |
||||
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
import com.fasterxml.jackson.databind.RuntimeJsonMappingException; |
||||
import feign.Response; |
||||
import feign.codec.Decoder; |
||||
|
||||
import java.io.IOException; |
||||
import java.io.Reader; |
||||
import java.lang.reflect.Type; |
||||
|
||||
public class JacksonDecoder implements Decoder { |
||||
private final ObjectMapper mapper; |
||||
|
||||
public JacksonDecoder() { |
||||
this(new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)); |
||||
} |
||||
|
||||
public JacksonDecoder(ObjectMapper mapper) { |
||||
this.mapper = mapper; |
||||
} |
||||
|
||||
@Override public Object decode(Response response, Type type) throws IOException { |
||||
if (response.body() == null) { |
||||
return null; |
||||
} |
||||
Reader reader = response.body().asReader(); |
||||
try { |
||||
return mapper.readValue(reader, mapper.constructType(type)); |
||||
} catch (RuntimeJsonMappingException e) { |
||||
if (e.getCause() != null && e.getCause() instanceof IOException) { |
||||
throw IOException.class.cast(e.getCause()); |
||||
} |
||||
throw e; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,46 @@
@@ -0,0 +1,46 @@
|
||||
/* |
||||
* Copyright 2013 Netflix, Inc. |
||||
* |
||||
* 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; |
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude; |
||||
import com.fasterxml.jackson.core.JsonProcessingException; |
||||
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
import com.fasterxml.jackson.databind.SerializationFeature; |
||||
import feign.RequestTemplate; |
||||
import feign.codec.EncodeException; |
||||
import feign.codec.Encoder; |
||||
|
||||
public class JacksonEncoder implements Encoder { |
||||
private final ObjectMapper mapper; |
||||
|
||||
public JacksonEncoder() { |
||||
this(new ObjectMapper() |
||||
.setSerializationInclusion(JsonInclude.Include.NON_NULL) |
||||
.configure(SerializationFeature.INDENT_OUTPUT, true)); |
||||
} |
||||
|
||||
public JacksonEncoder(ObjectMapper mapper) { |
||||
this.mapper = mapper; |
||||
} |
||||
|
||||
@Override public void encode(Object object, RequestTemplate template) throws EncodeException { |
||||
try { |
||||
template.body(mapper.writeValueAsString(object)); |
||||
} catch (JsonProcessingException e) { |
||||
throw new EncodeException(e.getMessage(), e); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,103 @@
@@ -0,0 +1,103 @@
|
||||
/* |
||||
* Copyright 2013 Netflix, Inc. |
||||
* |
||||
* 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; |
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude; |
||||
import com.fasterxml.jackson.databind.DeserializationFeature; |
||||
import com.fasterxml.jackson.databind.JsonDeserializer; |
||||
import com.fasterxml.jackson.databind.JsonSerializer; |
||||
import com.fasterxml.jackson.databind.Module; |
||||
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
import com.fasterxml.jackson.databind.SerializationFeature; |
||||
import dagger.Provides; |
||||
import feign.Feign; |
||||
import feign.codec.Decoder; |
||||
import feign.codec.Encoder; |
||||
|
||||
import javax.inject.Singleton; |
||||
import java.util.Collections; |
||||
import java.util.Set; |
||||
|
||||
/** |
||||
* <h3>Custom serializers/deserializers</h3> |
||||
* <br> |
||||
* In order to specify custom json parsing, Jackson's {@code ObjectMapper} supports {@link JsonSerializer serializers} |
||||
* and {@link JsonDeserializer deserializers}, which can be bundled together in {@link Module modules}. |
||||
* <p/> |
||||
* <br> |
||||
* Here's an example of adding a custom module. |
||||
* <p/> |
||||
* <pre> |
||||
* public class ObjectIdSerializer extends StdSerializer<ObjectId> { |
||||
* public ObjectIdSerializer() { |
||||
* super(ObjectId.class); |
||||
* } |
||||
* |
||||
* @Override |
||||
* public void serialize(ObjectId value, JsonGenerator jsonGenerator, SerializerProvider provider) throws IOException { |
||||
* jsonGenerator.writeString(value.toString()); |
||||
* } |
||||
* } |
||||
* |
||||
* public class ObjectIdDeserializer extends StdDeserializer<ObjectId> { |
||||
* public ObjectIdDeserializer() { |
||||
* super(ObjectId.class); |
||||
* } |
||||
* |
||||
* @Override |
||||
* public ObjectId deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException { |
||||
* return ObjectId.massageToObjectId(jsonParser.getValueAsString()); |
||||
* } |
||||
* } |
||||
* |
||||
* public class ObjectIdModule extends SimpleModule { |
||||
* public ObjectIdModule() { |
||||
* // first deserializers
|
||||
* addDeserializer(ObjectId.class, new ObjectIdDeserializer()); |
||||
* |
||||
* // then serializers:
|
||||
* addSerializer(ObjectId.class, new ObjectIdSerializer()); |
||||
* } |
||||
* } |
||||
* |
||||
* @Provides(type = Provides.Type.SET) |
||||
* Module objectIdModule() { |
||||
* return new ObjectIdModule(); |
||||
* } |
||||
* </pre> |
||||
*/ |
||||
@dagger.Module(injects = Feign.class, addsTo = Feign.Defaults.class) |
||||
public final class JacksonModule { |
||||
@Provides Encoder encoder(ObjectMapper mapper) { |
||||
return new JacksonEncoder(mapper); |
||||
} |
||||
|
||||
@Provides Decoder decoder(ObjectMapper mapper) { |
||||
return new JacksonDecoder(mapper); |
||||
} |
||||
|
||||
@Provides @Singleton ObjectMapper mapper(Set<Module> modules) { |
||||
return new ObjectMapper() |
||||
.setSerializationInclusion(JsonInclude.Include.NON_NULL) |
||||
.configure(SerializationFeature.INDENT_OUTPUT, true) |
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) |
||||
.registerModules(modules); |
||||
} |
||||
|
||||
@Provides(type = Provides.Type.SET_VALUES) Set<Module> noDefaultModules() { |
||||
return Collections.emptySet(); |
||||
} |
||||
} |
@ -0,0 +1,184 @@
@@ -0,0 +1,184 @@
|
||||
package feign.jackson; |
||||
|
||||
import com.fasterxml.jackson.core.JsonParser; |
||||
import com.fasterxml.jackson.core.JsonToken; |
||||
import com.fasterxml.jackson.databind.DeserializationContext; |
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer; |
||||
import com.fasterxml.jackson.databind.module.SimpleModule; |
||||
import com.google.common.reflect.TypeToken; |
||||
import dagger.Module; |
||||
import dagger.ObjectGraph; |
||||
import dagger.Provides; |
||||
import feign.RequestTemplate; |
||||
import feign.Response; |
||||
import feign.codec.Decoder; |
||||
import feign.codec.Encoder; |
||||
import org.testng.annotations.Test; |
||||
|
||||
import javax.inject.Inject; |
||||
import java.io.IOException; |
||||
import java.util.*; |
||||
|
||||
import static org.testng.Assert.assertEquals; |
||||
|
||||
@Test |
||||
public class JacksonModuleTest { |
||||
@Module(includes = JacksonModule.class, injects = EncoderAndDecoderBindings.class) |
||||
static class EncoderAndDecoderBindings { |
||||
@Inject |
||||
Encoder encoder; |
||||
@Inject |
||||
Decoder decoder; |
||||
} |
||||
|
||||
@Test |
||||
public void providesEncoderDecoder() throws Exception { |
||||
EncoderAndDecoderBindings bindings = new EncoderAndDecoderBindings(); |
||||
ObjectGraph.create(bindings).inject(bindings); |
||||
|
||||
assertEquals(bindings.encoder.getClass(), JacksonEncoder.class); |
||||
assertEquals(bindings.decoder.getClass(), JacksonDecoder.class); |
||||
} |
||||
|
||||
@Module(includes = JacksonModule.class, injects = EncoderBindings.class) |
||||
static class EncoderBindings { |
||||
@Inject Encoder encoder; |
||||
} |
||||
|
||||
@Test public void encodesMapObjectNumericalValuesAsInteger() throws Exception { |
||||
EncoderBindings bindings = new EncoderBindings(); |
||||
ObjectGraph.create(bindings).inject(bindings); |
||||
|
||||
Map<String, Object> map = new LinkedHashMap<String, Object>(); |
||||
map.put("foo", 1); |
||||
|
||||
RequestTemplate template = new RequestTemplate(); |
||||
bindings.encoder.encode(map, template); |
||||
assertEquals(template.body(), ""//
|
||||
+ "{\n" //
|
||||
+ " \"foo\" : 1\n" //
|
||||
+ "}"); |
||||
} |
||||
|
||||
@Test public void encodesFormParams() throws Exception { |
||||
EncoderBindings bindings = new EncoderBindings(); |
||||
ObjectGraph.create(bindings).inject(bindings); |
||||
|
||||
Map<String, Object> form = new LinkedHashMap<String, Object>(); |
||||
form.put("foo", 1); |
||||
form.put("bar", Arrays.asList(2, 3)); |
||||
|
||||
RequestTemplate template = new RequestTemplate(); |
||||
bindings.encoder.encode(form, template); |
||||
assertEquals(template.body(), ""//
|
||||
+ "{\n" //
|
||||
+ " \"foo\" : 1,\n" //
|
||||
+ " \"bar\" : [ 2, 3 ]\n" //
|
||||
+ "}"); |
||||
} |
||||
|
||||
static class Zone extends LinkedHashMap<String, Object> { |
||||
Zone() { |
||||
// for reflective instantiation.
|
||||
} |
||||
|
||||
Zone(String name) { |
||||
this(name, null); |
||||
} |
||||
|
||||
Zone(String name, String id) { |
||||
put("name", name); |
||||
if (id != null) { |
||||
put("id", id); |
||||
} |
||||
} |
||||
|
||||
private static final long serialVersionUID = 1L; |
||||
} |
||||
|
||||
@Module(includes = JacksonModule.class, injects = DecoderBindings.class) |
||||
static class DecoderBindings { |
||||
@Inject Decoder decoder; |
||||
} |
||||
|
||||
@Test public void decodes() throws Exception { |
||||
DecoderBindings bindings = new DecoderBindings(); |
||||
ObjectGraph.create(bindings).inject(bindings); |
||||
|
||||
List<Zone> zones = new LinkedList<Zone>(); |
||||
zones.add(new Zone("denominator.io.")); |
||||
zones.add(new Zone("denominator.io.", "ABCD")); |
||||
|
||||
Response response = Response.create(200, "OK", Collections.<String, Collection<String>>emptyMap(), zonesJson); |
||||
assertEquals(bindings.decoder.decode(response, new TypeToken<List<Zone>>() { |
||||
}.getType()), zones); |
||||
} |
||||
|
||||
@Test public void nullBodyDecodesToNull() throws Exception { |
||||
DecoderBindings bindings = new DecoderBindings(); |
||||
ObjectGraph.create(bindings).inject(bindings); |
||||
|
||||
Response response = Response.create(204, "OK", Collections.<String, Collection<String>>emptyMap(), null); |
||||
assertEquals(bindings.decoder.decode(response, String.class), null); |
||||
} |
||||
|
||||
private String zonesJson = ""//
|
||||
+ "[\n"//
|
||||
+ " {\n"//
|
||||
+ " \"name\": \"denominator.io.\"\n"//
|
||||
+ " },\n"//
|
||||
+ " {\n"//
|
||||
+ " \"name\": \"denominator.io.\",\n"//
|
||||
+ " \"id\": \"ABCD\"\n"//
|
||||
+ " }\n"//
|
||||
+ "]\n"; |
||||
|
||||
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 ZoneModule extends SimpleModule { |
||||
public ZoneModule() { |
||||
addDeserializer(Zone.class, new ZoneDeserializer()); |
||||
} |
||||
} |
||||
|
||||
@Module(includes = JacksonModule.class, injects = CustomJacksonModule.class) |
||||
static class CustomJacksonModule { |
||||
@Inject Decoder decoder; |
||||
|
||||
@Provides(type = Provides.Type.SET) |
||||
com.fasterxml.jackson.databind.Module upperZone() { |
||||
return new ZoneModule(); |
||||
} |
||||
} |
||||
|
||||
@Test public void customDecoder() throws Exception { |
||||
CustomJacksonModule bindings = new CustomJacksonModule(); |
||||
ObjectGraph.create(bindings).inject(bindings); |
||||
|
||||
List<Zone> zones = new LinkedList<Zone>(); |
||||
zones.add(new Zone("DENOMINATOR.IO.")); |
||||
zones.add(new Zone("DENOMINATOR.IO.", "ABCD")); |
||||
|
||||
Response response = Response.create(200, "OK", Collections.<String, Collection<String>>emptyMap(), zonesJson); |
||||
assertEquals(bindings.decoder.decode(response, new TypeToken<List<Zone>>() { |
||||
}.getType()), zones); |
||||
} |
||||
} |
@ -0,0 +1,40 @@
@@ -0,0 +1,40 @@
|
||||
package feign.jackson.examples; |
||||
|
||||
import feign.Feign; |
||||
import feign.RequestLine; |
||||
import feign.jackson.JacksonDecoder; |
||||
|
||||
import javax.inject.Named; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* adapted from {@code com.example.retrofit.GitHubClient} |
||||
*/ |
||||
public class GitHubExample { |
||||
interface GitHub { |
||||
@RequestLine("GET /repos/{owner}/{repo}/contributors") |
||||
List<Contributor> contributors(@Named("owner") String owner, @Named("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; |
||||
} |
||||
} |
||||
|
||||
public static void main(String... args) throws InterruptedException { |
||||
GitHub github = Feign.builder().decoder(new JacksonDecoder()).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 + ")"); |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue