diff --git a/mock/src/main/java/feign/mock/MockClient.java b/mock/src/main/java/feign/mock/MockClient.java index cefebbd3d5..822387690c 100644 --- a/mock/src/main/java/feign/mock/MockClient.java +++ b/mock/src/main/java/feign/mock/MockClient.java @@ -19,7 +19,6 @@ import java.net.HttpURLConnection; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; @@ -45,8 +44,6 @@ public RequestResponse(RequestKey requestKey, Response.Builder responseBuilder) } - public static final Map> EMPTY_HEADERS = Collections.emptyMap(); - private final List responses = new ArrayList(); private final Map> requests = new HashMap>(); @@ -196,7 +193,7 @@ public MockClient add(RequestKey requestKey, int status, String responseBody) { public MockClient add(RequestKey requestKey, int status, byte[] responseBody) { return add(requestKey, - Response.builder().status(status).reason("Mocked").headers(EMPTY_HEADERS) + Response.builder().status(status).reason("Mocked").headers(RequestHeaders.EMPTY) .body(responseBody)); } @@ -255,7 +252,7 @@ public void verifyNever(HttpMethod method, String url) { /** * To be called in an @After method: - * + * *
    * @After
    * public void tearDown() {
@@ -276,4 +273,5 @@ public void resetRequests() {
     requests.clear();
   }
 
+
 }
diff --git a/mock/src/main/java/feign/mock/RequestHeaders.java b/mock/src/main/java/feign/mock/RequestHeaders.java
new file mode 100644
index 0000000000..b019ba0514
--- /dev/null
+++ b/mock/src/main/java/feign/mock/RequestHeaders.java
@@ -0,0 +1,121 @@
+/**
+ * Copyright 2012-2018 The Feign Authors
+ *
+ * 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 feign.mock;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+public class RequestHeaders {
+
+  public static class Builder {
+
+    private Map> headers = new HashMap>();
+
+    private Builder() {}
+
+    public Builder add(String key, Collection values) {
+      if (!headers.containsKey(key)) {
+        headers.put(key, values);
+      } else {
+        Collection previousValues = headers.get(key);
+        previousValues.addAll(values);
+        headers.put(key, previousValues);
+      }
+      return this;
+    }
+
+    public Builder add(String key, String value) {
+      if (!headers.containsKey(key)) {
+        headers.put(key, new ArrayList(Arrays.asList(value)));
+      } else {
+        final Collection values = headers.get(key);
+        values.add(value);
+        headers.put(key, values);
+      }
+      return this;
+    }
+
+    public RequestHeaders build() {
+      return new RequestHeaders(this);
+    }
+
+  }
+
+  public static final Map> EMPTY = Collections.emptyMap();
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static RequestHeaders of(Map> headers) {
+    return new RequestHeaders(headers);
+  }
+
+  private Map> headers;
+
+  private RequestHeaders(Builder builder) {
+    this.headers = builder.headers;
+  }
+
+  private RequestHeaders(Map> headers) {
+    this.headers = headers;
+  }
+
+  public int size() {
+    return headers.size();
+  }
+
+  public int sizeOf(String key) {
+    if (!headers.containsKey(key)) {
+      return 0;
+    }
+    return headers.get(key).size();
+  }
+
+  public Collection fetch(String key) {
+    return headers.get(key);
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (obj == null) {
+      return false;
+    }
+    if (getClass() != obj.getClass()) {
+      return false;
+    }
+    final RequestHeaders other = (RequestHeaders) obj;
+    return this.headers.equals(other.headers);
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder builder = new StringBuilder();
+    for (Map.Entry> entry : headers.entrySet()) {
+      builder.append(entry).append(',').append(' ');
+    }
+    if (builder.length() > 0) {
+      return builder.substring(0, builder.length() - 2);
+    }
+    return "no";
+  }
+
+}
diff --git a/mock/src/main/java/feign/mock/RequestKey.java b/mock/src/main/java/feign/mock/RequestKey.java
index 3414b5122a..149eecfdf5 100644
--- a/mock/src/main/java/feign/mock/RequestKey.java
+++ b/mock/src/main/java/feign/mock/RequestKey.java
@@ -31,7 +31,7 @@ public static class Builder {
 
     private final String url;
 
-    private Map> headers;
+    private RequestHeaders headers;
 
     private Charset charset;
 
@@ -42,7 +42,13 @@ private Builder(HttpMethod method, String url) {
       this.url = url;
     }
 
+    @Deprecated
     public Builder headers(Map> headers) {
+      this.headers = RequestHeaders.of(headers);
+      return this;
+    }
+
+    public Builder headers(RequestHeaders headers) {
       this.headers = headers;
       return this;
     }
@@ -87,7 +93,7 @@ private static String buildUrl(Request request) {
 
   private final String url;
 
-  private final Map> headers;
+  private final RequestHeaders headers;
 
   private final Charset charset;
 
@@ -104,7 +110,7 @@ private RequestKey(Builder builder) {
   private RequestKey(Request request) {
     this.method = HttpMethod.valueOf(request.method());
     this.url = buildUrl(request);
-    this.headers = request.headers();
+    this.headers = RequestHeaders.of(request.headers());
     this.charset = request.charset();
     this.body = request.body();
   }
@@ -117,7 +123,7 @@ public String getUrl() {
     return url;
   }
 
-  public Map> getHeaders() {
+  public RequestHeaders getHeaders() {
     return headers;
   }
 
@@ -140,21 +146,23 @@ public int hashCode() {
 
   @Override
   public boolean equals(Object obj) {
-    if (this == obj)
+    if (this == obj) {
       return true;
-    if (obj == null)
+    }
+    if (obj == null) {
       return false;
-    if (getClass() != obj.getClass())
+    }
+    if (getClass() != obj.getClass()) {
       return false;
+    }
     final RequestKey other = (RequestKey) obj;
-    if (method != other.method)
+    if (method != other.method) {
       return false;
+    }
     if (url == null) {
-      if (other.url != null)
-        return false;
-    } else if (!url.equals(other.url))
-      return false;
-    return true;
+      return other.url == null;
+    } else
+      return url.equals(other.url);
   }
 
   public boolean equalsExtended(Object obj) {
@@ -173,7 +181,7 @@ public boolean equalsExtended(Object obj) {
   @Override
   public String toString() {
     return String.format("Request [%s %s: %s headers and %s]", method, url,
-        headers == null ? "without" : "with " + headers.size(),
+        headers == null ? "without" : "with " + headers,
         charset == null ? "no charset" : "charset " + charset);
   }
 
diff --git a/mock/src/test/java/feign/mock/MockClientSequentialTest.java b/mock/src/test/java/feign/mock/MockClientSequentialTest.java
index f8c0b243b4..6bcbf85f24 100644
--- a/mock/src/test/java/feign/mock/MockClientSequentialTest.java
+++ b/mock/src/test/java/feign/mock/MockClientSequentialTest.java
@@ -31,6 +31,7 @@
 import feign.Body;
 import feign.Feign;
 import feign.FeignException;
+import feign.Headers;
 import feign.Param;
 import feign.RequestLine;
 import feign.Response;
@@ -42,6 +43,7 @@ public class MockClientSequentialTest {
 
   interface GitHub {
 
+    @Headers({"Name: {owner}"})
     @RequestLine("GET /repos/{owner}/{repo}/contributors")
     List contributors(@Param("owner") String owner, @Param("repo") String repo);
 
@@ -89,26 +91,29 @@ public Object decode(Response response, Type type)
   }
 
   private GitHub githubSequential;
-
   private MockClient mockClientSequential;
 
   @Before
   public void setup() throws IOException {
     try (InputStream input = getClass().getResourceAsStream("/fixtures/contributors.json")) {
       byte[] data = toByteArray(input);
-
+      RequestHeaders headers = RequestHeaders
+          .builder()
+          .add("Name", "netflix")
+          .build();
       mockClientSequential = new MockClient(true);
       githubSequential = Feign.builder().decoder(new AssertionDecoder(new GsonDecoder()))
           .client(mockClientSequential
-              .add(HttpMethod.GET, "/repos/netflix/feign/contributors", HttpsURLConnection.HTTP_OK,
-                  data)
+              .add(RequestKey
+                  .builder(HttpMethod.GET, "/repos/netflix/feign/contributors")
+                  .headers(headers).build(), HttpsURLConnection.HTTP_OK, data)
               .add(HttpMethod.GET, "/repos/netflix/feign/contributors?client_id=55",
                   HttpsURLConnection.HTTP_NOT_FOUND)
               .add(HttpMethod.GET, "/repos/netflix/feign/contributors?client_id=7 7",
                   HttpsURLConnection.HTTP_INTERNAL_ERROR, new ByteArrayInputStream(data))
               .add(HttpMethod.GET, "/repos/netflix/feign/contributors",
                   Response.builder().status(HttpsURLConnection.HTTP_OK)
-                      .headers(MockClient.EMPTY_HEADERS).body(data)))
+                      .headers(RequestHeaders.EMPTY).body(data)))
           .target(new MockTarget<>(GitHub.class));
     }
   }
diff --git a/mock/src/test/java/feign/mock/MockClientTest.java b/mock/src/test/java/feign/mock/MockClientTest.java
index 511df69467..f85e20339c 100644
--- a/mock/src/test/java/feign/mock/MockClientTest.java
+++ b/mock/src/test/java/feign/mock/MockClientTest.java
@@ -91,7 +91,6 @@ public Object decode(Response response, Type type)
   }
 
   private GitHub github;
-
   private MockClient mockClient;
 
   @Before
diff --git a/mock/src/test/java/feign/mock/MockTargetTest.java b/mock/src/test/java/feign/mock/MockTargetTest.java
index 72dfe0869a..070362cd28 100644
--- a/mock/src/test/java/feign/mock/MockTargetTest.java
+++ b/mock/src/test/java/feign/mock/MockTargetTest.java
@@ -14,7 +14,7 @@
 package feign.mock;
 
 import static org.hamcrest.Matchers.equalTo;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertThat;
 import org.junit.Before;
 import org.junit.Test;
 
diff --git a/mock/src/test/java/feign/mock/RequestHeadersTest.java b/mock/src/test/java/feign/mock/RequestHeadersTest.java
new file mode 100644
index 0000000000..0b09f5a903
--- /dev/null
+++ b/mock/src/test/java/feign/mock/RequestHeadersTest.java
@@ -0,0 +1,88 @@
+/**
+ * Copyright 2012-2018 The Feign Authors
+ *
+ * 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 feign.mock;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.Test;
+
+public class RequestHeadersTest {
+
+  @Test
+  public void shouldCreateEmptyRequestHeaders() {
+    RequestHeaders headers = RequestHeaders
+        .builder()
+        .build();
+    assertThat(headers.size()).isEqualTo(0);
+  }
+
+  @Test
+  public void shouldReturnZeroSizeForUnknownKey() {
+    RequestHeaders headers = RequestHeaders
+        .builder()
+        .build();
+    assertThat(headers.sizeOf("unknown")).isEqualTo(0);
+  }
+
+  @Test
+  public void shouldCreateRequestHeadersFromSingleValue() {
+    RequestHeaders headers = RequestHeaders
+        .builder()
+        .add("header", "val")
+        .add("other header", "val2")
+        .build();
+
+    assertThat(headers.fetch("header")).contains("val");
+    assertThat(headers.sizeOf("header")).isEqualTo(1);
+    assertThat(headers.fetch("other header")).contains("val2");
+    assertThat(headers.sizeOf("other header")).isEqualTo(1);
+  }
+
+  @Test
+  public void shouldCreateRequestHeadersFromSingleValueAndCollection() {
+    RequestHeaders headers = RequestHeaders
+        .builder()
+        .add("header", "val")
+        .add("other header", "val2")
+        .add("header", Arrays.asList("val3", "val4"))
+        .build();
+
+    assertThat(headers.fetch("header")).contains("val", "val3", "val4");
+    assertThat(headers.sizeOf("header")).isEqualTo(3);
+    assertThat(headers.fetch("other header")).contains("val2");
+    assertThat(headers.sizeOf("other header")).isEqualTo(1);
+  }
+
+  @Test
+  public void shouldCreateRequestHeadersFromHeadersMap() {
+    Map> map = new HashMap>();
+    map.put("header", Arrays.asList("val", "val2"));
+    RequestHeaders headers = RequestHeaders.of(map);
+    assertThat(headers.size()).isEqualTo(1);
+  }
+
+  @Test
+  public void shouldPrintHeaders() {
+    RequestHeaders headers = RequestHeaders
+        .builder()
+        .add("header", "val")
+        .add("other header", "val2")
+        .add("header", Arrays.asList("val3", "val4"))
+        .build();
+    assertThat(headers.toString()).isEqualTo("other header=[val2], header=[val, val3, val4]");
+  }
+}
diff --git a/mock/src/test/java/feign/mock/RequestKeyTest.java b/mock/src/test/java/feign/mock/RequestKeyTest.java
index 00ea7e7edf..b9b3b9b21a 100644
--- a/mock/src/test/java/feign/mock/RequestKeyTest.java
+++ b/mock/src/test/java/feign/mock/RequestKeyTest.java
@@ -16,7 +16,7 @@
 import static org.hamcrest.Matchers.both;
 import static org.hamcrest.Matchers.containsString;
 import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.not;
 import static org.hamcrest.Matchers.startsWith;
 import static org.junit.Assert.assertThat;
@@ -35,10 +35,11 @@ public class RequestKeyTest {
 
   @Before
   public void setUp() {
-    Map> map = new HashMap<>();
-    map.put("my-header", Arrays.asList("val"));
+    RequestHeaders headers = RequestHeaders
+        .builder()
+        .add("my-header", "val").build();
     requestKey =
-        RequestKey.builder(HttpMethod.GET, "a").headers(map).charset(StandardCharsets.UTF_16)
+        RequestKey.builder(HttpMethod.GET, "a").headers(headers).charset(StandardCharsets.UTF_16)
             .body("content").build();
   }
 
@@ -46,15 +47,15 @@ public void setUp() {
   public void builder() throws Exception {
     assertThat(requestKey.getMethod(), equalTo(HttpMethod.GET));
     assertThat(requestKey.getUrl(), equalTo("a"));
-    assertThat(requestKey.getHeaders().entrySet(), hasSize(1));
-    assertThat(requestKey.getHeaders().get("my-header"),
+    assertThat(requestKey.getHeaders().size(), is(1));
+    assertThat(requestKey.getHeaders().fetch("my-header"),
         equalTo((Collection) Arrays.asList("val")));
     assertThat(requestKey.getCharset(), equalTo(StandardCharsets.UTF_16));
   }
 
   @Test
   public void create() throws Exception {
-    Map> map = new HashMap<>();
+    Map> map = new HashMap>();
     map.put("my-header", Arrays.asList("val"));
     Request request = Request.create("GET", "a", map, "content".getBytes(StandardCharsets.UTF_8),
         StandardCharsets.UTF_16);
@@ -62,8 +63,8 @@ public void create() throws Exception {
 
     assertThat(requestKey.getMethod(), equalTo(HttpMethod.GET));
     assertThat(requestKey.getUrl(), equalTo("a"));
-    assertThat(requestKey.getHeaders().entrySet(), hasSize(1));
-    assertThat(requestKey.getHeaders().get("my-header"),
+    assertThat(requestKey.getHeaders().size(), is(1));
+    assertThat(requestKey.getHeaders().fetch("my-header"),
         equalTo((Collection) Arrays.asList("val")));
     assertThat(requestKey.getCharset(), equalTo(StandardCharsets.UTF_16));
     assertThat(requestKey.getBody(), equalTo("content".getBytes(StandardCharsets.UTF_8)));
@@ -113,9 +114,10 @@ public void equalMinimum() {
 
   @Test
   public void equalExtra() {
-    Map> map = new HashMap<>();
-    map.put("my-other-header", Arrays.asList("other value"));
-    RequestKey requestKey2 = RequestKey.builder(HttpMethod.GET, "a").headers(map)
+    RequestHeaders headers = RequestHeaders
+        .builder()
+        .add("my-other-header", "other value").build();
+    RequestKey requestKey2 = RequestKey.builder(HttpMethod.GET, "a").headers(headers)
         .charset(StandardCharsets.ISO_8859_1).build();
 
     assertThat(requestKey.hashCode(), equalTo(requestKey2.hashCode()));
@@ -132,9 +134,10 @@ public void equalsExtended() {
 
   @Test
   public void equalsExtendedExtra() {
-    Map> map = new HashMap<>();
-    map.put("my-other-header", Arrays.asList("other value"));
-    RequestKey requestKey2 = RequestKey.builder(HttpMethod.GET, "a").headers(map)
+    RequestHeaders headers = RequestHeaders
+        .builder()
+        .add("my-other-header", "other value").build();
+    RequestKey requestKey2 = RequestKey.builder(HttpMethod.GET, "a").headers(headers)
         .charset(StandardCharsets.ISO_8859_1).build();
 
     assertThat(requestKey.hashCode(), equalTo(requestKey2.hashCode()));
@@ -145,7 +148,7 @@ public void equalsExtendedExtra() {
   public void testToString() throws Exception {
     assertThat(requestKey.toString(), startsWith("Request [GET a: "));
     assertThat(requestKey.toString(),
-        both(containsString(" with 1 ")).and(containsString(" UTF-16]")));
+        both(containsString(" with my-header=[val] ")).and(containsString(" UTF-16]")));
   }
 
   @Test