Skip to content

Commit

Permalink
fix issue square#198: add new @headers and @Header annotations
Browse files Browse the repository at this point in the history
  • Loading branch information
adriancole committed May 9, 2013
1 parent f844530 commit 39f0341
Show file tree
Hide file tree
Showing 8 changed files with 265 additions and 9 deletions.
27 changes: 27 additions & 0 deletions retrofit/src/main/java/retrofit/http/Header.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright 2013 Square, Inc.
package retrofit.http;

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

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

/**
* Replaces the header with the the value of its target. If the target is null,
* the header is removed.
*
* <p/>
* ex.
*
* <pre>
* @GET("/")
* void foo(@Header("Auth-Token") String token, ..);
* </pre>
*
* @author Adrian Cole ([email protected])
*/
@Retention(RUNTIME) @Target(PARAMETER)
public @interface Header {
String value();
}
2 changes: 1 addition & 1 deletion retrofit/src/main/java/retrofit/http/HeaderPair.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2012 Square, Inc.
// Copyright 2013 Square, Inc.
package retrofit.http;

/** Represents an HTTP header name/value pair. */
Expand Down
34 changes: 34 additions & 0 deletions retrofit/src/main/java/retrofit/http/Headers.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright 2013 Square, Inc.
package retrofit.http;

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

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

/**
* Adds headers literally supplied in the {@code value}.
*
* <p/>
* ex.
*
* <pre>
* @Headers("Cache-Control: max-age=640000")
* @GET("/")
* ...
*
* @Headers({
* "X-Foo: Bar",
* "X-Ping: Pong"
* })
* @GET("/")
* ...
* </pre>
*
* @author Adrian Cole ([email protected])
*/
@Target(METHOD) @Retention(RUNTIME)
public @interface Headers {
String[] value();
}
27 changes: 26 additions & 1 deletion retrofit/src/main/java/retrofit/http/RequestBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
Expand Down Expand Up @@ -64,7 +65,7 @@ private List<Parameter> createParamList() {
int singleEntityArgumentIndex = methodInfo.singleEntityArgumentIndex;
for (int i = 0; i < pathNamedParams.length; i++) {
Object arg = args[i];
if (arg == null) continue;
if (arg == null || pathNamedParams[i] == null) continue;
if (i != singleEntityArgumentIndex) {
params.add(new Parameter(pathNamedParams[i], arg, arg.getClass()));
}
Expand Down Expand Up @@ -150,6 +151,30 @@ Request build() {
}
}

List<HeaderPair> headers = new ArrayList<HeaderPair>();
if (this.headers != null) {
headers.addAll(this.headers);
}
if (methodInfo.headers != null) {
headers.addAll(methodInfo.headers);
}
List<String> headersToRemove = new ArrayList<String>();
if (methodInfo.headerParams != null) {
for (int i = 0; i < methodInfo.headerParams.length; i++) {
String name = methodInfo.headerParams[i];
if (name == null) continue;
Object arg = args[i];
if (arg != null) {
headers.add(new HeaderPair(name, arg.toString()));
} else {
headersToRemove.add(name);
}
}
}
for (Iterator<HeaderPair> header = headers.iterator(); header.hasNext();) {
if (headersToRemove.contains(header.next().getName()))
header.remove();
}
return new Request(methodInfo.restMethod.value(), url.toString(), headers, body);
}

Expand Down
4 changes: 2 additions & 2 deletions retrofit/src/main/java/retrofit/http/RestAdapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ public interface Log {
private volatile boolean debug;

private RestAdapter(Server server, Client.Provider clientProvider, Executor httpExecutor,
Executor callbackExecutor, HeaderPairs headers, Converter converter, Profiler profiler, Log log,
boolean debug) {
Executor callbackExecutor, HeaderPairs headers, Converter converter, Profiler profiler,
Log log, boolean debug) {
this.server = server;
this.clientProvider = clientProvider;
this.httpExecutor = httpExecutor;
Expand Down
38 changes: 34 additions & 4 deletions retrofit/src/main/java/retrofit/http/RestMethodInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
Expand All @@ -27,6 +29,8 @@ final class RestMethodInfo {
String path;
Set<String> pathParams;
QueryParam[] pathQueryParams;
List<HeaderPair> headers;
String[] headerParams;
String[] namedParams;
int singleEntityArgumentIndex = NO_SINGLE_ENTITY;
boolean isMultipart = false;
Expand All @@ -46,8 +50,8 @@ synchronized void init() {
}

/**
* Loads {@link #restMethod}, {@link #path}, {@link #pathParams}, {@link #pathQueryParams}, and
* {@link #isMultipart}.
* Loads {@link #restMethod}, {@link #path}, {@link #pathParams}, {@link #pathQueryParams},
* {@link headers}, and {@link #isMultipart}.
*/
private void parseMethodAnnotations() {
for (Annotation methodAnnotation : method.getAnnotations()) {
Expand All @@ -73,6 +77,12 @@ private void parseMethodAnnotations() {
}
pathParams = parsePathParameters(path);
restMethod = methodInfo;
} else if (annotationType == Headers.class) {
String[] headersToParse = ((Headers) methodAnnotation).value();
if (headersToParse.length == 0) {
throw new IllegalStateException("Headers annotation was empty.");
}
headers = parseHeaders(headersToParse);
} else if (annotationType == QueryParams.class) {
if (pathQueryParams != null) {
throw new IllegalStateException(
Expand Down Expand Up @@ -112,6 +122,17 @@ private void parseMethodAnnotations() {
}
}

private List<HeaderPair> parseHeaders(String[] headersToParse) {
List<HeaderPair> headers = new ArrayList<HeaderPair>();
for (String headerToParse: headersToParse) {
int colon = headerToParse.indexOf(':');
headers.add(new HeaderPair(headerToParse.substring(0, colon),
headerToParse.substring(colon + 2)));

}
return headers;
}

/** Loads {@link #type}. Returns true if the method is synchronous. */
private boolean parseResponseType() {
// Synchronous methods have a non-void return type.
Expand Down Expand Up @@ -168,8 +189,8 @@ private boolean parseResponseType() {
}

/**
* Loads {@link #namedParams}, {@link #singleEntityArgumentIndex}. Must be called after
* {@link #parseMethodAnnotations()}}.
* Loads {@link #namedParams}, {@link headerParams}, {@link #singleEntityArgumentIndex},
* Must be called after {@link #parseMethodAnnotations()}}.
*/
private void parseParameters() {
Class<?>[] parameterTypes = method.getParameterTypes();
Expand All @@ -180,6 +201,7 @@ private void parseParameters() {
}

String[] namedParams = new String[count];
String[] headerParams = new String[count];
for (int i = 0; i < count; i++) {
Class<?> parameterType = parameterTypes[i];
Annotation[] parameterAnnotations = parameterAnnotationArrays[i];
Expand All @@ -199,6 +221,13 @@ private void parseParameters() {
throw new IllegalStateException(
"Non-path params can only be used in multipart request.");
}
} else if (annotationType == Header.class) {
String header = ((Header) parameterAnnotation).value();
headerParams[i] = header;
if (parameterType != String.class) {
throw new IllegalStateException(
"Expected @Header parameter type to be String: " + header);
}
} else if (annotationType == SingleEntity.class) {
if (isMultipart) {
throw new IllegalStateException("SingleEntity cannot be used with multipart request.");
Expand Down Expand Up @@ -228,6 +257,7 @@ private void parseParameters() {
"Non-body HTTP method cannot contain @SingleEntity or @TypedOutput.");
}
this.namedParams = namedParams;
this.headerParams = headerParams;
}

/**
Expand Down
79 changes: 78 additions & 1 deletion retrofit/src/test/java/retrofit/http/RequestBuilderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.junit.Test;
import retrofit.http.client.Request;
Expand Down Expand Up @@ -248,6 +247,66 @@ public class RequestBuilderTest {
assertThat(request.getBody()).isNull();
}

@Test public void methodHeader() throws Exception {
Request request = new Helper() //
.setMethod("GET") //
.setUrl("http://example.com") //
.setPath("/foo/bar/") //
.addHeader("ping", "pong") //
.addMethodHeader("kit", "kat") //
.build();
assertThat(request.getMethod()).isEqualTo("GET");
assertThat(request.getHeaders()) //
.containsExactly(new HeaderPair("ping", "pong"), new HeaderPair("kit", "kat"));
assertThat(request.getUrl()).isEqualTo("http://example.com/foo/bar/");
assertThat(request.getBody()).isNull();
}

@Test public void headerParam() throws Exception {
Request request = new Helper() //
.setMethod("GET") //
.setUrl("http://example.com") //
.setPath("/foo/bar/") //
.addHeader("ping", "pong") //
.addHeaderParam("kit", "kat") //
.build();
assertThat(request.getMethod()).isEqualTo("GET");
assertThat(request.getHeaders()) //
.containsExactly(new HeaderPair("ping", "pong"), new HeaderPair("kit", "kat"));
assertThat(request.getUrl()).isEqualTo("http://example.com/foo/bar/");
assertThat(request.getBody()).isNull();
}

@Test public void nullHeaderParamRemovesHeader() throws Exception {
Request request = new Helper() //
.setMethod("GET") //
.setUrl("http://example.com") //
.setPath("/foo/bar/") //
.addHeader("ping", "pong") //
.addHeaderParam("ping", null) //
.build();
assertThat(request.getMethod()).isEqualTo("GET");
assertThat(request.getHeaders()).isEmpty();
assertThat(request.getUrl()).isEqualTo("http://example.com/foo/bar/");
assertThat(request.getBody()).isNull();
}

@Test public void nullHeaderParamRemovesMethodHeader() throws Exception {
Request request = new Helper() //
.setMethod("GET") //
.setUrl("http://example.com") //
.setPath("/foo/bar/") //
.addHeader("ping", "pong") //
.addMethodHeader("kit", "kat") //
.addHeaderParam("kit", null) //
.build();
assertThat(request.getMethod()).isEqualTo("GET");
assertThat(request.getHeaders()) //
.containsExactly(new HeaderPair("ping", "pong"));
assertThat(request.getUrl()).isEqualTo("http://example.com/foo/bar/");
assertThat(request.getBody()).isNull();
}

@Test public void noDuplicateSlashes() throws Exception {
Request request = new Helper() //
.setMethod("GET") //
Expand All @@ -274,9 +333,11 @@ private static class Helper {
private String path;
private Set<String> pathParams;
private final List<QueryParam> queryParams = new ArrayList<QueryParam>();
private final List<String> headerParams = new ArrayList<String>();
private final List<String> namedParams = new ArrayList<String>();
private final List<Object> args = new ArrayList<Object>();
private final List<HeaderPair> headers = new ArrayList<HeaderPair>();
private final List<HeaderPair> methodHeaders = new ArrayList<HeaderPair>();
private int singleEntityArgumentIndex = NO_SINGLE_ENTITY;
private String url;

Expand Down Expand Up @@ -318,6 +379,15 @@ Helper addNamedParam(String name, Object value) {
return this;
}

Helper addHeaderParam(String name, Object value) {
if (name == null) {
throw new IllegalArgumentException("Name can not be null.");
}
headerParams.add(name);
args.add(value);
return this;
}

Helper addSingleEntityParam(Object value) {
if (singleEntityArgumentIndex != NO_SINGLE_ENTITY) {
throw new IllegalStateException("Single entity param already added.");
Expand All @@ -334,6 +404,11 @@ Helper addHeader(String name, String value) {
return this;
}

Helper addMethodHeader(String name, String value) {
methodHeaders.add(new HeaderPair(name, value));
return this;
}

Helper setMultipart() {
isMultipart = true;
return this;
Expand Down Expand Up @@ -370,6 +445,8 @@ Request build() throws NoSuchMethodException, URISyntaxException {
methodInfo.path = path;
methodInfo.pathParams = pathParams;
methodInfo.pathQueryParams = queryParams.toArray(new QueryParam[queryParams.size()]);
methodInfo.headers = methodHeaders;
methodInfo.headerParams = headerParams.toArray(new String[headerParams.size()]);
methodInfo.namedParams = namedParams.toArray(new String[namedParams.size()]);
methodInfo.singleEntityArgumentIndex = singleEntityArgumentIndex;
methodInfo.isMultipart = isMultipart;
Expand Down
Loading

0 comments on commit 39f0341

Please sign in to comment.