Skip to content

Commit

Permalink
Merge pull request square#470 from ypresto/add_part_map
Browse files Browse the repository at this point in the history
Add @PartMap annotation.
  • Loading branch information
JakeWharton committed May 2, 2014
2 parents 196c7d3 + 07548db commit 3003982
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 0 deletions.
17 changes: 17 additions & 0 deletions retrofit/src/main/java/retrofit/RequestBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,23 @@ void setArguments(Object[] args) {
}
}
break;
case PART_MAP:
if (value != null) { // Skip null values.
for (Map.Entry<?, ?> entry : ((Map<?, ?>) value).entrySet()) {
String entryName = entry.getKey().toString();
Object entryValue = entry.getValue();
if (entryValue != null) { // Skip null values.
if (entryValue instanceof TypedOutput) {
multipartBody.addPart(entryName, (TypedOutput) entryValue);
} else if (entryValue instanceof String) {
multipartBody.addPart(entryName, new TypedString((String) entryValue));
} else {
multipartBody.addPart(entryName, converter.toBody(entryValue));
}
}
}
}
break;
case BODY:
if (value == null) {
throw new IllegalArgumentException("Body parameter value must not be null.");
Expand Down
13 changes: 13 additions & 0 deletions retrofit/src/main/java/retrofit/RestMethodInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import retrofit.http.Headers;
import retrofit.http.Multipart;
import retrofit.http.Part;
import retrofit.http.PartMap;
import retrofit.http.Path;
import retrofit.http.Query;
import retrofit.http.QueryMap;
Expand Down Expand Up @@ -68,6 +69,7 @@ enum ParamUsage {
FIELD,
FIELD_MAP,
PART,
PART_MAP,
BODY,
HEADER
}
Expand Down Expand Up @@ -411,6 +413,17 @@ private void parseParameters() {
gotPart = true;
paramNames[i] = name;
paramUsage[i] = ParamUsage.PART;
} else if (annotationType == PartMap.class) {
if (requestType != RequestType.MULTIPART) {
throw parameterError(i,
"@PartMap parameters can only be used with multipart encoding.");
}
if (!Map.class.isAssignableFrom(parameterType)) {
throw parameterError(i, "@PartMap parameter type must be Map.");
}

gotPart = true;
paramUsage[i] = ParamUsage.PART_MAP;
} else if (annotationType == Body.class) {
if (requestType != RequestType.SIMPLE) {
throw parameterError(i,
Expand Down
52 changes: 52 additions & 0 deletions retrofit/src/main/java/retrofit/http/PartMap.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright (C) 2014 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package retrofit.http;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* Denotes name and value parts of a multi-part request
* <p>
* Values of the map on which this annotation exists will be processed in one of three ways:
* <ul>
* <li>If the type implements {@link retrofit.mime.TypedOutput TypedOutput} the headers and
* body will be used directly.</li>
* <li>If the type is {@link String} the value will also be used directly with a {@code text/plain}
* content type.</li>
* <li>Other object types will be converted to an appropriate representation by calling {@link
* retrofit.converter.Converter#toBody(Object)}.</li>
* </ul>
* <p>
* <pre>
* &#64;Multipart
* &#64;POST("/upload")
* void upload(&#64;Part("file") TypedFile file, &#64;PartMap Map&lt;String, String&gt; params);
* </pre>
* <p>
*
* @see Multipart
* @see Part
*/
@Documented
@Target(PARAMETER)
@Retention(RUNTIME)
public @interface PartMap {
}
40 changes: 40 additions & 0 deletions retrofit/src/test/java/retrofit/RequestBuilderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import static retrofit.RestMethodInfo.ParamUsage.FIELD_MAP;
import static retrofit.RestMethodInfo.ParamUsage.HEADER;
import static retrofit.RestMethodInfo.ParamUsage.PART;
import static retrofit.RestMethodInfo.ParamUsage.PART_MAP;
import static retrofit.RestMethodInfo.ParamUsage.PATH;
import static retrofit.RestMethodInfo.ParamUsage.QUERY;
import static retrofit.RestMethodInfo.ParamUsage.QUERY_MAP;
Expand Down Expand Up @@ -490,6 +491,38 @@ public class RequestBuilderTest {
assertThat(two).contains("name=\"kit\"").endsWith("\r\nkat");
}

@Test public void multipartPartMap() throws Exception {
Map<String, Object> params = new LinkedHashMap<String, Object>();
params.put("ping", "pong");
params.put("kit", new TypedString("kat"));

Request request = new Helper() //
.setMethod("POST") //
.setHasBody() //
.setUrl("http://example.com") //
.setPath("/foo/bar/") //
.setMultipart() //
.addPartMap("params", params) //
.build();
assertThat(request.getMethod()).isEqualTo("POST");
assertThat(request.getHeaders()).hasSize(2);
assertThat(request.getHeaders().get(0).getName()).isEqualTo("Content-Type");
assertThat(request.getHeaders().get(1)).isEqualTo(new Header("Content-Length", "414"));
assertThat(request.getUrl()).isEqualTo("http://example.com/foo/bar/");

MultipartTypedOutput body = (MultipartTypedOutput) request.getBody();
List<byte[]> bodyParts = MimeHelper.getParts(body);
assertThat(bodyParts).hasSize(2);

Iterator<byte[]> iterator = bodyParts.iterator();

String one = new String(iterator.next(), "UTF-8");
assertThat(one).contains("name=\"ping\"\r\n").endsWith("\r\npong");

String two = new String(iterator.next(), "UTF-8");
assertThat(two).contains("name=\"kit\"").endsWith("\r\nkat");
}

@Test public void multipartNullRemovesPart() throws Exception {
Request request = new Helper() //
.setMethod("POST") //
Expand Down Expand Up @@ -858,6 +891,13 @@ Helper addPart(String name, Object value) {
return this;
}

Helper addPartMap(String name, Map<String, Object> values) {
paramNames.add(name);
paramUsages.add(PART_MAP);
args.add(values);
return this;
}

Helper setBody(Object value) {
paramNames.add(null);
paramUsages.add(BODY);
Expand Down
34 changes: 34 additions & 0 deletions retrofit/src/test/java/retrofit/RestMethodInfoTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import retrofit.http.POST;
import retrofit.http.PUT;
import retrofit.http.Part;
import retrofit.http.PartMap;
import retrofit.http.Path;
import retrofit.http.Query;
import retrofit.http.QueryMap;
Expand Down Expand Up @@ -908,6 +909,21 @@ Response a(@Part("a") TypedOutput a, @Part("b") int b) {
assertThat(methodInfo.requestType).isEqualTo(MULTIPART);
}

@Test public void partMapMultipart() {
class Example {
@Multipart @PUT("/")
Response a(@Part("a") TypedOutput a, @PartMap Map<String, String> b) {
return null;
}
}

Method method = TestingUtils.getMethod(Example.class, "a");
RestMethodInfo methodInfo = new RestMethodInfo(method);
methodInfo.init();

assertThat(methodInfo.requestType).isEqualTo(MULTIPART);
}

@Test public void implicitMultipartForbidden() {
class Example {
@POST("/") Response a(@Part("a") int a) {
Expand All @@ -926,6 +942,24 @@ class Example {
}
}

@Test public void implicitMultipartWithPartMapForbidden() {
class Example {
@POST("/") Response a(@PartMap Map<String, String> params) {
return null;
}
}

Method method = TestingUtils.getMethod(Example.class, "a");
RestMethodInfo methodInfo = new RestMethodInfo(method);
try {
methodInfo.init();
fail();
} catch (IllegalArgumentException e) {
assertThat(e).hasMessage(
"Example.a: @PartMap parameters can only be used with multipart encoding. (parameter #1)");
}
}

@Test public void multipartFailsOnNonBodyMethod() {
class Example {
@Multipart @GET("/") Response a() {
Expand Down

0 comments on commit 3003982

Please sign in to comment.