|
18 | 18 | import feign.Types; |
19 | 19 | import feign.jackson.JacksonDecoder; |
20 | 20 |
|
21 | | -import java.io.File; |
22 | 21 | import java.io.IOException; |
23 | | -import java.io.InputStream; |
24 | 22 | import java.lang.reflect.ParameterizedType; |
25 | 23 | import java.lang.reflect.Type; |
26 | | -import java.nio.file.Files; |
27 | | -import java.nio.file.Paths; |
28 | | -import java.nio.file.StandardCopyOption; |
29 | 24 | import java.util.Collection; |
30 | 25 | import java.util.Collections; |
31 | 26 | import java.util.Map; |
32 | | -import java.util.regex.Matcher; |
33 | | -import java.util.regex.Pattern; |
34 | 27 |
|
35 | 28 | import org.openapitools.client.model.ApiResponse; |
36 | 29 |
|
37 | 30 | public class ApiResponseDecoder extends JacksonDecoder { |
38 | 31 |
|
39 | | - private static final Pattern FILENAME_PATTERN = |
40 | | - Pattern.compile("filename=\"([^\"]+)\"|filename=([^\\s;]+)"); |
41 | | - |
42 | 32 | public ApiResponseDecoder(ObjectMapper mapper) { |
43 | 33 | super(mapper); |
44 | 34 | } |
45 | 35 |
|
46 | 36 | @Override |
47 | 37 | public Object decode(Response response, Type type) throws IOException { |
| 38 | + //Detects if the type is an instance of the parameterized class ApiResponse |
48 | 39 | if (type instanceof ParameterizedType && Types.getRawType(type).isAssignableFrom(ApiResponse.class)) { |
| 40 | + //The ApiResponse class has a single type parameter, the Dto class itself |
49 | 41 | Type responseBodyType = ((ParameterizedType) type).getActualTypeArguments()[0]; |
50 | | - Object body = isBinaryType(responseBodyType) |
51 | | - ? decodeBinary(response, responseBodyType) |
52 | | - : super.decode(response, responseBodyType); |
| 42 | + Object body = super.decode(response, responseBodyType); |
53 | 43 | Map<String, Collection<String>> responseHeaders = Collections.unmodifiableMap(response.headers()); |
54 | 44 | return new ApiResponse<>(response.status(), responseHeaders, body); |
55 | | - } |
56 | | - |
57 | | - if (isBinaryType(type)) { |
58 | | - return decodeBinary(response, type); |
59 | | - } |
60 | | - |
61 | | - return super.decode(response, type); |
62 | | - } |
63 | | - |
64 | | - private boolean isBinaryType(Type type) { |
65 | | - Class<?> raw = Types.getRawType(type); |
66 | | - return File.class.isAssignableFrom(raw) |
67 | | - || byte[].class.isAssignableFrom(raw) |
68 | | - || InputStream.class.isAssignableFrom(raw); |
69 | | - } |
70 | | - |
71 | | - private Object decodeBinary(Response response, Type type) throws IOException { |
72 | | - Class<?> raw = Types.getRawType(type); |
73 | | - if (response.body() == null) { |
74 | | - return null; |
75 | | - } |
76 | | - if (byte[].class.isAssignableFrom(raw)) { |
77 | | - return response.body().asInputStream().readAllBytes(); |
78 | | - } |
79 | | - if (InputStream.class.isAssignableFrom(raw)) { |
80 | | - return response.body().asInputStream(); |
81 | | - } |
82 | | - return downloadToTempFile(response); |
83 | | - } |
84 | | - |
85 | | - private File downloadToTempFile(Response response) throws IOException { |
86 | | - String filename = extractFilename(response); |
87 | | - File file; |
88 | | - if (filename != null) { |
89 | | - // Sanitize: strip path components to prevent path traversal |
90 | | - String safeName = Paths.get(filename).getFileName().toString(); |
91 | | - java.nio.file.Path tempDir = Files.createTempDirectory("feign-download"); |
92 | | - file = Files.createFile(tempDir.resolve(safeName)).toFile(); |
93 | | - tempDir.toFile().deleteOnExit(); |
94 | 45 | } else { |
95 | | - file = Files.createTempFile("download-", "").toFile(); |
96 | | - } |
97 | | - file.deleteOnExit(); |
98 | | - try (InputStream is = response.body().asInputStream()) { |
99 | | - Files.copy(is, file.toPath(), StandardCopyOption.REPLACE_EXISTING); |
100 | | - } |
101 | | - return file; |
102 | | - } |
103 | | - |
104 | | - private String extractFilename(Response response) { |
105 | | - Collection<String> dispositions = response.headers().get("Content-Disposition"); |
106 | | - if (dispositions == null) return null; |
107 | | - for (String disposition : dispositions) { |
108 | | - Matcher m = FILENAME_PATTERN.matcher(disposition); |
109 | | - if (m.find()) { |
110 | | - // Group 1: quoted filename (may contain spaces), Group 2: unquoted token |
111 | | - return m.group(1) != null ? m.group(1) : m.group(2); |
112 | | - } |
| 46 | + //The response is not encapsulated in the ApiResponse, decode the Dto as normal |
| 47 | + return super.decode(response, type); |
113 | 48 | } |
114 | | - return null; |
115 | 49 | } |
116 | 50 | } |
0 commit comments