Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Giving @Multipart more flexibility to set the type of Multipart #3494

Open
wants to merge 3 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions retrofit/src/main/java/retrofit2/RequestBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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));
}
}

Expand Down
7 changes: 6 additions & 1 deletion retrofit/src/main/java/retrofit2/RequestFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
}
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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.");
Expand Down
5 changes: 4 additions & 1 deletion retrofit/src/main/java/retrofit2/http/Multipart.java
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
48 changes: 48 additions & 0 deletions retrofit/src/test/java/retrofit2/RequestFactoryTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3252,6 +3252,54 @@ Call<ResponseBody> method(@Tag List<String> one, @Tag List<Long> two) {
}
}

@Test
public void shouldBuildRequestBodyWithContentTypeSuccessfully() throws IOException {
class Example {
@Multipart(type = "multipart/form-data; charset=utf-8") //
@POST("/foo/bar/") //
Call<ResponseBody> 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<ResponseBody> 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();
Expand Down