From ec34ef9a9197e89035a4b2f2f3a1ae249b3ab063 Mon Sep 17 00:00:00 2001 From: Nidhi Date: Tue, 9 Nov 2021 10:33:54 +0530 Subject: [PATCH] fix: Added support for multiple files in multipart REST (#8986) * fix: Added support for multiple files in multipart REST * Added test case * Review comment * Review comment --- .../java/com/external/helpers/DataUtils.java | 55 +++++++++++++------ .../com/external/helpers/DataUtilsTest.java | 37 ++++++++++++- 2 files changed, 75 insertions(+), 17 deletions(-) diff --git a/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/helpers/DataUtils.java b/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/helpers/DataUtils.java index 2fdb484e94..51693ed536 100644 --- a/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/helpers/DataUtils.java +++ b/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/helpers/DataUtils.java @@ -26,6 +26,8 @@ import java.io.ByteArrayInputStream; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.Map; @@ -150,26 +152,15 @@ public class DataUtils { if (MultipartFormDataType.TEXT.equals(multipartFormDataType)) { bodyBuilder.part(key, property.getValue()); } else if (MultipartFormDataType.FILE.equals(multipartFormDataType)) { - - MultipartFormDataDTO multipartFormDataDTO = null; try { - multipartFormDataDTO = objectMapper.readValue( - String.valueOf(property.getValue()), - MultipartFormDataDTO.class); + populateFileTypeBodyBuilder(bodyBuilder, property, outputMessage); } catch (JsonProcessingException e) { e.printStackTrace(); + throw new AppsmithPluginException( + AppsmithPluginError.PLUGIN_DATASOURCE_ARGUMENT_ERROR, + "Unable to parse content. Expected to receive an array or object of multipart data" + ); } - final MultipartFormDataDTO finalMultipartFormDataDTO = multipartFormDataDTO; - Flux data = DataBufferUtils.readInputStream( - () -> new ByteArrayInputStream(String - .valueOf(finalMultipartFormDataDTO.getData()) - .getBytes(StandardCharsets.UTF_8)), - outputMessage.bufferFactory(), - 4096); - - bodyBuilder.asyncPart(key, data, DataBuffer.class) - .filename(multipartFormDataDTO.getName()) - .contentType(MediaType.valueOf(multipartFormDataDTO.getType())); } } @@ -181,6 +172,38 @@ public class DataUtils { }); } + private void populateFileTypeBodyBuilder(MultipartBodyBuilder bodyBuilder, Property property, ClientHttpRequest outputMessage) + throws JsonProcessingException { + final Object fileValue = property.getValue(); + final String key = property.getKey(); + List multipartFormDataDTOs = new ArrayList<>(); + try { + multipartFormDataDTOs = Arrays.asList( + objectMapper.readValue( + String.valueOf(fileValue), + MultipartFormDataDTO[].class)); + } catch (JsonProcessingException e) { + e.printStackTrace(); + final MultipartFormDataDTO multipartFormDataDTO = objectMapper.readValue(String.valueOf(fileValue), + MultipartFormDataDTO.class); + multipartFormDataDTOs.add(multipartFormDataDTO); + } + multipartFormDataDTOs.forEach(multipartFormDataDTO -> { + final MultipartFormDataDTO finalMultipartFormDataDTO = multipartFormDataDTO; + Flux data = DataBufferUtils.readInputStream( + () -> new ByteArrayInputStream(String + .valueOf(finalMultipartFormDataDTO.getData()) + .getBytes(StandardCharsets.UTF_8)), + outputMessage.bufferFactory(), + 4096); + + bodyBuilder.asyncPart(key, data, DataBuffer.class) + .filename(multipartFormDataDTO.getName()) + .contentType(MediaType.valueOf(multipartFormDataDTO.getType())); + }); + + } + /** * Given a JSON string, we infer the top-level type of the object it represents and then parse it into that * type. However, only `Map` and `List` top-levels are supported. Note that the map or list may contain diff --git a/app/server/appsmith-plugins/restApiPlugin/src/test/java/com/external/helpers/DataUtilsTest.java b/app/server/appsmith-plugins/restApiPlugin/src/test/java/com/external/helpers/DataUtilsTest.java index db88eaee6e..7faab6ae0f 100644 --- a/app/server/appsmith-plugins/restApiPlugin/src/test/java/com/external/helpers/DataUtilsTest.java +++ b/app/server/appsmith-plugins/restApiPlugin/src/test/java/com/external/helpers/DataUtilsTest.java @@ -163,6 +163,41 @@ public class DataUtilsTest { .verify(); } + @Test + public void testParseMultipartFileData_withValidMultipleFileList_returnsExpectedBody() { + List properties = new ArrayList<>(); + final Property p1 = new Property("fileType", "[{\"name\": \"test1.json\", \"type\": \"application/json\", \"data\" : {}}, {\"name\": \"test2.json\", \"type\": \"application/json\", \"data\" : {}}]"); + p1.setType("file"); + properties.add(p1); + + final BodyInserter bodyInserter = + (BodyInserter) dataUtils.parseMultipartFileData(properties); + MockClientHttpRequest request = new MockClientHttpRequest(HttpMethod.POST, URI.create("https://example.com")); + + Mono result = bodyInserter.insert(request, this.context); + StepVerifier.create(result).expectComplete().verify(); + StepVerifier.create(DataBufferUtils.join(request.getBody())) + .consumeNextWith(dataBuffer -> { + byte[] resultBytes = new byte[dataBuffer.readableByteCount()]; + dataBuffer.read(resultBytes); + DataBufferUtils.release(dataBuffer); + String content = new String(resultBytes, StandardCharsets.UTF_8); + Assert.assertTrue(content.contains( + "Content-Disposition: form-data; name=\"fileType\"; filename=\"test1.json\"\r\n" + + "Content-Type: application/json\r\n" + + "\r\n" + + "{}")); + + Assert.assertTrue(content.contains( + "Content-Disposition: form-data; name=\"fileType\"; filename=\"test2.json\"\r\n" + + "Content-Type: application/json\r\n" + + "\r\n" + + "{}")); + }) + .expectComplete() + .verify(); + } + @Test public void testParseFormData_withEncodingParamsToggleTrue_returnsEncodedString() throws UnsupportedEncodingException { final String encoded_value = dataUtils.parseFormData(List.of(new Property("key", "valüe")), @@ -177,7 +212,7 @@ public class DataUtilsTest { } @Test - public void testParseFormData_withOutEncodingParamsToggleTrue_returnsEncodedString() throws UnsupportedEncodingException { + public void testParseFormData_withoutEncodingParamsToggleTrue_returnsEncodedString() throws UnsupportedEncodingException { final String encoded_value = dataUtils.parseFormData(List.of(new Property("key", "valüe")), false); String expected_value = null;