();
+ this.delegates.add(documentCurlRequest(this.outputDir));
+ this.delegates.add(documentCurlResponse(this.outputDir));
+ this.delegates.add(documentCurlRequestAndResponse(this.outputDir));
+ }
+
+ @Override
+ public void handle(MvcResult result) throws Exception {
+ for (ResultHandler delegate : this.delegates) {
+ delegate.handle(result);
+ }
+ }
+
+ /**
+ * Document the links in the response using the given {@code descriptors}. The links
+ * are extracted from the response based on its content type.
+ *
+ * If a link is present in the response but is not described by one of the descriptors
+ * a failure will occur when this handler is invoked. Similarly, if a link is
+ * described but is not present in the response a failure will also occur when this
+ * handler is invoked.
+ *
+ * @param descriptors the link descriptors
+ * @return {@code this}
+ * @see HypermediaDocumentation#linkWithRel(String)
+ * @see LinkExtractors#extractorForContentType(String)
+ */
+ public RestDocumentationResultHandler withLinks(LinkDescriptor... descriptors) {
+ return withLinks(null, descriptors);
+ }
+
+ /**
+ * Document the links in the response using the given {@code descriptors}. The links
+ * are extracted from the response using the given {@code linkExtractor}.
+ *
+ * If a link is present in the response but is not described by one of the descriptors
+ * a failure will occur when this handler is invoked. Similarly, if a link is
+ * described but is not present in the response a failure will also occur when this
+ * handler is invoked.
+ *
+ * @param linkExtractor used to extract the links from the response
+ * @param descriptors the link descriptors
+ * @return {@code this}
+ * @see HypermediaDocumentation#linkWithRel(String)
+ */
+ public RestDocumentationResultHandler withLinks(LinkExtractor linkExtractor,
+ LinkDescriptor... descriptors) {
+ this.delegates.add(documentLinks(this.outputDir, linkExtractor, descriptors));
+ return this;
+ }
+
+}
diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/core/RestDocumentation.java b/spring-restdocs/src/main/java/org/springframework/restdocs/curl/CurlConfiguration.java
similarity index 60%
rename from spring-restdocs-core/src/main/java/org/springframework/restdocs/core/RestDocumentation.java
rename to spring-restdocs/src/main/java/org/springframework/restdocs/curl/CurlConfiguration.java
index 974e1dbb..c8c8c944 100644
--- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/core/RestDocumentation.java
+++ b/spring-restdocs/src/main/java/org/springframework/restdocs/curl/CurlConfiguration.java
@@ -14,17 +14,22 @@
* limitations under the License.
*/
-package org.springframework.restdocs.core;
+package org.springframework.restdocs.curl;
-public class RestDocumentation {
+/**
+ * Configuration for documenting Curl requests and responses
+ *
+ * @author Andy Wilkinson
+ */
+class CurlConfiguration {
- public static RestDocumentationResultHandler document(String outputDir)
- throws Exception {
- return new RestDocumentationResultHandler(outputDir);
+ private boolean includeResponseHeaders = true;
+
+ boolean isIncludeResponseHeaders() {
+ return this.includeResponseHeaders;
}
- public static LinkDescriptor linkWithRel(String rel) {
- return new LinkDescriptor(rel);
+ void setIncludeResponseHeaders(boolean includeResponseHeaders) {
+ this.includeResponseHeaders = includeResponseHeaders;
}
-
-}
+}
\ No newline at end of file
diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/curl/CurlDocumentation.java b/spring-restdocs/src/main/java/org/springframework/restdocs/curl/CurlDocumentation.java
new file mode 100644
index 00000000..82005876
--- /dev/null
+++ b/spring-restdocs/src/main/java/org/springframework/restdocs/curl/CurlDocumentation.java
@@ -0,0 +1,189 @@
+/*
+ * 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.curl;
+
+import static org.springframework.restdocs.util.IterableEnumeration.iterable;
+
+import java.io.IOException;
+import java.io.StringWriter;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.restdocs.snippet.DocumentationWriter;
+import org.springframework.restdocs.snippet.DocumentationWriter.DocumentationAction;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.util.FileCopyUtils;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+/**
+ * Static factory methods for documenting a RESTful API as if it were being driven using
+ * the cURL command-line utility.
+ *
+ * @author Andy Wilkinson
+ */
+public abstract class CurlDocumentation {
+
+ private CurlDocumentation() {
+
+ }
+
+ /**
+ * Produces a documentation snippet containing the request formatted as a cURL command
+ *
+ * @param outputDir The directory to which snippet should be written
+ * @return the handler that will produce the snippet
+ */
+ public static CurlSnippetResultHandler documentCurlRequest(String outputDir) {
+ return new CurlSnippetResultHandler(outputDir, "request") {
+
+ @Override
+ public void handle(MvcResult result, DocumentationWriter writer)
+ throws Exception {
+ writer.shellCommand(new CurlRequestDocumentationAction(writer, result,
+ getCurlConfiguration()));
+ }
+ };
+ }
+
+ /**
+ * Produces a documentation snippet containing the response formatted as the response
+ * to a cURL command
+ *
+ * @param outputDir The directory to which snippet should be written
+ * @return the handler that will produce the snippet
+ */
+ public static CurlSnippetResultHandler documentCurlResponse(String outputDir) {
+ return new CurlSnippetResultHandler(outputDir, "response") {
+
+ @Override
+ public void handle(MvcResult result, DocumentationWriter writer)
+ throws Exception {
+ writer.codeBlock("http", new CurlResponseDocumentationAction(writer,
+ result, getCurlConfiguration()));
+ }
+ };
+ }
+
+ /**
+ * Produces a documentation snippet containing both the request formatted as a cURL
+ * command and the response formatted formatted s the response to a cURL command.
+ *
+ * @param outputDir The directory to which the snippet should be written
+ * @return the handler that will produce the snippet
+ */
+ public static CurlSnippetResultHandler documentCurlRequestAndResponse(String outputDir) {
+ return new CurlSnippetResultHandler(outputDir, "request-response") {
+
+ @Override
+ public void handle(MvcResult result, DocumentationWriter writer)
+ throws Exception {
+ writer.shellCommand(new CurlRequestDocumentationAction(writer, result,
+ getCurlConfiguration()));
+ writer.codeBlock("http", new CurlResponseDocumentationAction(writer,
+ result, getCurlConfiguration()));
+ }
+ };
+ }
+
+ private static final class CurlRequestDocumentationAction implements
+ DocumentationAction {
+
+ private final DocumentationWriter writer;
+
+ private final MvcResult result;
+
+ private final CurlConfiguration curlConfiguration;
+
+ CurlRequestDocumentationAction(DocumentationWriter writer, MvcResult result,
+ CurlConfiguration curlConfiguration) {
+ this.writer = writer;
+ this.result = result;
+ this.curlConfiguration = curlConfiguration;
+ }
+
+ @Override
+ public void perform() throws Exception {
+ MockHttpServletRequest request = this.result.getRequest();
+ this.writer.print(String.format("curl %s://%s:%d%s", request.getScheme(),
+ request.getRemoteHost(), request.getRemotePort(),
+ request.getRequestURI()));
+
+ if (this.curlConfiguration.isIncludeResponseHeaders()) {
+ this.writer.print(" -i");
+ }
+
+ RequestMethod requestMethod = RequestMethod.valueOf(request.getMethod());
+ if (requestMethod != RequestMethod.GET) {
+ this.writer.print(String.format(" -X %s", requestMethod.toString()));
+ }
+
+ for (String headerName : iterable(request.getHeaderNames())) {
+ for (String header : iterable(request.getHeaders(headerName))) {
+ this.writer
+ .print(String.format(" -H \"%s: %s\"", headerName, header));
+ }
+ }
+
+ if (request.getContentLengthLong() > 0) {
+ this.writer.print(String.format(" -d '%s'", getContent(request)));
+ }
+
+ this.writer.println();
+ }
+
+ private String getContent(MockHttpServletRequest request) throws IOException {
+ StringWriter bodyWriter = new StringWriter();
+ FileCopyUtils.copy(request.getReader(), bodyWriter);
+ return bodyWriter.toString();
+ }
+ }
+
+ private static final class CurlResponseDocumentationAction implements
+ DocumentationAction {
+
+ private final DocumentationWriter writer;
+
+ private final MvcResult result;
+
+ private final CurlConfiguration curlConfiguration;
+
+ CurlResponseDocumentationAction(DocumentationWriter writer, MvcResult result,
+ CurlConfiguration curlConfiguration) {
+ this.writer = writer;
+ this.result = result;
+ this.curlConfiguration = curlConfiguration;
+ }
+
+ @Override
+ public void perform() throws Exception {
+ if (this.curlConfiguration.isIncludeResponseHeaders()) {
+ HttpStatus status = HttpStatus.valueOf(this.result.getResponse()
+ .getStatus());
+ this.writer.println(String.format("HTTP/1.1 %d %s", status.value(),
+ status.getReasonPhrase()));
+ for (String headerName : this.result.getResponse().getHeaderNames()) {
+ for (String header : this.result.getResponse().getHeaders(headerName)) {
+ this.writer.println(String.format("%s: %s", headerName, header));
+ }
+ }
+ this.writer.println();
+ }
+ this.writer.println(this.result.getResponse().getContentAsString());
+ }
+ }
+
+}
diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/curl/CurlSnippetResultHandler.java b/spring-restdocs/src/main/java/org/springframework/restdocs/curl/CurlSnippetResultHandler.java
new file mode 100644
index 00000000..31a687cf
--- /dev/null
+++ b/spring-restdocs/src/main/java/org/springframework/restdocs/curl/CurlSnippetResultHandler.java
@@ -0,0 +1,44 @@
+/*
+ * 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.curl;
+
+import org.springframework.restdocs.snippet.SnippetWritingResultHandler;
+import org.springframework.test.web.servlet.ResultHandler;
+
+/**
+ * Abstract base class for Spring Mock MVC {@link ResultHandler ResultHandlers} that
+ * produce documentation snippets relating to cURL.
+ *
+ * @author Andy Wilkinson
+ */
+public abstract class CurlSnippetResultHandler extends SnippetWritingResultHandler {
+
+ private final CurlConfiguration curlConfiguration = new CurlConfiguration();
+
+ public CurlSnippetResultHandler(String outputDir, String fileName) {
+ super(outputDir, fileName);
+ }
+
+ CurlConfiguration getCurlConfiguration() {
+ return this.curlConfiguration;
+ }
+
+ public CurlSnippetResultHandler includeResponseHeaders() {
+ this.curlConfiguration.setIncludeResponseHeaders(true);
+ return this;
+ }
+}
\ No newline at end of file
diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/HypermediaDocumentation.java b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/HypermediaDocumentation.java
new file mode 100644
index 00000000..65a51bd0
--- /dev/null
+++ b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/HypermediaDocumentation.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.hypermedia;
+
+import java.util.Arrays;
+
+import org.springframework.restdocs.RestDocumentationResultHandler;
+
+/**
+ * Static factory methods for documenting a RESTful API that utilises Hypermedia.
+ *
+ * @author Andy Wilkinson
+ */
+public abstract class HypermediaDocumentation {
+
+ private HypermediaDocumentation() {
+
+ }
+
+ /**
+ * Creates a {@code LinkDescriptor} that describes a link with the given {@code rel}.
+ *
+ * @param rel The rel of the link
+ * @return a {@code LinkDescriptor} ready for further configuration
+ * @see RestDocumentationResultHandler#withLinks(LinkDescriptor...)
+ * @see RestDocumentationResultHandler#withLinks(LinkExtractor, LinkDescriptor...)
+ */
+ public static LinkDescriptor linkWithRel(String rel) {
+ return new LinkDescriptor(rel);
+ }
+
+ /**
+ * Creates a {@code LinkSnippetResultHandler} that will produce a documentation
+ * snippet for a response's links.
+ *
+ * @param outputDir The directory to which the snippet should be written
+ * @param linkExtractor Used to extract the links from the response
+ * @param descriptors The descriptions of the response's links
+ * @return the handler
+ * @see RestDocumentationResultHandler#withLinks(LinkDescriptor...)
+ * @see RestDocumentationResultHandler#withLinks(LinkExtractor, LinkDescriptor...)
+ */
+ public static LinkSnippetResultHandler documentLinks(String outputDir,
+ LinkExtractor linkExtractor, LinkDescriptor... descriptors) {
+ return new LinkSnippetResultHandler(outputDir, linkExtractor,
+ Arrays.asList(descriptors));
+ }
+
+}
diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/core/Link.java b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/Link.java
similarity index 97%
rename from spring-restdocs-core/src/main/java/org/springframework/restdocs/core/Link.java
rename to spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/Link.java
index ef153487..694eec9e 100644
--- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/core/Link.java
+++ b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/Link.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.restdocs.core;
+package org.springframework.restdocs.hypermedia;
import org.springframework.core.style.ToStringCreator;
diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/core/LinkDescriptor.java b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/LinkDescriptor.java
similarity index 68%
rename from spring-restdocs-core/src/main/java/org/springframework/restdocs/core/LinkDescriptor.java
rename to spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/LinkDescriptor.java
index e28a8af4..24a2044c 100644
--- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/core/LinkDescriptor.java
+++ b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/LinkDescriptor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014 the original author or authors.
+ * 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.
@@ -14,18 +14,31 @@
* limitations under the License.
*/
-package org.springframework.restdocs.core;
+package org.springframework.restdocs.hypermedia;
+/**
+ * A description of a link found in a hypermedia API
+ *
+ * @see HypermediaDocumentation#linkWithRel(String)
+ *
+ * @author Andy Wilkinson
+ */
public class LinkDescriptor {
private final String rel;
private String description;
- public LinkDescriptor(String rel) {
+ LinkDescriptor(String rel) {
this.rel = rel;
}
+ /**
+ * Specifies the description of the link
+ *
+ * @param description The link's description
+ * @return {@code this}
+ */
public LinkDescriptor description(String description) {
this.description = description;
return this;
diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/core/LinkExtractor.java b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/LinkExtractor.java
similarity index 96%
rename from spring-restdocs-core/src/main/java/org/springframework/restdocs/core/LinkExtractor.java
rename to spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/LinkExtractor.java
index f63d2c4f..0627a306 100644
--- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/core/LinkExtractor.java
+++ b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/LinkExtractor.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.restdocs.core;
+package org.springframework.restdocs.hypermedia;
import java.io.IOException;
import java.util.List;
diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/core/LinkExtractors.java b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/LinkExtractors.java
similarity index 96%
rename from spring-restdocs-core/src/main/java/org/springframework/restdocs/core/LinkExtractors.java
rename to spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/LinkExtractors.java
index df35102e..07ffd203 100644
--- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/core/LinkExtractors.java
+++ b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/LinkExtractors.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.restdocs.core;
+package org.springframework.restdocs.hypermedia;
import java.io.IOException;
import java.util.ArrayList;
@@ -35,7 +35,11 @@ import com.fasterxml.jackson.databind.ObjectMapper;
*
* @author Andy Wilkinson
*/
-public class LinkExtractors {
+public abstract class LinkExtractors {
+
+ private LinkExtractors() {
+
+ }
/**
* Returns a {@code LinkExtractor} capable of extracting links in Hypermedia
@@ -75,7 +79,7 @@ public class LinkExtractors {
return null;
}
- private static abstract class JsonContentLinkExtractor implements LinkExtractor {
+ private abstract static class JsonContentLinkExtractor implements LinkExtractor {
private final ObjectMapper objectMapper = new ObjectMapper();
diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/LinkSnippetResultHandler.java b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/LinkSnippetResultHandler.java
new file mode 100644
index 00000000..ee8fc828
--- /dev/null
+++ b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/LinkSnippetResultHandler.java
@@ -0,0 +1,113 @@
+/*
+ * 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.hypermedia;
+
+import static org.junit.Assert.fail;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.springframework.restdocs.snippet.DocumentationWriter;
+import org.springframework.restdocs.snippet.SnippetWritingResultHandler;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.util.Assert;
+
+/**
+ * A {@link SnippetWritingResultHandler} that produces a snippet documenting a RESTful
+ * resource's links.
+ *
+ * @author Andy Wilkinson
+ */
+public class LinkSnippetResultHandler extends SnippetWritingResultHandler {
+
+ private final Map descriptorsByRel = new HashMap();
+
+ private final LinkExtractor extractor;
+
+ LinkSnippetResultHandler(String outputDir, LinkExtractor linkExtractor,
+ List descriptors) {
+ super(outputDir, "links");
+ this.extractor = linkExtractor;
+ for (LinkDescriptor descriptor : descriptors) {
+ Assert.hasText(descriptor.getRel());
+ Assert.hasText(descriptor.getDescription());
+ this.descriptorsByRel.put(descriptor.getRel(), descriptor);
+ }
+ }
+
+ @Override
+ protected void handle(MvcResult result, DocumentationWriter writer) throws Exception {
+ Map> links;
+ if (this.extractor != null) {
+ links = this.extractor.extractLinks(result.getResponse());
+ }
+ else {
+ String contentType = result.getResponse().getContentType();
+ LinkExtractor extractorForContentType = LinkExtractors
+ .extractorForContentType(contentType);
+ if (extractorForContentType != null) {
+ links = extractorForContentType.extractLinks(result.getResponse());
+ }
+ else {
+ throw new IllegalStateException(
+ "No LinkExtractor has been provided and one is not available for the content type "
+ + contentType);
+ }
+
+ }
+
+ Set actualRels = links.keySet();
+ Set expectedRels = this.descriptorsByRel.keySet();
+
+ Set undocumentedRels = new HashSet(actualRels);
+ undocumentedRels.removeAll(expectedRels);
+
+ Set missingRels = new HashSet(expectedRels);
+ missingRels.removeAll(actualRels);
+
+ if (!undocumentedRels.isEmpty() || !missingRels.isEmpty()) {
+ String message = "";
+ if (!undocumentedRels.isEmpty()) {
+ message += "Links with the following relations were not documented: "
+ + undocumentedRels;
+ }
+ if (!missingRels.isEmpty()) {
+ message += "Links with the following relations were not found in the response: "
+ + missingRels;
+ }
+ fail(message);
+ }
+
+ Assert.isTrue(actualRels.equals(expectedRels));
+
+ writer.println("|===");
+ writer.println("| Relation | Description");
+
+ for (Entry entry : this.descriptorsByRel.entrySet()) {
+ writer.println();
+ writer.println("| " + entry.getKey());
+ writer.println("| " + entry.getValue().getDescription());
+ }
+
+ writer.println("|===");
+ }
+
+}
\ No newline at end of file
diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/core/DocumentationWriter.java b/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/AsciidoctorWriter.java
similarity index 50%
rename from spring-restdocs-core/src/main/java/org/springframework/restdocs/core/DocumentationWriter.java
rename to spring-restdocs/src/main/java/org/springframework/restdocs/snippet/AsciidoctorWriter.java
index 178bb37b..5791694b 100644
--- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/core/DocumentationWriter.java
+++ b/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/AsciidoctorWriter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014 the original author or authors.
+ * 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.
@@ -14,47 +14,48 @@
* limitations under the License.
*/
-package org.springframework.restdocs.core;
+package org.springframework.restdocs.snippet;
-import java.io.OutputStream;
-import java.io.PrintWriter;
+import java.io.Writer;
-public class DocumentationWriter extends PrintWriter {
+/**
+ * A {@link DocumentationWriter} that produces output in Asciidoctor.
+ *
+ * @author Andy Wilkinson
+ */
+public class AsciidoctorWriter extends DocumentationWriter {
- public DocumentationWriter(OutputStream stream) {
- super(stream, true);
+ /**
+ * Creates a new {@code AsciidoctorWriter} that will write to the given {@code writer}
+ * @param writer The writer to which output will be written
+ */
+ public AsciidoctorWriter(Writer writer) {
+ super(writer);
}
- public void shellCommand(final DocumentationAction... actions) throws Exception {
+ @Override
+ public void shellCommand(final DocumentationAction action) throws Exception {
codeBlock("bash", new DocumentationAction() {
@Override
public void perform() throws Exception {
- DocumentationWriter.this.print("$ ");
- for (DocumentationAction action : actions) {
- action.perform();
- }
+ AsciidoctorWriter.this.print("$ ");
+ action.perform();
}
});
}
- public void codeBlock(String language, DocumentationAction... actions)
- throws Exception {
+ @Override
+ public void codeBlock(String language, DocumentationAction action) throws Exception {
println();
if (language != null) {
println("[source," + language + "]");
}
println("----");
- for (DocumentationAction action : actions) {
- action.perform();
- }
+ action.perform();
println("----");
println();
}
- public interface DocumentationAction {
-
- void perform() throws Exception;
- }
-
}
diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/core/DocumentationProperties.java b/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/DocumentationProperties.java
similarity index 93%
rename from spring-restdocs-core/src/main/java/org/springframework/restdocs/core/DocumentationProperties.java
rename to spring-restdocs/src/main/java/org/springframework/restdocs/snippet/DocumentationProperties.java
index fe644def..f8ca87b1 100644
--- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/core/DocumentationProperties.java
+++ b/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/DocumentationProperties.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014 the original author or authors.
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.restdocs.core;
+package org.springframework.restdocs.snippet;
import java.io.File;
import java.io.IOException;
diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/DocumentationWriter.java b/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/DocumentationWriter.java
new file mode 100644
index 00000000..83d0ee01
--- /dev/null
+++ b/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/DocumentationWriter.java
@@ -0,0 +1,74 @@
+/*
+ * 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.snippet;
+
+import java.io.PrintWriter;
+import java.io.Writer;
+
+/**
+ * A {@link PrintWriter} that provides additional methods which are useful for producing
+ * API documentation.
+ *
+ * @author Andy Wilkinson
+ */
+public abstract class DocumentationWriter extends PrintWriter {
+
+ protected DocumentationWriter(Writer writer) {
+ super(writer, true);
+ }
+
+ /**
+ * Calls the given {@code action} to document a shell command. Any prefix necessary
+ * for the documentation format is written prior to calling the {@code action}. Having
+ * called the action, any necessary suffix is then written.
+ *
+ * @param action the action that will produce the shell command
+ * @throws Exception if the documentation fails
+ */
+ public abstract void shellCommand(DocumentationAction action) throws Exception;
+
+ /**
+ * Calls the given {@code action} to document a code block. The code block will be
+ * annotated as containing code written in the given {@code language}. Any prefix
+ * necessary for the documentation format is written prior to calling the
+ * {@code action}. Having called the action, any necessary suffix is the written.
+ *
+ * @param language the language in which the code is written
+ * @param action the action that will produce the code
+ * @throws Exception if the documentation fails
+ */
+ public abstract void codeBlock(String language, DocumentationAction action)
+ throws Exception;
+
+ /**
+ * Encapsulates an action that outputs some documentation. Typically implemented as a
+ * lamda or, pre-Java 8, as an anonymous inner class.
+ *
+ * @author Andy Wilkinson
+ * @see DocumentationWriter#shellCommand
+ * @see DocumentationWriter#codeBlock
+ */
+ public interface DocumentationAction {
+
+ /**
+ * Perform the encapsulated action
+ *
+ * @throws Exception if the action fails
+ */
+ void perform() throws Exception;
+ }
+}
diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/SnippetWritingResultHandler.java b/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/SnippetWritingResultHandler.java
new file mode 100644
index 00000000..5785cc8d
--- /dev/null
+++ b/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/SnippetWritingResultHandler.java
@@ -0,0 +1,79 @@
+/*
+ * 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.snippet;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.test.web.servlet.ResultHandler;
+
+/**
+ * Base class for a {@link ResultHandler} that writes a documentation snippet
+ *
+ * @author Andy Wilkinson
+ */
+public abstract class SnippetWritingResultHandler implements ResultHandler {
+
+ private String outputDir;
+
+ private String fileName;
+
+ protected SnippetWritingResultHandler(String outputDir, String fileName) {
+ this.outputDir = outputDir;
+ this.fileName = fileName;
+ }
+
+ protected abstract void handle(MvcResult result, DocumentationWriter writer)
+ throws Exception;
+
+ @Override
+ public void handle(MvcResult result) throws Exception {
+ Writer writer = createWriter();
+ try {
+ handle(result, new AsciidoctorWriter(writer));
+ }
+ finally {
+ writer.close();
+ }
+ }
+
+ private Writer createWriter() throws IOException {
+ File outputFile = new File(this.outputDir, this.fileName + ".asciidoc");
+ if (!outputFile.isAbsolute()) {
+ outputFile = makeRelativeToConfiguredOutputDir(outputFile);
+ }
+
+ if (outputFile != null) {
+ outputFile.getParentFile().mkdirs();
+ return new FileWriter(outputFile);
+ }
+
+ return new OutputStreamWriter(System.out);
+ }
+
+ private File makeRelativeToConfiguredOutputDir(File outputFile) {
+ File configuredOutputDir = new DocumentationProperties().getOutputDir();
+ if (configuredOutputDir != null) {
+ return new File(configuredOutputDir, outputFile.getPath());
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/core/IterableEnumeration.java b/spring-restdocs/src/main/java/org/springframework/restdocs/util/IterableEnumeration.java
similarity index 65%
rename from spring-restdocs-core/src/main/java/org/springframework/restdocs/core/IterableEnumeration.java
rename to spring-restdocs/src/main/java/org/springframework/restdocs/util/IterableEnumeration.java
index b374fbc0..0dc04bb1 100644
--- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/core/IterableEnumeration.java
+++ b/spring-restdocs/src/main/java/org/springframework/restdocs/util/IterableEnumeration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014 the original author or authors.
+ * 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.
@@ -14,16 +14,23 @@
* limitations under the License.
*/
-package org.springframework.restdocs.core;
+package org.springframework.restdocs.util;
import java.util.Enumeration;
import java.util.Iterator;
-public final class IterableEnumeration implements Iterable {
+/**
+ * An adapter to expose an {@link Enumeration} as an {@link Iterable}.
+ *
+ * @author Andy Wilkinson
+ *
+ * @param the type of the Enumeration's contents
+ */
+public class IterableEnumeration implements Iterable {
private final Enumeration enumeration;
- public IterableEnumeration(Enumeration enumeration) {
+ private IterableEnumeration(Enumeration enumeration) {
this.enumeration = enumeration;
}
@@ -49,6 +56,13 @@ public final class IterableEnumeration implements Iterable {
};
}
+ /**
+ * Creates a new {@code Iterable} that will iterate over the given {@code enumeration}
+ *
+ * @param the type of the enumeration's elements
+ * @param enumeration The enumeration to expose as an {@code Iterable}
+ * @return the iterable
+ */
public static Iterable iterable(Enumeration enumeration) {
return new IterableEnumeration(enumeration);
}
diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/core/LinkExtractorsTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/core/LinkExtractorsTests.java
similarity index 95%
rename from spring-restdocs-core/src/test/java/org/springframework/restdocs/core/LinkExtractorsTests.java
rename to spring-restdocs/src/test/java/org/springframework/restdocs/core/LinkExtractorsTests.java
index 1794b7d1..f4a77439 100644
--- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/core/LinkExtractorsTests.java
+++ b/spring-restdocs/src/test/java/org/springframework/restdocs/core/LinkExtractorsTests.java
@@ -34,6 +34,9 @@ import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.restdocs.hypermedia.Link;
+import org.springframework.restdocs.hypermedia.LinkExtractor;
+import org.springframework.restdocs.hypermedia.LinkExtractors;
import org.springframework.util.FileCopyUtils;
/**
diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/core/RestDocumentationConfigurerTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/core/RestDocumentationConfigurerTests.java
new file mode 100644
index 00000000..5ff872ba
--- /dev/null
+++ b/spring-restdocs/src/test/java/org/springframework/restdocs/core/RestDocumentationConfigurerTests.java
@@ -0,0 +1,78 @@
+/*
+ * 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.core;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.restdocs.RestDocumentationConfigurer;
+import org.springframework.test.web.servlet.request.RequestPostProcessor;
+
+/**
+ * Tests for {@link RestDocumentationConfigurer}.
+ *
+ * @author Andy Wilkinson
+ */
+public class RestDocumentationConfigurerTests {
+
+ private MockHttpServletRequest request = new MockHttpServletRequest();
+
+ @Test
+ public void defaultConfiguration() {
+ RequestPostProcessor postProcessor = new RestDocumentationConfigurer()
+ .beforeMockMvcCreated(null, null);
+ postProcessor.postProcessRequest(this.request);
+
+ assertUriConfiguration("http", "localhost", 8080);
+ }
+
+ @Test
+ public void customScheme() {
+ RequestPostProcessor postProcessor = new RestDocumentationConfigurer()
+ .withScheme("https").beforeMockMvcCreated(null, null);
+ postProcessor.postProcessRequest(this.request);
+
+ assertUriConfiguration("https", "localhost", 8080);
+ }
+
+ @Test
+ public void customHost() {
+ RequestPostProcessor postProcessor = new RestDocumentationConfigurer().withHost(
+ "api.example.com").beforeMockMvcCreated(null, null);
+ postProcessor.postProcessRequest(this.request);
+
+ assertUriConfiguration("http", "api.example.com", 8080);
+ }
+
+ @Test
+ public void customPort() {
+ RequestPostProcessor postProcessor = new RestDocumentationConfigurer().withPort(
+ 8081).beforeMockMvcCreated(null, null);
+ postProcessor.postProcessRequest(this.request);
+
+ assertUriConfiguration("http", "localhost", 8081);
+ }
+
+ private void assertUriConfiguration(String scheme, String host, int port) {
+ assertEquals(scheme, this.request.getScheme());
+ assertEquals(host, this.request.getRemoteHost());
+ assertEquals(port, this.request.getRemotePort());
+ assertEquals(port, this.request.getServerPort());
+ }
+
+}
diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/snippet/AsciidoctorWriterTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/snippet/AsciidoctorWriterTests.java
new file mode 100644
index 00000000..33831d0b
--- /dev/null
+++ b/spring-restdocs/src/test/java/org/springframework/restdocs/snippet/AsciidoctorWriterTests.java
@@ -0,0 +1,67 @@
+/*
+ * 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.snippet;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.StringWriter;
+import java.io.Writer;
+
+import org.junit.Test;
+import org.springframework.restdocs.snippet.AsciidoctorWriter;
+import org.springframework.restdocs.snippet.DocumentationWriter;
+import org.springframework.restdocs.snippet.DocumentationWriter.DocumentationAction;
+
+/**
+ * Tests for {@link AsciidoctorWriter}
+ *
+ * @author Andy Wilkinson
+ */
+public class AsciidoctorWriterTests {
+
+ private Writer output = new StringWriter();
+
+ private DocumentationWriter documentationWriter = new AsciidoctorWriter(this.output);
+
+ @Test
+ public void codeBlock() throws Exception {
+ this.documentationWriter.codeBlock("java", new DocumentationAction() {
+
+ @Override
+ public void perform() throws Exception {
+ AsciidoctorWriterTests.this.documentationWriter.println("foo");
+ }
+ });
+
+ String expectedOutput = String.format("\n[source,java]\n----\nfoo\n----\n\n");
+ assertEquals(expectedOutput, this.output.toString());
+ }
+
+ @Test
+ public void shellCommand() throws Exception {
+ this.documentationWriter.shellCommand(new DocumentationAction() {
+
+ @Override
+ public void perform() throws Exception {
+ AsciidoctorWriterTests.this.documentationWriter.println("foo");
+ }
+ });
+
+ String expectedOutput = String.format("\n[source,bash]\n----\n$ foo\n----\n\n");
+ assertEquals(expectedOutput, this.output.toString());
+ }
+}
diff --git a/spring-restdocs-core/src/test/resources/link-payloads/atom/multiple-links-different-rels.json b/spring-restdocs/src/test/resources/link-payloads/atom/multiple-links-different-rels.json
similarity index 100%
rename from spring-restdocs-core/src/test/resources/link-payloads/atom/multiple-links-different-rels.json
rename to spring-restdocs/src/test/resources/link-payloads/atom/multiple-links-different-rels.json
diff --git a/spring-restdocs-core/src/test/resources/link-payloads/atom/multiple-links-same-rels.json b/spring-restdocs/src/test/resources/link-payloads/atom/multiple-links-same-rels.json
similarity index 100%
rename from spring-restdocs-core/src/test/resources/link-payloads/atom/multiple-links-same-rels.json
rename to spring-restdocs/src/test/resources/link-payloads/atom/multiple-links-same-rels.json
diff --git a/spring-restdocs-core/src/test/resources/link-payloads/atom/no-links.json b/spring-restdocs/src/test/resources/link-payloads/atom/no-links.json
similarity index 100%
rename from spring-restdocs-core/src/test/resources/link-payloads/atom/no-links.json
rename to spring-restdocs/src/test/resources/link-payloads/atom/no-links.json
diff --git a/spring-restdocs-core/src/test/resources/link-payloads/atom/single-link.json b/spring-restdocs/src/test/resources/link-payloads/atom/single-link.json
similarity index 100%
rename from spring-restdocs-core/src/test/resources/link-payloads/atom/single-link.json
rename to spring-restdocs/src/test/resources/link-payloads/atom/single-link.json
diff --git a/spring-restdocs-core/src/test/resources/link-payloads/atom/wrong-format.json b/spring-restdocs/src/test/resources/link-payloads/atom/wrong-format.json
similarity index 100%
rename from spring-restdocs-core/src/test/resources/link-payloads/atom/wrong-format.json
rename to spring-restdocs/src/test/resources/link-payloads/atom/wrong-format.json
diff --git a/spring-restdocs-core/src/test/resources/link-payloads/hal/multiple-links-different-rels.json b/spring-restdocs/src/test/resources/link-payloads/hal/multiple-links-different-rels.json
similarity index 100%
rename from spring-restdocs-core/src/test/resources/link-payloads/hal/multiple-links-different-rels.json
rename to spring-restdocs/src/test/resources/link-payloads/hal/multiple-links-different-rels.json
diff --git a/spring-restdocs-core/src/test/resources/link-payloads/hal/multiple-links-same-rels.json b/spring-restdocs/src/test/resources/link-payloads/hal/multiple-links-same-rels.json
similarity index 100%
rename from spring-restdocs-core/src/test/resources/link-payloads/hal/multiple-links-same-rels.json
rename to spring-restdocs/src/test/resources/link-payloads/hal/multiple-links-same-rels.json
diff --git a/spring-restdocs-core/src/test/resources/link-payloads/hal/no-links.json b/spring-restdocs/src/test/resources/link-payloads/hal/no-links.json
similarity index 100%
rename from spring-restdocs-core/src/test/resources/link-payloads/hal/no-links.json
rename to spring-restdocs/src/test/resources/link-payloads/hal/no-links.json
diff --git a/spring-restdocs-core/src/test/resources/link-payloads/hal/single-link.json b/spring-restdocs/src/test/resources/link-payloads/hal/single-link.json
similarity index 100%
rename from spring-restdocs-core/src/test/resources/link-payloads/hal/single-link.json
rename to spring-restdocs/src/test/resources/link-payloads/hal/single-link.json
diff --git a/spring-restdocs-core/src/test/resources/link-payloads/hal/wrong-format.json b/spring-restdocs/src/test/resources/link-payloads/hal/wrong-format.json
similarity index 100%
rename from spring-restdocs-core/src/test/resources/link-payloads/hal/wrong-format.json
rename to spring-restdocs/src/test/resources/link-payloads/hal/wrong-format.json