{
+
+ private final String name;
+
+ private boolean optional;
+
+ /**
+ * Creates a new {@code HeaderDescriptor} describing the header with the given
+ * {@code name}.
+ * @param name the name
+ * @see HttpHeaders
+ */
+ protected HeaderDescriptor(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Marks the header as optional.
+ *
+ * @return {@code this}
+ */
+ public final HeaderDescriptor optional() {
+ this.optional = true;
+ return this;
+ }
+
+ /**
+ * Returns the name for the header.
+ *
+ * @return the header name
+ */
+ public final String getName() {
+ return this.name;
+ }
+
+ /**
+ * Returns {@code true} if the described header is optional, otherwise {@code false}.
+ *
+ * @return {@code true} if the described header is optional, otherwise {@code false}
+ */
+ public final boolean isOptional() {
+ return this.optional;
+ }
+
+}
diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/headers/HeaderDocumentation.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/headers/HeaderDocumentation.java
new file mode 100644
index 00000000..85800ecf
--- /dev/null
+++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/headers/HeaderDocumentation.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2014-2015 the original author or 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 org.springframework.restdocs.headers;
+
+import java.util.Arrays;
+import java.util.Map;
+
+import org.springframework.restdocs.snippet.Snippet;
+
+/**
+ * Static factory methods for documenting a RESTful API's request and response headers.
+ *
+ * @author Andreas Evers
+ * @author Andy Wilkinson
+ */
+public abstract class HeaderDocumentation {
+
+ private HeaderDocumentation() {
+
+ }
+
+ /**
+ * Creates a {@code HeaderDescriptor} that describes a header with the given
+ * {@code name}.
+ *
+ * @param name The name of the header
+ * @return a {@code HeaderDescriptor} ready for further configuration
+ */
+ public static HeaderDescriptor headerWithName(String name) {
+ return new HeaderDescriptor(name);
+ }
+
+ /**
+ * Returns a new {@link Snippet} that will document the headers of the API operation's
+ * request. The headers will be documented using the given {@code descriptors}.
+ *
+ * If a header is documented, is not marked as optional, and is not present in the
+ * request, a failure will occur.
+ *
+ * @param descriptors the descriptions of the request's headers
+ * @return the snippet that will document the request headers
+ * @see #headerWithName(String)
+ */
+ public static Snippet requestHeaders(HeaderDescriptor... descriptors) {
+ return new RequestHeadersSnippet(Arrays.asList(descriptors));
+ }
+
+ /**
+ * Returns a new {@link Snippet} that will document the headers of the API
+ * operations's request. The given {@code attributes} will be available during snippet
+ * generation and the headers will be documented using the given {@code descriptors}.
+ *
+ * If a header is documented, is not marked as optional, and is not present in the
+ * request, a failure will occur.
+ *
+ * @param attributes the attributes
+ * @param descriptors the descriptions of the request's headers
+ * @return the snippet that will document the request headers
+ * @see #headerWithName(String)
+ */
+ public static Snippet requestHeaders(Map attributes,
+ HeaderDescriptor... descriptors) {
+ return new RequestHeadersSnippet(Arrays.asList(descriptors), attributes);
+ }
+
+ /**
+ * Returns a new {@link Snippet} that will document the headers of the API operation's
+ * response. The headers will be documented using the given {@code descriptors}.
+ *
+ * If a header is documented, is not marked as optional, and is not present in the
+ * request, a failure will occur.
+ *
+ * @param descriptors the descriptions of the response's headers
+ * @return the snippet that will document the response headers
+ * @see #headerWithName(String)
+ */
+ public static Snippet responseHeaders(HeaderDescriptor... descriptors) {
+ return new ResponseHeadersSnippet(Arrays.asList(descriptors));
+ }
+
+ /**
+ * Returns a new {@link Snippet} that will document the headers of the API
+ * operations's response. The given {@code attributes} will be available during
+ * snippet generation and the headers will be documented using the given
+ * {@code descriptors}.
+ *
+ * If a header is documented, is not marked as optional, and is not present in the
+ * response, a failure will occur.
+ *
+ * @param attributes the attributes
+ * @param descriptors the descriptions of the response's headers
+ * @return the snippet that will document the response headers
+ * @see #headerWithName(String)
+ */
+ public static Snippet responseHeaders(Map attributes,
+ HeaderDescriptor... descriptors) {
+ return new ResponseHeadersSnippet(Arrays.asList(descriptors), attributes);
+ }
+
+}
diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/headers/RequestHeadersSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/headers/RequestHeadersSnippet.java
new file mode 100644
index 00000000..83dd6b6d
--- /dev/null
+++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/headers/RequestHeadersSnippet.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2014-2015 the original author or 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 org.springframework.restdocs.headers;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.restdocs.operation.Operation;
+import org.springframework.restdocs.snippet.Snippet;
+
+/**
+ * A {@link Snippet} that documents the headers in a request.
+ *
+ * @author Andreas Evers
+ * @see HeaderDocumentation#requestHeaders(HeaderDescriptor...)
+ * @see HeaderDocumentation#requestHeaders(Map, HeaderDescriptor...)
+ */
+public class RequestHeadersSnippet extends AbstractHeadersSnippet {
+
+ /**
+ * Creates a new {@code RequestHeadersSnippet} that will document the headers in the
+ * request using the given {@code descriptors}.
+ *
+ * @param descriptors the descriptors
+ */
+ protected RequestHeadersSnippet(List descriptors) {
+ this(descriptors, null);
+ }
+
+ /**
+ * Creates a new {@code RequestHeadersSnippet} that will document the headers in the
+ * request using the given {@code descriptors}. The given {@code attributes} will be
+ * included in the model during template rendering.
+ *
+ * @param descriptors the descriptors
+ * @param attributes the additional attributes
+ */
+ protected RequestHeadersSnippet(List descriptors,
+ Map attributes) {
+ super("request", descriptors, attributes);
+ }
+
+ @Override
+ protected Set extractActualHeaders(Operation operation) {
+ return operation.getRequest().getHeaders().keySet();
+ }
+
+}
diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/headers/ResponseHeadersSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/headers/ResponseHeadersSnippet.java
new file mode 100644
index 00000000..979083c4
--- /dev/null
+++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/headers/ResponseHeadersSnippet.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2014-2015 the original author or 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 org.springframework.restdocs.headers;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.restdocs.operation.Operation;
+import org.springframework.restdocs.snippet.Snippet;
+
+/**
+ * A {@link Snippet} that documents the headers in a response.
+ *
+ * @author Andreas Evers
+ * @see HeaderDocumentation#responseHeaders(HeaderDescriptor...)
+ * @see HeaderDocumentation#responseHeaders(Map, HeaderDescriptor...)
+ */
+public class ResponseHeadersSnippet extends AbstractHeadersSnippet {
+
+ /**
+ * Creates a new {@code ResponseHeadersSnippet} that will document the headers in the
+ * response using the given {@code descriptors}.
+ *
+ * @param descriptors the descriptors
+ */
+ protected ResponseHeadersSnippet(List descriptors) {
+ this(descriptors, null);
+ }
+
+ /**
+ * Creates a new {@code ResponseHeadersSnippet} that will document the headers in the
+ * response using the given {@code descriptors}. The given {@code attributes} will be
+ * included in the model during template rendering.
+ *
+ * @param descriptors the descriptors
+ * @param attributes the additional attributes
+ */
+ protected ResponseHeadersSnippet(List descriptors,
+ Map attributes) {
+ super("response", descriptors, attributes);
+ }
+
+ @Override
+ protected Set extractActualHeaders(Operation operation) {
+ return operation.getResponse().getHeaders().keySet();
+ }
+
+}
diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/headers/package-info.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/headers/package-info.java
new file mode 100644
index 00000000..f27ba176
--- /dev/null
+++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/headers/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2014-2015 the original author or 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.
+ */
+
+/**
+ * Documenting the headers of a RESTful API's requests and responses.
+ */
+package org.springframework.restdocs.headers;
diff --git a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/default-request-headers.snippet b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/default-request-headers.snippet
new file mode 100644
index 00000000..5f14875d
--- /dev/null
+++ b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/default-request-headers.snippet
@@ -0,0 +1,9 @@
+|===
+|Name|Description
+
+{{#headers}}
+|{{name}}
+|{{description}}
+
+{{/headers}}
+|===
\ No newline at end of file
diff --git a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/default-response-headers.snippet b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/default-response-headers.snippet
new file mode 100644
index 00000000..5f14875d
--- /dev/null
+++ b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/default-response-headers.snippet
@@ -0,0 +1,9 @@
+|===
+|Name|Description
+
+{{#headers}}
+|{{name}}
+|{{description}}
+
+{{/headers}}
+|===
\ No newline at end of file
diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/headers/RequestHeadersSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/headers/RequestHeadersSnippetTests.java
new file mode 100644
index 00000000..b5b2d1e4
--- /dev/null
+++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/headers/RequestHeadersSnippetTests.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2014-2015 the original author or 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 org.springframework.restdocs.headers;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.restdocs.snippet.SnippetException;
+import org.springframework.restdocs.templates.TemplateEngine;
+import org.springframework.restdocs.templates.TemplateResourceResolver;
+import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
+import org.springframework.restdocs.test.ExpectedSnippet;
+import org.springframework.restdocs.test.OperationBuilder;
+
+import static org.hamcrest.CoreMatchers.endsWith;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.mock;
+import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
+import static org.springframework.restdocs.snippet.Attributes.attributes;
+import static org.springframework.restdocs.snippet.Attributes.key;
+import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader;
+
+/**
+ * Tests for {@link RequestHeadersSnippet}.
+ *
+ * @author Andreas Evers
+ * @author Andy Wilkinson
+ */
+public class RequestHeadersSnippetTests {
+
+ @Rule
+ public final ExpectedException thrown = ExpectedException.none();
+
+ @Rule
+ public final ExpectedSnippet snippet = new ExpectedSnippet();
+
+ @Test
+ public void requestWithHeaders() throws IOException {
+ this.snippet.expectRequestHeaders("request-with-headers").withContents(
+ tableWithHeader("Name", "Description").row("X-Test", "one")
+ .row("Accept", "two").row("Accept-Encoding", "three")
+ .row("Accept-Language", "four").row("Cache-Control", "five")
+ .row("Connection", "six"));
+ new RequestHeadersSnippet(Arrays.asList(
+ headerWithName("X-Test").description("one"), headerWithName("Accept")
+ .description("two"), headerWithName("Accept-Encoding")
+ .description("three"), headerWithName("Accept-Language")
+ .description("four"), headerWithName("Cache-Control")
+ .description("five"),
+ headerWithName("Connection").description("six")))
+ .document(new OperationBuilder("request-with-headers", this.snippet
+ .getOutputDirectory()).request("http://localhost")
+ .header("X-Test", "test").header("Accept", "*/*")
+ .header("Accept-Encoding", "gzip, deflate")
+ .header("Accept-Language", "en-US,en;q=0.5")
+ .header("Cache-Control", "max-age=0")
+ .header("Connection", "keep-alive").build());
+ }
+
+ @Test
+ public void caseInsensitiveRequestHeaders() throws IOException {
+ this.snippet
+ .expectRequestHeaders("case-insensitive-request-headers")
+ .withContents(tableWithHeader("Name", "Description").row("X-Test", "one"));
+ new RequestHeadersSnippet(Arrays.asList(headerWithName("X-Test").description(
+ "one"))).document(new OperationBuilder(
+ "case-insensitive-request-headers", this.snippet.getOutputDirectory())
+ .request("/").header("X-test", "test").build());
+ }
+
+ @Test
+ public void undocumentedRequestHeader() throws IOException {
+ new RequestHeadersSnippet(Arrays.asList(headerWithName("X-Test").description(
+ "one"))).document(new OperationBuilder("undocumented-request-header",
+ this.snippet.getOutputDirectory()).request("http://localhost")
+ .header("X-Test", "test").header("Accept", "*/*").build());
+ }
+
+ @Test
+ public void missingRequestHeader() throws IOException {
+ this.thrown.expect(SnippetException.class);
+ this.thrown
+ .expectMessage(equalTo("Headers with the following names were not found"
+ + " in the request: [Accept]"));
+ new RequestHeadersSnippet(Arrays.asList(headerWithName("Accept").description(
+ "one"))).document(new OperationBuilder("missing-request-headers",
+ this.snippet.getOutputDirectory()).request("http://localhost").build());
+ }
+
+ @Test
+ public void undocumentedRequestHeaderAndMissingRequestHeader() throws IOException {
+ this.thrown.expect(SnippetException.class);
+ this.thrown
+ .expectMessage(endsWith("Headers with the following names were not found"
+ + " in the request: [Accept]"));
+ new RequestHeadersSnippet(Arrays.asList(headerWithName("Accept").description(
+ "one"))).document(new OperationBuilder(
+ "undocumented-request-header-and-missing-request-header", this.snippet
+ .getOutputDirectory()).request("http://localhost")
+ .header("X-Test", "test").build());
+ }
+
+ @Test
+ public void requestHeadersWithCustomDescriptorAttributes() throws IOException {
+ this.snippet.expectRequestHeaders("request-headers-with-custom-attributes")
+ .withContents(//
+ tableWithHeader("Name", "Description", "Foo")
+ .row("X-Test", "one", "alpha")
+ .row("Accept-Encoding", "two", "bravo")
+ .row("Accept", "three", "charlie"));
+ TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
+ given(resolver.resolveTemplateResource("request-headers")).willReturn(
+ snippetResource("request-headers-with-extra-column"));
+ new RequestHeadersSnippet(Arrays.asList(
+ headerWithName("X-Test").description("one").attributes(
+ key("foo").value("alpha")),
+ headerWithName("Accept-Encoding").description("two").attributes(
+ key("foo").value("bravo")),
+ headerWithName("Accept").description("three").attributes(
+ key("foo").value("charlie")))).document(new OperationBuilder(
+ "request-headers-with-custom-attributes", this.snippet
+ .getOutputDirectory())
+ .attribute(TemplateEngine.class.getName(),
+ new MustacheTemplateEngine(resolver)).request("http://localhost")
+ .header("X-Test", "test").header("Accept-Encoding", "gzip, deflate")
+ .header("Accept", "*/*").build());
+ }
+
+ @Test
+ public void requestHeadersWithCustomAttributes() throws IOException {
+ this.snippet.expectRequestHeaders("request-headers-with-custom-attributes")
+ .withContents(startsWith(".Custom title"));
+ TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
+ given(resolver.resolveTemplateResource("request-headers")).willReturn(
+ snippetResource("request-headers-with-title"));
+ new RequestHeadersSnippet(Arrays.asList(headerWithName("X-Test").description(
+ "one")), attributes(key("title").value("Custom title")))
+ .document(new OperationBuilder("request-headers-with-custom-attributes",
+ this.snippet.getOutputDirectory())
+ .attribute(TemplateEngine.class.getName(),
+ new MustacheTemplateEngine(resolver))
+ .request("http://localhost").header("X-Test", "test").build());
+ }
+
+ private FileSystemResource snippetResource(String name) {
+ return new FileSystemResource("src/test/resources/custom-snippet-templates/"
+ + name + ".snippet");
+ }
+
+}
diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/headers/ResponseHeadersSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/headers/ResponseHeadersSnippetTests.java
new file mode 100644
index 00000000..24487094
--- /dev/null
+++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/headers/ResponseHeadersSnippetTests.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2014-2015 the original author or 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 org.springframework.restdocs.headers;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.restdocs.snippet.SnippetException;
+import org.springframework.restdocs.templates.TemplateEngine;
+import org.springframework.restdocs.templates.TemplateResourceResolver;
+import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
+import org.springframework.restdocs.test.ExpectedSnippet;
+import org.springframework.restdocs.test.OperationBuilder;
+
+import static org.hamcrest.CoreMatchers.endsWith;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.mock;
+import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
+import static org.springframework.restdocs.snippet.Attributes.attributes;
+import static org.springframework.restdocs.snippet.Attributes.key;
+import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader;
+
+/**
+ * Tests for {@link ResponseHeadersSnippet}.
+ *
+ * @author Andreas Evers
+ * @author Andy Wilkinson
+ */
+public class ResponseHeadersSnippetTests {
+
+ @Rule
+ public final ExpectedException thrown = ExpectedException.none();
+
+ @Rule
+ public final ExpectedSnippet snippet = new ExpectedSnippet();
+
+ @Test
+ public void responseWithHeaders() throws IOException {
+ this.snippet.expectResponseHeaders("response-headers").withContents(
+ tableWithHeader("Name", "Description").row("X-Test", "one")
+ .row("Content-Type", "two").row("Etag", "three")
+ .row("Cache-Control", "five").row("Vary", "six"));
+ new ResponseHeadersSnippet(Arrays.asList(
+ headerWithName("X-Test").description("one"),
+ headerWithName("Content-Type").description("two"), headerWithName("Etag")
+ .description("three"), headerWithName("Cache-Control")
+ .description("five"), headerWithName("Vary").description("six")))
+ .document(new OperationBuilder("response-headers", this.snippet
+ .getOutputDirectory()).response().header("X-Test", "test")
+ .header("Content-Type", "application/json")
+ .header("Etag", "lskjadldj3ii32l2ij23")
+ .header("Cache-Control", "max-age=0")
+ .header("Vary", "User-Agent").build());
+ }
+
+ @Test
+ public void caseInsensitiveResponseHeaders() throws IOException {
+ this.snippet
+ .expectResponseHeaders("case-insensitive-response-headers")
+ .withContents(tableWithHeader("Name", "Description").row("X-Test", "one"));
+ new ResponseHeadersSnippet(Arrays.asList(headerWithName("X-Test").description(
+ "one"))).document(new OperationBuilder(
+ "case-insensitive-response-headers", this.snippet.getOutputDirectory())
+ .response().header("X-test", "test").build());
+ }
+
+ @Test
+ public void responseHeadersWithCustomDescriptorAttributes() throws IOException {
+ this.snippet.expectResponseHeaders("response-headers-with-custom-attributes")
+ .withContents(
+ tableWithHeader("Name", "Description", "Foo")
+ .row("X-Test", "one", "alpha")
+ .row("Content-Type", "two", "bravo")
+ .row("Etag", "three", "charlie"));
+ TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
+ given(resolver.resolveTemplateResource("response-headers")).willReturn(
+ snippetResource("response-headers-with-extra-column"));
+ new ResponseHeadersSnippet(Arrays.asList(
+ headerWithName("X-Test").description("one").attributes(
+ key("foo").value("alpha")),
+ headerWithName("Content-Type").description("two").attributes(
+ key("foo").value("bravo")),
+ headerWithName("Etag").description("three").attributes(
+ key("foo").value("charlie")))).document(new OperationBuilder(
+ "response-headers-with-custom-attributes", this.snippet
+ .getOutputDirectory())
+ .attribute(TemplateEngine.class.getName(),
+ new MustacheTemplateEngine(resolver)).response()
+ .header("X-Test", "test").header("Content-Type", "application/json")
+ .header("Etag", "lskjadldj3ii32l2ij23").build());
+ }
+
+ @Test
+ public void responseHeadersWithCustomAttributes() throws IOException {
+ this.snippet.expectResponseHeaders("response-headers-with-custom-attributes")
+ .withContents(startsWith(".Custom title"));
+ TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
+ given(resolver.resolveTemplateResource("response-headers")).willReturn(
+ snippetResource("response-headers-with-title"));
+ new ResponseHeadersSnippet(Arrays.asList(headerWithName("X-Test").description(
+ "one")), attributes(key("title").value("Custom title")))
+ .document(new OperationBuilder("response-headers-with-custom-attributes",
+ this.snippet.getOutputDirectory())
+ .attribute(TemplateEngine.class.getName(),
+ new MustacheTemplateEngine(resolver)).response()
+ .header("X-Test", "test").build());
+ }
+
+ @Test
+ public void undocumentedResponseHeader() throws IOException {
+ new ResponseHeadersSnippet(Arrays.asList(headerWithName("X-Test").description(
+ "one"))).document(new OperationBuilder("undocumented-response-header",
+ this.snippet.getOutputDirectory()).response().header("X-Test", "test")
+ .header("Content-Type", "*/*").build());
+ }
+
+ @Test
+ public void missingResponseHeader() throws IOException {
+ this.thrown.expect(SnippetException.class);
+ this.thrown
+ .expectMessage(equalTo("Headers with the following names were not found"
+ + " in the response: [Content-Type]"));
+ new ResponseHeadersSnippet(Arrays.asList(headerWithName("Content-Type")
+ .description("one"))).document(new OperationBuilder(
+ "missing-response-headers", this.snippet.getOutputDirectory()).response()
+ .build());
+ }
+
+ @Test
+ public void undocumentedResponseHeaderAndMissingResponseHeader() throws IOException {
+ this.thrown.expect(SnippetException.class);
+ this.thrown
+ .expectMessage(endsWith("Headers with the following names were not found"
+ + " in the response: [Content-Type]"));
+ new ResponseHeadersSnippet(Arrays.asList(headerWithName("Content-Type")
+ .description("one"))).document(new OperationBuilder(
+ "undocumented-response-header-and-missing-response-header", this.snippet
+ .getOutputDirectory()).response().header("X-Test", "test")
+ .build());
+ }
+
+ private FileSystemResource snippetResource(String name) {
+ return new FileSystemResource("src/test/resources/custom-snippet-templates/"
+ + name + ".snippet");
+ }
+
+}
diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/test/ExpectedSnippet.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/test/ExpectedSnippet.java
index c20b0df0..63364f6a 100644
--- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/test/ExpectedSnippet.java
+++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/test/ExpectedSnippet.java
@@ -34,6 +34,7 @@ import static org.junit.Assert.assertThat;
* generated the expected snippet.
*
* @author Andy Wilkinson
+ * @author Andreas Evers
*/
public class ExpectedSnippet implements TestRule {
@@ -75,6 +76,16 @@ public class ExpectedSnippet implements TestRule {
return this;
}
+ public ExpectedSnippet expectRequestHeaders(String name) {
+ expect(name, "request-headers");
+ return this;
+ }
+
+ public ExpectedSnippet expectResponseHeaders(String name) {
+ expect(name, "response-headers");
+ return this;
+ }
+
public ExpectedSnippet expectLinks(String name) {
expect(name, "links");
return this;
diff --git a/spring-restdocs-core/src/test/resources/custom-snippet-templates/request-headers-with-extra-column.snippet b/spring-restdocs-core/src/test/resources/custom-snippet-templates/request-headers-with-extra-column.snippet
new file mode 100644
index 00000000..29d68777
--- /dev/null
+++ b/spring-restdocs-core/src/test/resources/custom-snippet-templates/request-headers-with-extra-column.snippet
@@ -0,0 +1,10 @@
+|===
+|Name|Description|Foo
+
+{{#headers}}
+|{{name}}
+|{{description}}
+|{{foo}}
+
+{{/headers}}
+|===
\ No newline at end of file
diff --git a/spring-restdocs-core/src/test/resources/custom-snippet-templates/request-headers-with-title.snippet b/spring-restdocs-core/src/test/resources/custom-snippet-templates/request-headers-with-title.snippet
new file mode 100644
index 00000000..998090a0
--- /dev/null
+++ b/spring-restdocs-core/src/test/resources/custom-snippet-templates/request-headers-with-title.snippet
@@ -0,0 +1,10 @@
+.{{title}}
+|===
+|Name|Description
+
+{{#headers}}
+|{{name}}
+|{{description}}
+
+{{/headers}}
+|===
\ No newline at end of file
diff --git a/spring-restdocs-core/src/test/resources/custom-snippet-templates/response-headers-with-extra-column.snippet b/spring-restdocs-core/src/test/resources/custom-snippet-templates/response-headers-with-extra-column.snippet
new file mode 100644
index 00000000..29d68777
--- /dev/null
+++ b/spring-restdocs-core/src/test/resources/custom-snippet-templates/response-headers-with-extra-column.snippet
@@ -0,0 +1,10 @@
+|===
+|Name|Description|Foo
+
+{{#headers}}
+|{{name}}
+|{{description}}
+|{{foo}}
+
+{{/headers}}
+|===
\ No newline at end of file
diff --git a/spring-restdocs-core/src/test/resources/custom-snippet-templates/response-headers-with-title.snippet b/spring-restdocs-core/src/test/resources/custom-snippet-templates/response-headers-with-title.snippet
new file mode 100644
index 00000000..998090a0
--- /dev/null
+++ b/spring-restdocs-core/src/test/resources/custom-snippet-templates/response-headers-with-title.snippet
@@ -0,0 +1,10 @@
+.{{title}}
+|===
+|Name|Description
+
+{{#headers}}
+|{{name}}
+|{{description}}
+
+{{/headers}}
+|===
\ No newline at end of file