Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions json/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
JSON-java Codec
===================

This module adds support for encoding and decoding [JSON][] via [JSON-java][].

Add `JsonEncoder` and/or `JsonDecoder` to your `Feign.Builder` like so:

```java
api = Feign.builder()
.decoder(new JsonDecoder())
.encoder(new JsonEncoder())
.target(GitHub.class, "https://api");
```

[JSON]: https://www.json.org/json-en.html
[JSON-java]: https://github.com/stleary/JSON-java
69 changes: 69 additions & 0 deletions json/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?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-json</artifactId>
<name>Feign JSON-java</name>
<description>Feign JSON-java</description>

<properties>
<main.basedir>${project.basedir}/..</main.basedir>
<hamcrest.version>1.3</hamcrest.version>
<mockito.version>1.10.19</mockito.version>
</properties>

<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>feign-core</artifactId>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<version>${hamcrest.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
<version>${hamcrest.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>feign-mock</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
86 changes: 86 additions & 0 deletions json/src/main/java/feign/json/JsonDecoder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* 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.json;

import feign.Response;
import feign.codec.DecodeException;
import feign.codec.Decoder;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.lang.reflect.Type;
import static feign.Util.UTF_8;
import static java.lang.String.format;

/**
* Decodes responses using JSON-java.
* <p>
* Basic example with {@link feign.Feign.Builder}:
*
* <pre>
* interface GitHub {
*
* {@literal @}RequestLine("GET /repos/{owner}/{repo}/contributors")
* JSONArray contributors({@literal @}Param("owner") String owner, {@literal @}Param("repo") String repo);
*
* }
*
* GitHub github = Feign.builder()
* .decoder(new JsonDecoder())
* .target(GitHub.class, "https://api.github.com");
*
* JSONArray contributors = github.contributors("openfeign", "feign");
*
* System.out.println(contributors.getJSONObject(0).getString("login"));
* </pre>
*/
public class JsonDecoder implements Decoder {

@Override
public Object decode(Response response, Type type) throws IOException, DecodeException {
if (response.body() == null)
return null;
try (Reader reader = response.body().asReader(response.charset())) {
Reader bodyReader = (reader.markSupported()) ? reader : new BufferedReader(reader);
bodyReader.mark(1);
if (bodyReader.read() == -1) {
return null; // Empty body
}
bodyReader.reset();
return decodeJSON(response, type, bodyReader);
} catch (JSONException jsonException) {
if (jsonException.getCause() != null && jsonException.getCause() instanceof IOException) {
throw (IOException) jsonException.getCause();
}
throw new DecodeException(response.status(), jsonException.getMessage(), response.request(),
jsonException);
}
}

private Object decodeJSON(Response response, Type type, Reader reader) {
JSONTokener tokenizer = new JSONTokener(reader);
if (JSONObject.class.isAssignableFrom((Class<?>) type))
return new JSONObject(tokenizer);
else if (JSONArray.class.isAssignableFrom((Class<?>) type))
return new JSONArray(tokenizer);
else
throw new DecodeException(response.status(),
format("%s is not a type supported by this decoder.", type), response.request());
}

}
65 changes: 65 additions & 0 deletions json/src/main/java/feign/json/JsonEncoder.java
Original file line number Diff line number Diff line change
@@ -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.json;

import feign.RequestTemplate;
import feign.codec.EncodeException;
import feign.codec.Encoder;
import org.json.JSONArray;
import org.json.JSONObject;
import java.lang.reflect.Type;
import static java.lang.String.format;

/**
* Encodes requests using JSON-java.
* <p>
* Basic example with {@link feign.Feign.Builder}:
*
* <pre>
* interface GitHub {
*
* {@literal @}RequestLine("POST /repos/{owner}/{repo}/contributors")
* JSONObject create({@literal @}Param("owner") String owner,
* {@literal @}@Param("repo") String repo,
* JSONObject contributor);
*
* }
*
* GitHub github = Feign.builder()
* .decoder(new JsonDecoder())
* .encoder(new JsonEncoder())
* .target(GitHub.class, "https://api.github.com");
*
* JSONObject contributor = new JSONObject();
*
* contributor.put("login", "radio-rogal");
* contributor.put("contributions", 0);
* github.create("openfeign", "feign", contributor);
* </pre>
*/
public class JsonEncoder implements Encoder {

@Override
public void encode(Object object, Type bodyType, RequestTemplate template)
throws EncodeException {
if (object == null)
return;
if (object instanceof JSONArray || object instanceof JSONObject) {
template.body(object.toString());
} else {
throw new EncodeException(format("%s is not a type supported by this encoder.", bodyType));
}
}

}
93 changes: 93 additions & 0 deletions json/src/test/java/feign/json/JsonCodecTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/**
* 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.json;

import feign.Feign;
import feign.Param;
import feign.Request;
import feign.RequestLine;
import feign.mock.HttpMethod;
import feign.mock.MockClient;
import feign.mock.MockTarget;
import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import static feign.Util.toByteArray;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;


interface GitHub {

@RequestLine("GET /repos/{owner}/{repo}/contributors")
JSONArray contributors(@Param("owner") String owner, @Param("repo") String repo);

@RequestLine("POST /repos/{owner}/{repo}/contributors")
JSONObject create(@Param("owner") String owner,
@Param("repo") String repo,
JSONObject contributor);

}


public class JsonCodecTest {

private GitHub github;
private MockClient mockClient;

@Before
public void setUp() {
mockClient = new MockClient();
github = Feign.builder()
.decoder(new JsonDecoder())
.encoder(new JsonEncoder())
.client(mockClient)
.target(new MockTarget<>(GitHub.class));
}

@Test
public void decodes() throws IOException {
try (InputStream input = getClass().getResourceAsStream("/fixtures/contributors.json")) {
byte[] response = toByteArray(input);
mockClient.ok(HttpMethod.GET, "/repos/openfeign/feign/contributors", response);
JSONArray contributors = github.contributors("openfeign", "feign");
assertThat(contributors.toList(), hasSize(30));
contributors.forEach(contributor -> ((JSONObject) contributor).getString("login"));
}
}

@Test
public void encodes() {
JSONObject contributor = new JSONObject();
contributor.put("login", "radio-rogal");
contributor.put("contributions", 0);
mockClient.ok(HttpMethod.POST, "/repos/openfeign/feign/contributors",
"{\"login\":\"radio-rogal\",\"contributions\":0}");
JSONObject response = github.create("openfeign", "feign", contributor);
Request request = mockClient.verifyOne(HttpMethod.POST, "/repos/openfeign/feign/contributors");
assertNotNull(request.body());
String json = new String(request.body());
assertThat(json, containsString("\"login\":\"radio-rogal\""));
assertThat(json, containsString("\"contributions\":0"));
assertEquals("radio-rogal", response.getString("login"));
assertEquals(0, response.getInt("contributions"));
}

}
Loading