From b8caa85fa8d1ebd46888c6a5efe8540a6551ca65 Mon Sep 17 00:00:00 2001 From: Mick Jermsurawong Date: Tue, 9 Apr 2019 22:42:46 -0700 Subject: [PATCH] support nested file params --- .../stripe/net/LiveStripeResponseGetter.java | 99 +++++++++++-------- .../java/com/stripe/functional/FileTest.java | 10 +- .../net/LiveStripeResponseGetterTest.java | 49 +++++++++ 3 files changed, 117 insertions(+), 41 deletions(-) diff --git a/src/main/java/com/stripe/net/LiveStripeResponseGetter.java b/src/main/java/com/stripe/net/LiveStripeResponseGetter.java index ac38074c4a0..e0642326428 100644 --- a/src/main/java/com/stripe/net/LiveStripeResponseGetter.java +++ b/src/main/java/com/stripe/net/LiveStripeResponseGetter.java @@ -367,6 +367,13 @@ private static List flattenParamsValue(Object value, String keyPrefix } else if (value == null) { flatParams = new ArrayList<>(); flatParams.add(new Parameter(keyPrefix, "")); + } else if ((value instanceof File) || (value instanceof InputStream)) { + throw new InvalidRequestException(String.format( + "java.io.File or java.io.InputStream %s is not supported at '%s' parameter. " + + "Please check our API reference for the parameter type, " + + "or use the provided parameter class instead.", + value, keyPrefix), + keyPrefix, null, null, 0, null); } else { flatParams = new ArrayList<>(); flatParams.add(new Parameter(keyPrefix, value.toString())); @@ -607,46 +614,8 @@ private static StripeResponse getMultipartStripeResponse( MultipartProcessor multipartProcessor = null; try { - multipartProcessor = new MultipartProcessor( - conn, boundary, ApiResource.CHARSET); - - for (Map.Entry entry : params.entrySet()) { - String key = entry.getKey(); - Object value = entry.getValue(); - - if (value instanceof File) { - File currentFile = (File) value; - if (!currentFile.exists()) { - throw new InvalidRequestException("File for key " - + key + " must exist.", null, null, null, 0, null); - } else if (!currentFile.isFile()) { - throw new InvalidRequestException("File for key " - + key - + " must be a file and not a directory.", - null, null, null, 0, null); - } else if (!currentFile.canRead()) { - throw new InvalidRequestException( - "Must have read permissions on file for key " - + key + ".", null, null, null, 0, null); - } - multipartProcessor.addFileField(key, currentFile.getName(), - new FileInputStream(currentFile)); - } else if (value instanceof InputStream) { - @Cleanup InputStream inputStream = (InputStream) value; - if (inputStream.available() == 0) { - throw new InvalidRequestException( - "Must have available bytes to read on InputStream for key " - + key + ".", null, null, null, 0, null - ); - } - multipartProcessor.addFileField(key, "blob", inputStream); - } else { - // We only allow a single level of nesting for params - // for multipart - multipartProcessor.addFormField(key, (String) value); - } - } - + multipartProcessor = new MultipartProcessor(conn, boundary, ApiResource.CHARSET); + encodeMultipartParams(multipartProcessor, params); } finally { if (multipartProcessor != null) { multipartProcessor.finish(); @@ -682,6 +651,56 @@ private static StripeResponse getMultipartStripeResponse( } + /** + * Encode multipart params as a counter-part method to {@link this#createQuery(Map)} for + * encoding params for non-multipart request. + * @param multipartProcessor multi-part processor handling encoding of input stream and + * basic key-value forms. + * @param params parameter map that can contain file or input stream. + */ + static void encodeMultipartParams(MultipartProcessor multipartProcessor, + Map params) + throws InvalidRequestException, IOException { + + for (Map.Entry entry : params.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + + if (value instanceof File) { + File currentFile = (File) value; + if (!currentFile.exists()) { + throw new InvalidRequestException("File for key " + + key + " must exist.", null, null, null, 0, null); + } else if (!currentFile.isFile()) { + throw new InvalidRequestException("File for key " + + key + + " must be a file and not a directory.", + null, null, null, 0, null); + } else if (!currentFile.canRead()) { + throw new InvalidRequestException( + "Must have read permissions on file for key " + + key + ".", null, null, null, 0, null); + } + multipartProcessor.addFileField(key, currentFile.getName(), + new FileInputStream(currentFile)); + } else if (value instanceof InputStream) { + @Cleanup InputStream inputStream = (InputStream) value; + if (inputStream.available() == 0) { + throw new InvalidRequestException( + "Must have available bytes to read on InputStream for key " + + key + ".", null, null, null, 0, null + ); + } + multipartProcessor.addFileField(key, "blob", inputStream); + } else { + List parameters = flattenParamsValue(value, key); + for (Parameter parameter : parameters) { + multipartProcessor.addFormField(parameter.key, parameter.value); + } + } + } + } + private static void raiseMalformedJsonError(String responseBody, int responseCode, String requestId) throws ApiException { throw new ApiException( diff --git a/src/test/java/com/stripe/functional/FileTest.java b/src/test/java/com/stripe/functional/FileTest.java index b8f53fc7076..23ce4f208d1 100644 --- a/src/test/java/com/stripe/functional/FileTest.java +++ b/src/test/java/com/stripe/functional/FileTest.java @@ -47,6 +47,10 @@ public void testCreateWithFileWithTypedParams() throws StripeException { File fileObject = new File(getClass().getResource("/test.png").getFile()); FileCreateParams fileCreateParams = FileCreateParams.builder() .setPurpose(FileCreateParams.Purpose.DISPUTE_EVIDENCE) + .setFileLinkData(FileCreateParams.FileLinkData.builder() + .setCreate(true) + .setExpiresAt(123L) + .build()) .setFile(fileObject) .build(); @@ -58,7 +62,11 @@ public void testCreateWithFileWithTypedParams() throws StripeException { "/v1/files", ImmutableMap.of( "purpose", "dispute_evidence", - "file", fileObject + "file", fileObject, + "file_link_data", ImmutableMap.of( + "create", true, + "expires_at", 123 + ) ), ApiResource.RequestType.MULTIPART, null diff --git a/src/test/java/com/stripe/net/LiveStripeResponseGetterTest.java b/src/test/java/com/stripe/net/LiveStripeResponseGetterTest.java index 1f8b07e581b..55f7c395018 100644 --- a/src/test/java/com/stripe/net/LiveStripeResponseGetterTest.java +++ b/src/test/java/com/stripe/net/LiveStripeResponseGetterTest.java @@ -1,22 +1,32 @@ package com.stripe.net; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import com.stripe.Stripe; +import com.stripe.exception.InvalidRequestException; import com.stripe.exception.StripeException; import com.stripe.net.RequestOptions.RequestOptionsBuilder; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -164,6 +174,45 @@ public void testCorrectAdditionalOwners() throws StripeException, UnsupportedEnc LiveStripeResponseGetter.createQuery(params)); } + @Test + public void testThrowingOnCreateQueryWithFile() { + final Map params = new HashMap<>(); + params.put("file", new File(getClass().getResource("/test.png").getFile())); + + // file encoding should be handled in multi-part request instead + assertThrows(InvalidRequestException.class, () -> { + LiveStripeResponseGetter.createQuery(params); + }); + } + + @Test + public void testEncodeMultipartParams() throws IOException, InvalidRequestException { + final HttpURLConnection mockConn = mock(HttpURLConnection.class); + final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + when(mockConn.getOutputStream()).thenReturn(outputStream); + final MultipartProcessor multipartProcessor = new MultipartProcessor( + mockConn, "abc", ApiResource.CHARSET); + + final Map params = new HashMap<>(); + params.put("file", new File(getClass().getResource("/test.png").getFile())); + + final Map nestedParams = new HashMap<>(); + nestedParams.put("create", true); + nestedParams.put("expires_at", 123L); + params.put("file_link_data", nestedParams); + + LiveStripeResponseGetter.encodeMultipartParams(multipartProcessor, params); + + String encoded = outputStream.toString(); + + assertThat(encoded, CoreMatchers.containsString( + "Content-Disposition: form-data; name=\"file\"; filename=\"test.png\"")); + assertThat(encoded, CoreMatchers.containsString( + "Content-Disposition: form-data; name=\"file_link_data[create]\"")); + assertThat(encoded, CoreMatchers.containsString( + "Content-Disposition: form-data; name=\"file_link_data[expires_at]\"")); + } + @Test public void testAppInfo() { final RequestOptions options = new RequestOptionsBuilder().setApiKey("sk_foobar").build();