Skip to content

Commit

Permalink
[Java][client] Fix feign classcastexception when getting headers (#16745
Browse files Browse the repository at this point in the history
)

* Avoid ClassCastException when getting headers (the header values are Collection<String> and not List<String>)
Delete unused classes

* Remove feign10x

* Add unit test
Refactor to avoid creating the headers map when ApiResponse is not used
  • Loading branch information
robertdanci authored Oct 10, 2023
1 parent 494ee48 commit 87f9d53
Show file tree
Hide file tree
Showing 11 changed files with 89 additions and 107 deletions.
6 changes: 0 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,6 @@ nb-configuration.xml
/target
/generated-files
test-output/
nbactions.xml
test-output/

# website
website/build/
Expand All @@ -73,7 +71,6 @@ samples/client/petstore/build
samples/client/petstore/cpp-qt/PetStore/moc_*
samples/client/petstore/cpp-qt/PetStore/*.o
samples/client/petstore/cpp-qt/build-*
samples/client/petstore/cpp-qt/build-*
samples/client/petstore/cpp-qt/PetStore/PetStore
samples/client/petstore/cpp-qt/PetStore/Makefile
samples/client/petstore/cpp-qt/PetStore/PetStore.pro.user
Expand All @@ -100,17 +97,14 @@ samples/client/petstore/java/jersey2/build/
samples/client/petstore/java/okhttp-gson/.gradle/
samples/client/petstore/java/okhttp-gson/build/
samples/client/petstore/java/feign/build/
samples/client/petstore/java/feign10x/build/
samples/client/petstore/java/feign/project/
samples/client/petstore/java/feign10x/project/
samples/client/petstore/java/retrofit/build/
samples/client/petstore/java/retrofit2/build/
samples/client/petstore/java/retrofit2/hello.txt
samples/client/petstore/java/retrofit2rx/build/
samples/client/petstore/java/default/build/
samples/client/petstore/scala/build/
samples/client/petstore/java/resttemplate/hello.txt
samples/client/petstore/java/retrofit2/hello.txt
samples/client/petstore/java/feign/hello.txt
samples/client/petstore/java/jersey2-java6/project/
samples/client/petstore/java/jersey2-java8/project/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,13 @@ public class ApiResponseDecoder extends JacksonDecoder {

@Override
public Object decode(Response response, Type type) throws IOException {
Map<String, Collection<String>> responseHeaders = Collections.unmodifiableMap(response.headers());
//Detects if the type is an instance of the parameterized class ApiResponse
Type responseBodyType;
if (type instanceof ParameterizedType && Types.getRawType(type).isAssignableFrom(ApiResponse.class)) {
//The ApiResponse class has a single type parameter, the Dto class itself
responseBodyType = ((ParameterizedType) type).getActualTypeArguments()[0];
Type responseBodyType = ((ParameterizedType) type).getActualTypeArguments()[0];
Object body = super.decode(response, responseBodyType);
return new ApiResponse(response.status(), responseHeaders, body);
Map<String, Collection<String>> responseHeaders = Collections.unmodifiableMap(response.headers());
return new ApiResponse<>(response.status(), responseHeaders, body);
} else {
//The response is not encapsulated in the ApiResponse, decode the Dto as normal
return super.decode(response, type);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
package {{modelPackage}};

import java.util.Map;
import java.util.List;
import java.util.Collection;

public class ApiResponse<T>{
final private int statusCode;
final private Map<String, List<String>> headers;
final private Map<String, Collection<String>> headers;
final private T data;
/**
* @param statusCode The status code of HTTP response
* @param headers The headers of HTTP response
*/
public ApiResponse(int statusCode, Map<String, List<String>> headers) {
public ApiResponse(int statusCode, Map<String, Collection<String>> headers) {
this(statusCode, headers, null);
}

Expand All @@ -22,7 +22,7 @@ public class ApiResponse<T>{
* @param headers The headers of HTTP response
* @param data The object deserialized from response bod
*/
public ApiResponse(int statusCode, Map<String, List<String>> headers, T data) {
public ApiResponse(int statusCode, Map<String, Collection<String>> headers, T data) {
this.statusCode = statusCode;
this.headers = headers;
this.data = data;
Expand All @@ -32,7 +32,7 @@ public class ApiResponse<T>{
return statusCode;
}

public Map<String, List<String>> getHeaders() {
public Map<String, Collection<String>> getHeaders() {
return headers;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
package org.openapitools.client.model;

import java.util.Map;
import java.util.List;
import java.util.Collection;

public class ApiResponse<T>{

final private int statusCode;
final private Map<String, List<String>> headers;
final private Map<String, Collection<String>> headers;
final private T data;

/**
* @param statusCode The status code of HTTP response
* @param headers The headers of HTTP response
*/
public ApiResponse(int statusCode, Map<String, List<String>> headers) {
public ApiResponse(int statusCode, Map<String, Collection<String>> headers) {
this(statusCode, headers, null);
}

Expand All @@ -22,7 +22,7 @@ public ApiResponse(int statusCode, Map<String, List<String>> headers) {
* @param headers The headers of HTTP response
* @param data The object deserialized from response bod
*/
public ApiResponse(int statusCode, Map<String, List<String>> headers, T data) {
public ApiResponse(int statusCode, Map<String, Collection<String>> headers, T data) {
this.statusCode = statusCode;
this.headers = headers;
this.data = data;
Expand All @@ -32,7 +32,7 @@ public int getStatusCode() {
return statusCode;
}

public Map<String, List<String>> getHeaders() {
public Map<String, Collection<String>> getHeaders() {
return headers;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,13 @@ public ApiResponseDecoder(ObjectMapper mapper) {

@Override
public Object decode(Response response, Type type) throws IOException {
Map<String, Collection<String>> responseHeaders = Collections.unmodifiableMap(response.headers());
//Detects if the type is an instance of the parameterized class ApiResponse
Type responseBodyType;
if (type instanceof ParameterizedType && Types.getRawType(type).isAssignableFrom(ApiResponse.class)) {
//The ApiResponse class has a single type parameter, the Dto class itself
responseBodyType = ((ParameterizedType) type).getActualTypeArguments()[0];
Type responseBodyType = ((ParameterizedType) type).getActualTypeArguments()[0];
Object body = super.decode(response, responseBodyType);
return new ApiResponse(response.status(), responseHeaders, body);
Map<String, Collection<String>> responseHeaders = Collections.unmodifiableMap(response.headers());
return new ApiResponse<>(response.status(), responseHeaders, body);
} else {
//The response is not encapsulated in the ApiResponse, decode the Dto as normal
return super.decode(response, type);
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
package org.openapitools.client.model;

import java.util.Map;
import java.util.List;
import java.util.Collection;

public class ApiResponse<T>{

final private int statusCode;
final private Map<String, List<String>> headers;
final private Map<String, Collection<String>> headers;
final private T data;

/**
* @param statusCode The status code of HTTP response
* @param headers The headers of HTTP response
*/
public ApiResponse(int statusCode, Map<String, List<String>> headers) {
public ApiResponse(int statusCode, Map<String, Collection<String>> headers) {
this(statusCode, headers, null);
}

Expand All @@ -22,7 +22,7 @@ public ApiResponse(int statusCode, Map<String, List<String>> headers) {
* @param headers The headers of HTTP response
* @param data The object deserialized from response bod
*/
public ApiResponse(int statusCode, Map<String, List<String>> headers, T data) {
public ApiResponse(int statusCode, Map<String, Collection<String>> headers, T data) {
this.statusCode = statusCode;
this.headers = headers;
this.data = data;
Expand All @@ -32,7 +32,7 @@ public int getStatusCode() {
return statusCode;
}

public Map<String, List<String>> getHeaders() {
public Map<String, Collection<String>> getHeaders() {
return headers;
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,13 @@ public ApiResponseDecoder(ObjectMapper mapper) {

@Override
public Object decode(Response response, Type type) throws IOException {
Map<String, Collection<String>> responseHeaders = Collections.unmodifiableMap(response.headers());
//Detects if the type is an instance of the parameterized class ApiResponse
Type responseBodyType;
if (type instanceof ParameterizedType && Types.getRawType(type).isAssignableFrom(ApiResponse.class)) {
//The ApiResponse class has a single type parameter, the Dto class itself
responseBodyType = ((ParameterizedType) type).getActualTypeArguments()[0];
Type responseBodyType = ((ParameterizedType) type).getActualTypeArguments()[0];
Object body = super.decode(response, responseBodyType);
return new ApiResponse(response.status(), responseHeaders, body);
Map<String, Collection<String>> responseHeaders = Collections.unmodifiableMap(response.headers());
return new ApiResponse<>(response.status(), responseHeaders, body);
} else {
//The response is not encapsulated in the ApiResponse, decode the Dto as normal
return super.decode(response, type);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
package org.openapitools.client.model;

import java.util.Map;
import java.util.List;
import java.util.Collection;

public class ApiResponse<T>{

final private int statusCode;
final private Map<String, List<String>> headers;
final private Map<String, Collection<String>> headers;
final private T data;

/**
* @param statusCode The status code of HTTP response
* @param headers The headers of HTTP response
*/
public ApiResponse(int statusCode, Map<String, List<String>> headers) {
public ApiResponse(int statusCode, Map<String, Collection<String>> headers) {
this(statusCode, headers, null);
}

Expand All @@ -22,7 +22,7 @@ public ApiResponse(int statusCode, Map<String, List<String>> headers) {
* @param headers The headers of HTTP response
* @param data The object deserialized from response bod
*/
public ApiResponse(int statusCode, Map<String, List<String>> headers, T data) {
public ApiResponse(int statusCode, Map<String, Collection<String>> headers, T data) {
this.statusCode = statusCode;
this.headers = headers;
this.data = data;
Expand All @@ -32,7 +32,7 @@ public int getStatusCode() {
return statusCode;
}

public Map<String, List<String>> getHeaders() {
public Map<String, Collection<String>> getHeaders() {
return headers;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package org.openapitools.client;

import static org.junit.jupiter.api.Assertions.assertEquals;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableMap;
import feign.Request;
import feign.Request.HttpMethod;
import feign.RequestTemplate;
import feign.Response;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import org.junit.jupiter.api.Test;
import org.openapitools.client.model.ApiResponse;
import org.openapitools.client.model.Cat;

class ApiResponseDecoderTest {

private final ObjectMapper objectMapper = new ObjectMapper();

ApiResponseDecoder decoder = new ApiResponseDecoder(objectMapper);


@Test
void shouldDecodeApiResponseWithHeaders() throws IOException {

final Cat cat = makeCat();
TypeReference<ApiResponse<Cat>> typeReference = new TypeReference<ApiResponse<Cat>>() {
};

ApiResponse<Cat> response = (ApiResponse<Cat>) decoder.decode(Response.builder()
.headers(ImmutableMap.of("Location",
Collections.singletonList("https://example.com/cats/1")))
.body(objectMapper.writeValueAsBytes(cat))
.status(201)
.request(buildRequest())
.build(), typeReference.getType());

assertEquals(cat.getColor(), response.getData().getColor());
assertEquals(cat.isDeclawed(), response.getData().isDeclawed());

Collection<String> locationValues = response.getHeaders().get("Location");
assertEquals(1, locationValues.size());
assertEquals("https://example.com/cats/1", locationValues.iterator().next());
}

private Cat makeCat() {
final Cat cat = new Cat();
cat.color("black");
cat.declawed(true);
return cat;
}

private Request buildRequest() {
return Request.create(HttpMethod.GET, "https://example.com/cats/1", Collections.emptyMap(),
null, new RequestTemplate());
}
}

0 comments on commit 87f9d53

Please sign in to comment.