diff --git a/retrofit/src/main/java/retrofit2/RequestBuilder.java b/retrofit/src/main/java/retrofit2/RequestBuilder.java index d47f61fea5..bc3bb2080d 100644 --- a/retrofit/src/main/java/retrofit2/RequestBuilder.java +++ b/retrofit/src/main/java/retrofit2/RequestBuilder.java @@ -72,7 +72,8 @@ final class RequestBuilder { @Nullable MediaType contentType, boolean hasBody, boolean isFormEncoded, - boolean isMultipart) { + boolean isMultipart, + String multipartType) { this.method = method; this.baseUrl = baseUrl; this.relativeUrl = relativeUrl; @@ -92,7 +93,7 @@ final class RequestBuilder { } else if (isMultipart) { // Will be set to 'body' in 'build'. multipartBuilder = new MultipartBody.Builder(); - multipartBuilder.setType(MultipartBody.FORM); + multipartBuilder.setType(MediaType.get(multipartType)); } } diff --git a/retrofit/src/main/java/retrofit2/RequestFactory.java b/retrofit/src/main/java/retrofit2/RequestFactory.java index bea554efae..cc66552e74 100644 --- a/retrofit/src/main/java/retrofit2/RequestFactory.java +++ b/retrofit/src/main/java/retrofit2/RequestFactory.java @@ -76,6 +76,7 @@ static RequestFactory parseAnnotations(Retrofit retrofit, Method method) { private final boolean hasBody; private final boolean isFormEncoded; private final boolean isMultipart; + private final String multipartType; private final ParameterHandler[] parameterHandlers; final boolean isKotlinSuspendFunction; @@ -89,6 +90,7 @@ static RequestFactory parseAnnotations(Retrofit retrofit, Method method) { hasBody = builder.hasBody; isFormEncoded = builder.isFormEncoded; isMultipart = builder.isMultipart; + multipartType = builder.multipartType; parameterHandlers = builder.parameterHandlers; isKotlinSuspendFunction = builder.isKotlinSuspendFunction; } @@ -116,7 +118,8 @@ okhttp3.Request create(Object[] args) throws IOException { contentType, hasBody, isFormEncoded, - isMultipart); + isMultipart, + multipartType); if (isKotlinSuspendFunction) { // The Continuation is the last parameter and the handlers array contains null at that index. @@ -161,6 +164,7 @@ static final class Builder { boolean hasBody; boolean isFormEncoded; boolean isMultipart; + @Nullable String multipartType; @Nullable String relativeUrl; @Nullable Headers headers; @Nullable MediaType contentType; @@ -251,6 +255,7 @@ private void parseMethodAnnotation(Annotation annotation) { throw methodError(method, "Only one encoding annotation is allowed."); } isMultipart = true; + multipartType = ((Multipart) annotation).type(); } else if (annotation instanceof FormUrlEncoded) { if (isMultipart) { throw methodError(method, "Only one encoding annotation is allowed."); diff --git a/retrofit/src/main/java/retrofit2/http/Multipart.java b/retrofit/src/main/java/retrofit2/http/Multipart.java index 9ae1b5607d..fe5ad5035d 100644 --- a/retrofit/src/main/java/retrofit2/http/Multipart.java +++ b/retrofit/src/main/java/retrofit2/http/Multipart.java @@ -29,4 +29,7 @@ @Documented @Target(METHOD) @Retention(RUNTIME) -public @interface Multipart {} +public @interface Multipart { + /** Sets the type(MediaType) on MultipartBody. When calling the */ + String type() default "multipart/form-data"; +} diff --git a/retrofit/src/test/java/retrofit2/RequestFactoryTest.java b/retrofit/src/test/java/retrofit2/RequestFactoryTest.java index e8c21752ab..450925e052 100644 --- a/retrofit/src/test/java/retrofit2/RequestFactoryTest.java +++ b/retrofit/src/test/java/retrofit2/RequestFactoryTest.java @@ -3252,6 +3252,54 @@ Call method(@Tag List one, @Tag List two) { } } + @Test + public void shouldBuildRequestBodyWithContentTypeSuccessfully() throws IOException { + class Example { + @Multipart(type = "multipart/form-data; charset=utf-8") // + @POST("/foo/bar/") // + Call method(@Part("ping") String ping, @Part("kit") RequestBody kit) { + return null; + } + } + + Request request = buildRequest(Example.class, "pong", RequestBody.create(TEXT_PLAIN, "kat")); + assertThat(request.method()).isEqualTo("POST"); + assertThat(request.headers().size()).isZero(); + assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/"); + + RequestBody body = request.body(); + assertThat(body.contentType().toString()) + .startsWith("multipart/form-data; charset=utf-8; boundary="); + + Buffer buffer = new Buffer(); + body.writeTo(buffer); + String bodyString = buffer.readUtf8(); + + assertThat(bodyString) + .contains("Content-Disposition: form-data;") + .contains("name=\"ping\"\r\n") + .contains("\r\npong\r\n--") + .contains("name=\"kit\"") + .contains("\r\nkat\r\n--"); + } + + @Test + public void shouldFailRequestBodyWithContentTypeInvalid() throws IOException { + class Example { + @Multipart(type = "invalid-type") // + @POST("/foo/bar/") // + Call method(@Part("ping") String ping, @Part("kit") RequestBody kit) { + return null; + } + } + try { + buildRequest(Example.class, "pong", RequestBody.create(TEXT_PLAIN, "kat")); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessageContaining("No subtype found for: \"invalid-type\""); + } + } + private static void assertBody(RequestBody body, String expected) { assertThat(body).isNotNull(); Buffer buffer = new Buffer();