forked from loafle/openapi-generator-original
[Java][client] Fix feign classcastexception when getting headers (#16745)
* 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
This commit is contained in:
parent
494ee489ad
commit
87f9d53c3a
6
.gitignore
vendored
6
.gitignore
vendored
@ -49,8 +49,6 @@ nb-configuration.xml
|
|||||||
/target
|
/target
|
||||||
/generated-files
|
/generated-files
|
||||||
test-output/
|
test-output/
|
||||||
nbactions.xml
|
|
||||||
test-output/
|
|
||||||
|
|
||||||
# website
|
# website
|
||||||
website/build/
|
website/build/
|
||||||
@ -73,7 +71,6 @@ samples/client/petstore/build
|
|||||||
samples/client/petstore/cpp-qt/PetStore/moc_*
|
samples/client/petstore/cpp-qt/PetStore/moc_*
|
||||||
samples/client/petstore/cpp-qt/PetStore/*.o
|
samples/client/petstore/cpp-qt/PetStore/*.o
|
||||||
samples/client/petstore/cpp-qt/build-*
|
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/PetStore
|
||||||
samples/client/petstore/cpp-qt/PetStore/Makefile
|
samples/client/petstore/cpp-qt/PetStore/Makefile
|
||||||
samples/client/petstore/cpp-qt/PetStore/PetStore.pro.user
|
samples/client/petstore/cpp-qt/PetStore/PetStore.pro.user
|
||||||
@ -100,9 +97,7 @@ samples/client/petstore/java/jersey2/build/
|
|||||||
samples/client/petstore/java/okhttp-gson/.gradle/
|
samples/client/petstore/java/okhttp-gson/.gradle/
|
||||||
samples/client/petstore/java/okhttp-gson/build/
|
samples/client/petstore/java/okhttp-gson/build/
|
||||||
samples/client/petstore/java/feign/build/
|
samples/client/petstore/java/feign/build/
|
||||||
samples/client/petstore/java/feign10x/build/
|
|
||||||
samples/client/petstore/java/feign/project/
|
samples/client/petstore/java/feign/project/
|
||||||
samples/client/petstore/java/feign10x/project/
|
|
||||||
samples/client/petstore/java/retrofit/build/
|
samples/client/petstore/java/retrofit/build/
|
||||||
samples/client/petstore/java/retrofit2/build/
|
samples/client/petstore/java/retrofit2/build/
|
||||||
samples/client/petstore/java/retrofit2/hello.txt
|
samples/client/petstore/java/retrofit2/hello.txt
|
||||||
@ -110,7 +105,6 @@ samples/client/petstore/java/retrofit2rx/build/
|
|||||||
samples/client/petstore/java/default/build/
|
samples/client/petstore/java/default/build/
|
||||||
samples/client/petstore/scala/build/
|
samples/client/petstore/scala/build/
|
||||||
samples/client/petstore/java/resttemplate/hello.txt
|
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/feign/hello.txt
|
||||||
samples/client/petstore/java/jersey2-java6/project/
|
samples/client/petstore/java/jersey2-java6/project/
|
||||||
samples/client/petstore/java/jersey2-java8/project/
|
samples/client/petstore/java/jersey2-java8/project/
|
||||||
|
@ -22,14 +22,13 @@ public class ApiResponseDecoder extends JacksonDecoder {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object decode(Response response, Type type) throws IOException {
|
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
|
//Detects if the type is an instance of the parameterized class ApiResponse
|
||||||
Type responseBodyType;
|
|
||||||
if (type instanceof ParameterizedType && Types.getRawType(type).isAssignableFrom(ApiResponse.class)) {
|
if (type instanceof ParameterizedType && Types.getRawType(type).isAssignableFrom(ApiResponse.class)) {
|
||||||
//The ApiResponse class has a single type parameter, the Dto class itself
|
//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);
|
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 {
|
} else {
|
||||||
//The response is not encapsulated in the ApiResponse, decode the Dto as normal
|
//The response is not encapsulated in the ApiResponse, decode the Dto as normal
|
||||||
return super.decode(response, type);
|
return super.decode(response, type);
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
package {{modelPackage}};
|
package {{modelPackage}};
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.List;
|
import java.util.Collection;
|
||||||
|
|
||||||
public class ApiResponse<T>{
|
public class ApiResponse<T>{
|
||||||
|
|
||||||
final private int statusCode;
|
final private int statusCode;
|
||||||
final private Map<String, List<String>> headers;
|
final private Map<String, Collection<String>> headers;
|
||||||
final private T data;
|
final private T data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param statusCode The status code of HTTP response
|
* @param statusCode The status code of HTTP response
|
||||||
* @param headers The headers 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);
|
this(statusCode, headers, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ public class ApiResponse<T>{
|
|||||||
* @param headers The headers of HTTP response
|
* @param headers The headers of HTTP response
|
||||||
* @param data The object deserialized from response bod
|
* @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.statusCode = statusCode;
|
||||||
this.headers = headers;
|
this.headers = headers;
|
||||||
this.data = data;
|
this.data = data;
|
||||||
@ -32,7 +32,7 @@ public class ApiResponse<T>{
|
|||||||
return statusCode;
|
return statusCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, List<String>> getHeaders() {
|
public Map<String, Collection<String>> getHeaders() {
|
||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
package org.openapitools.client.model;
|
package org.openapitools.client.model;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.List;
|
import java.util.Collection;
|
||||||
|
|
||||||
public class ApiResponse<T>{
|
public class ApiResponse<T>{
|
||||||
|
|
||||||
final private int statusCode;
|
final private int statusCode;
|
||||||
final private Map<String, List<String>> headers;
|
final private Map<String, Collection<String>> headers;
|
||||||
final private T data;
|
final private T data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param statusCode The status code of HTTP response
|
* @param statusCode The status code of HTTP response
|
||||||
* @param headers The headers 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);
|
this(statusCode, headers, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ public class ApiResponse<T>{
|
|||||||
* @param headers The headers of HTTP response
|
* @param headers The headers of HTTP response
|
||||||
* @param data The object deserialized from response bod
|
* @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.statusCode = statusCode;
|
||||||
this.headers = headers;
|
this.headers = headers;
|
||||||
this.data = data;
|
this.data = data;
|
||||||
@ -32,7 +32,7 @@ public class ApiResponse<T>{
|
|||||||
return statusCode;
|
return statusCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, List<String>> getHeaders() {
|
public Map<String, Collection<String>> getHeaders() {
|
||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,14 +22,13 @@ public class ApiResponseDecoder extends JacksonDecoder {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object decode(Response response, Type type) throws IOException {
|
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
|
//Detects if the type is an instance of the parameterized class ApiResponse
|
||||||
Type responseBodyType;
|
|
||||||
if (type instanceof ParameterizedType && Types.getRawType(type).isAssignableFrom(ApiResponse.class)) {
|
if (type instanceof ParameterizedType && Types.getRawType(type).isAssignableFrom(ApiResponse.class)) {
|
||||||
//The ApiResponse class has a single type parameter, the Dto class itself
|
//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);
|
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 {
|
} else {
|
||||||
//The response is not encapsulated in the ApiResponse, decode the Dto as normal
|
//The response is not encapsulated in the ApiResponse, decode the Dto as normal
|
||||||
return super.decode(response, type);
|
return super.decode(response, type);
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
package org.openapitools.client;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import feign.Response;
|
|
||||||
import feign.Types;
|
|
||||||
import feign.jackson.JacksonDecoder;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.reflect.ParameterizedType;
|
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.openapitools.client.model.HttpResponse;
|
|
||||||
|
|
||||||
public class JacksonResponseDecoder extends JacksonDecoder {
|
|
||||||
|
|
||||||
public JacksonResponseDecoder(ObjectMapper mapper) {
|
|
||||||
super(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 HttpResponse
|
|
||||||
Type responseBodyType;
|
|
||||||
if (Types.getRawType(type).isAssignableFrom(HttpResponse.class)) {
|
|
||||||
//The HttpResponse class has a single type parameter, the Dto class itself
|
|
||||||
responseBodyType = ((ParameterizedType) type).getActualTypeArguments()[0];
|
|
||||||
Object body = super.decode(response, responseBodyType);
|
|
||||||
return new HttpResponse(responseHeaders, body, response.status());
|
|
||||||
} else {
|
|
||||||
//The response is not encapsulated in the HttpResponse, decode the Dto as normal
|
|
||||||
return super.decode(response, type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +1,19 @@
|
|||||||
package org.openapitools.client.model;
|
package org.openapitools.client.model;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.List;
|
import java.util.Collection;
|
||||||
|
|
||||||
public class ApiResponse<T>{
|
public class ApiResponse<T>{
|
||||||
|
|
||||||
final private int statusCode;
|
final private int statusCode;
|
||||||
final private Map<String, List<String>> headers;
|
final private Map<String, Collection<String>> headers;
|
||||||
final private T data;
|
final private T data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param statusCode The status code of HTTP response
|
* @param statusCode The status code of HTTP response
|
||||||
* @param headers The headers 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);
|
this(statusCode, headers, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ public class ApiResponse<T>{
|
|||||||
* @param headers The headers of HTTP response
|
* @param headers The headers of HTTP response
|
||||||
* @param data The object deserialized from response bod
|
* @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.statusCode = statusCode;
|
||||||
this.headers = headers;
|
this.headers = headers;
|
||||||
this.data = data;
|
this.data = data;
|
||||||
@ -32,7 +32,7 @@ public class ApiResponse<T>{
|
|||||||
return statusCode;
|
return statusCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, List<String>> getHeaders() {
|
public Map<String, Collection<String>> getHeaders() {
|
||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
package org.openapitools.client.model;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
public class HttpResponse<T>{
|
|
||||||
|
|
||||||
private Map<String, Collection<String>> headers;
|
|
||||||
|
|
||||||
private T body;
|
|
||||||
|
|
||||||
private int status;
|
|
||||||
|
|
||||||
public HttpResponse(Map<String, Collection<String>> headers, T body, int status) {
|
|
||||||
this.headers = headers;
|
|
||||||
this.body = body;
|
|
||||||
this.status = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public T getBody(){
|
|
||||||
return body;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, Collection<String>> getHeaders(){
|
|
||||||
return headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getStatus(){
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
}
|
|
@ -22,14 +22,13 @@ public class ApiResponseDecoder extends JacksonDecoder {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object decode(Response response, Type type) throws IOException {
|
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
|
//Detects if the type is an instance of the parameterized class ApiResponse
|
||||||
Type responseBodyType;
|
|
||||||
if (type instanceof ParameterizedType && Types.getRawType(type).isAssignableFrom(ApiResponse.class)) {
|
if (type instanceof ParameterizedType && Types.getRawType(type).isAssignableFrom(ApiResponse.class)) {
|
||||||
//The ApiResponse class has a single type parameter, the Dto class itself
|
//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);
|
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 {
|
} else {
|
||||||
//The response is not encapsulated in the ApiResponse, decode the Dto as normal
|
//The response is not encapsulated in the ApiResponse, decode the Dto as normal
|
||||||
return super.decode(response, type);
|
return super.decode(response, type);
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
package org.openapitools.client.model;
|
package org.openapitools.client.model;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.List;
|
import java.util.Collection;
|
||||||
|
|
||||||
public class ApiResponse<T>{
|
public class ApiResponse<T>{
|
||||||
|
|
||||||
final private int statusCode;
|
final private int statusCode;
|
||||||
final private Map<String, List<String>> headers;
|
final private Map<String, Collection<String>> headers;
|
||||||
final private T data;
|
final private T data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param statusCode The status code of HTTP response
|
* @param statusCode The status code of HTTP response
|
||||||
* @param headers The headers 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);
|
this(statusCode, headers, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ public class ApiResponse<T>{
|
|||||||
* @param headers The headers of HTTP response
|
* @param headers The headers of HTTP response
|
||||||
* @param data The object deserialized from response bod
|
* @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.statusCode = statusCode;
|
||||||
this.headers = headers;
|
this.headers = headers;
|
||||||
this.data = data;
|
this.data = data;
|
||||||
@ -32,7 +32,7 @@ public class ApiResponse<T>{
|
|||||||
return statusCode;
|
return statusCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, List<String>> getHeaders() {
|
public Map<String, Collection<String>> getHeaders() {
|
||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user