Use a template engine to produce the documentation snippets
This commit introduces a new TemplateEngine abstraction that is used
to produce the documentation snippets. A default JMustache-based
implementation is provided. JMustache has been repackaged and embedded
to prevent unwanted conflicts and side-effects.
By default, snippet templates are loaded from the classpath in the
org.springframework.restdocs.templates package. Default snippet
templates are provided for all of the snippets that can be generated.
Each of these templates is named after the snippet that it will
produce – the snippet {name}.adoc is produced by the snippet template
default-{name}.snippet. A snippet named {name}.snippet, if present,
will be used in preference to the default snippet, thereby allowing
the default snippets to be overriden.
This commit is contained in:
@@ -15,6 +15,7 @@ apply plugin: 'samples'
|
||||
|
||||
ext {
|
||||
springVersion = '4.1.5.RELEASE'
|
||||
jmustacheVersion = '1.10'
|
||||
}
|
||||
|
||||
subprojects {
|
||||
@@ -28,6 +29,7 @@ subprojects {
|
||||
}
|
||||
dependencies {
|
||||
dependency 'com.fasterxml.jackson.core:jackson-databind:2.4.6'
|
||||
dependency "com.samskivert:jmustache:$jmustacheVersion"
|
||||
dependency 'javax.servlet:javax.servlet-api:3.1.0'
|
||||
dependency 'junit:junit:4.12'
|
||||
dependency 'org.hamcrest:hamcrest-core:1.3'
|
||||
|
||||
12
docs/src/docs/asciidoc/customizing-snippets.adoc
Normal file
12
docs/src/docs/asciidoc/customizing-snippets.adoc
Normal file
@@ -0,0 +1,12 @@
|
||||
[[customizing-snippets]]
|
||||
== Customizing the generated snippets
|
||||
|
||||
Spring REST Docs uses https://mustache.github.io[Mustache] templates to produce the
|
||||
generated snippets. You can customize the generated snippets by overriding the
|
||||
{source}spring-restdocs/src/main/resources/org/springframework/restdocs/templates[default
|
||||
templates].
|
||||
|
||||
Templates are loaded from the classpath in the `org.springframework.restdocs.templates`
|
||||
package and each template is named after the snippet that it will produce. For example, to
|
||||
override the template for the `curl-request.adoc` snippet, create a template named
|
||||
`curl-request.snippet` in `src/test/resources/org/springframework/restdocs/templates`.
|
||||
@@ -9,7 +9,9 @@ Andy Wilkinson
|
||||
|
||||
:examples-dir: ../../test/java
|
||||
:github: https://github.com/spring-projects/spring-restdocs
|
||||
:samples: {github}/tree/{branch-or-tag}/samples
|
||||
:source: {github}/tree/{branch-or-tag}/
|
||||
:samples: {source}/samples
|
||||
:templates: {source}/spring-restdocs/src/main/resources/org/springframework/restdocs/templates
|
||||
:spring-boot-docs: http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle
|
||||
:spring-framework-docs: http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle
|
||||
|
||||
@@ -22,6 +24,7 @@ include::introduction.adoc[]
|
||||
include::getting-started.adoc[]
|
||||
include::documenting-your-api.adoc[]
|
||||
include::customizing-responses.adoc[]
|
||||
include::customizing-snippets.adoc[]
|
||||
include::configuration.adoc[]
|
||||
include::working-with-asciidoctor.adoc[]
|
||||
include::contributing.adoc[]
|
||||
@@ -33,8 +33,8 @@ can be used to reference the snippets output directory, for example:
|
||||
=== Customizing tables
|
||||
|
||||
Many of the snippets contain a table in its default configuration. The appearance of the
|
||||
table can be customized by providing some additional configuration when the snippet is
|
||||
included.
|
||||
table can be customized, either by providing some additional configuration when the
|
||||
snippet is included or by using a custom snippet template.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ sonarRunner {
|
||||
|
||||
configurations {
|
||||
jacoco
|
||||
jarjar
|
||||
jmustache
|
||||
}
|
||||
|
||||
ext {
|
||||
@@ -38,6 +40,24 @@ ext {
|
||||
] as String[]
|
||||
}
|
||||
|
||||
task jmustacheRepackJar(type: Jar) { repackJar ->
|
||||
repackJar.baseName = "restdocs-jmustache-repack"
|
||||
repackJar.version = jmustacheVersion
|
||||
|
||||
doLast() {
|
||||
project.ant {
|
||||
taskdef name: "jarjar", classname: "com.tonicsystems.jarjar.JarJarTask",
|
||||
classpath: configurations.jarjar.asPath
|
||||
jarjar(destfile: repackJar.archivePath) {
|
||||
configurations.jmustache.each { originalJar ->
|
||||
zipfileset(src: originalJar, includes: '**/*.class')
|
||||
}
|
||||
rule(pattern: 'com.samskivert.**', result: 'org.springframework.restdocs.@1')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'com.fasterxml.jackson.core:jackson-databind'
|
||||
compile 'junit:junit'
|
||||
@@ -46,13 +66,23 @@ dependencies {
|
||||
compile 'org.springframework:spring-web'
|
||||
compile 'org.springframework:spring-webmvc'
|
||||
compile 'javax.servlet:javax.servlet-api'
|
||||
compile files(jmustacheRepackJar)
|
||||
jacoco 'org.jacoco:org.jacoco.agent::runtime'
|
||||
jarjar 'com.googlecode.jarjar:jarjar:1.3'
|
||||
jmustache 'com.samskivert:jmustache@jar'
|
||||
testCompile 'org.springframework.hateoas:spring-hateoas'
|
||||
testCompile 'org.mockito:mockito-core'
|
||||
testCompile 'org.hamcrest:hamcrest-core'
|
||||
testCompile 'org.hamcrest:hamcrest-library'
|
||||
}
|
||||
|
||||
jar {
|
||||
dependsOn jmustacheRepackJar
|
||||
from(zipTree(jmustacheRepackJar.archivePath)) {
|
||||
include "org/springframework/restdocs/**"
|
||||
}
|
||||
}
|
||||
|
||||
task sourcesJar(type: Jar) {
|
||||
classifier = 'sources'
|
||||
from project.sourceSets.main.allSource
|
||||
@@ -60,13 +90,12 @@ task sourcesJar(type: Jar) {
|
||||
|
||||
javadoc {
|
||||
description = "Generates project-level javadoc for use in -javadoc jar"
|
||||
|
||||
options.memberLevel = org.gradle.external.javadoc.JavadocMemberLevel.PROTECTED
|
||||
options.author = true
|
||||
options.header = "Spring REST Docs $version"
|
||||
options.docTitle = "${options.header} API"
|
||||
options.links(project.ext.javadocLinks)
|
||||
options.addStringOption('-quiet')
|
||||
options.addStringOption '-quiet'
|
||||
}
|
||||
|
||||
task javadocJar(type: Jar) {
|
||||
@@ -80,7 +109,7 @@ artifacts {
|
||||
}
|
||||
|
||||
test {
|
||||
jvmArgs "-javaagent:${configurations.jacoco.asPath}=destfile=${buildDir}/jacoco.exec,includes=org.springframework.restdocs.*"
|
||||
jvmArgs "-javaagent:${configurations.jacoco.asPath}=destfile=${buildDir}/jacoco.exec,includes=org.springframework.restdocs.*,excludes=org.springframework.restdocs.mustache.*"
|
||||
testLogging {
|
||||
exceptionFormat "full"
|
||||
}
|
||||
|
||||
@@ -21,6 +21,9 @@ import java.util.List;
|
||||
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.restdocs.RestDocumentation;
|
||||
import org.springframework.restdocs.templates.StandardTemplateResourceResolver;
|
||||
import org.springframework.restdocs.templates.TemplateEngine;
|
||||
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
|
||||
import org.springframework.test.web.servlet.request.RequestPostProcessor;
|
||||
import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcConfigurer;
|
||||
@@ -44,6 +47,8 @@ public class RestDocumentationConfigurer extends MockMvcConfigurerAdapter {
|
||||
|
||||
private final RequestPostProcessor requestPostProcessor;
|
||||
|
||||
private TemplateEngineConfigurer templateEngineConfigurer = new TemplateEngineConfigurer();
|
||||
|
||||
/**
|
||||
* Creates a new {@link RestDocumentationConfigurer}.
|
||||
* @see RestDocumentation#documentationConfiguration()
|
||||
@@ -52,7 +57,8 @@ public class RestDocumentationConfigurer extends MockMvcConfigurerAdapter {
|
||||
this.requestPostProcessor = new ConfigurerApplyingRequestPostProcessor(
|
||||
Arrays.<AbstractConfigurer> asList(this.uriConfigurer,
|
||||
this.snippetConfigurer, new StepCountConfigurer(),
|
||||
new ContentLengthHeaderConfigurer()));
|
||||
new ContentLengthHeaderConfigurer(),
|
||||
new TemplateEngineConfigurer()));
|
||||
}
|
||||
|
||||
public UriConfigurer uris() {
|
||||
@@ -63,6 +69,11 @@ public class RestDocumentationConfigurer extends MockMvcConfigurerAdapter {
|
||||
return this.snippetConfigurer;
|
||||
}
|
||||
|
||||
public RestDocumentationConfigurer templateEngine(TemplateEngine templateEngine) {
|
||||
this.templateEngineConfigurer.setTemplateEngine(templateEngine);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestPostProcessor beforeMockMvcCreated(
|
||||
ConfigurableMockMvcBuilder<?> builder, WebApplicationContext context) {
|
||||
@@ -95,6 +106,22 @@ public class RestDocumentationConfigurer extends MockMvcConfigurerAdapter {
|
||||
|
||||
}
|
||||
|
||||
private static class TemplateEngineConfigurer extends AbstractConfigurer {
|
||||
|
||||
private TemplateEngine templateEngine = new MustacheTemplateEngine(
|
||||
new StandardTemplateResourceResolver());
|
||||
|
||||
@Override
|
||||
void apply(MockHttpServletRequest request) {
|
||||
request.setAttribute(TemplateEngine.class.getName(), this.templateEngine);
|
||||
}
|
||||
|
||||
void setTemplateEngine(TemplateEngine templateEngine) {
|
||||
this.templateEngine = templateEngine;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class ConfigurerApplyingRequestPostProcessor implements
|
||||
RequestPostProcessor {
|
||||
|
||||
|
||||
@@ -17,12 +17,15 @@
|
||||
package org.springframework.restdocs.curl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.springframework.restdocs.snippet.DocumentationWriter;
|
||||
import org.springframework.restdocs.snippet.DocumentationWriter.DocumentationAction;
|
||||
import org.springframework.restdocs.snippet.SnippetWritingResultHandler;
|
||||
import org.springframework.restdocs.templates.TemplateEngine;
|
||||
import org.springframework.restdocs.util.DocumentableHttpServletRequest;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -50,18 +53,11 @@ public abstract class CurlDocumentation {
|
||||
* @return the handler that will produce the snippet
|
||||
*/
|
||||
public static SnippetWritingResultHandler documentCurlRequest(String outputDir) {
|
||||
return new SnippetWritingResultHandler(outputDir, "curl-request") {
|
||||
|
||||
@Override
|
||||
public void handle(MvcResult result, DocumentationWriter writer)
|
||||
throws IOException {
|
||||
writer.shellCommand(new CurlRequestDocumentationAction(writer, result));
|
||||
}
|
||||
};
|
||||
return new CurlRequestWritingResultHandler(outputDir);
|
||||
}
|
||||
|
||||
private static final class CurlRequestDocumentationAction implements
|
||||
DocumentationAction {
|
||||
private static final class CurlRequestWritingResultHandler extends
|
||||
SnippetWritingResultHandler {
|
||||
|
||||
private static final String SCHEME_HTTP = "http";
|
||||
|
||||
@@ -71,46 +67,52 @@ public abstract class CurlDocumentation {
|
||||
|
||||
private static final int STANDARD_PORT_HTTPS = 443;
|
||||
|
||||
private final DocumentationWriter writer;
|
||||
|
||||
private final MvcResult result;
|
||||
|
||||
CurlRequestDocumentationAction(DocumentationWriter writer, MvcResult result) {
|
||||
this.writer = writer;
|
||||
this.result = result;
|
||||
private CurlRequestWritingResultHandler(String outputDir) {
|
||||
super(outputDir, "curl-request");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void perform() throws IOException {
|
||||
DocumentableHttpServletRequest request = new DocumentableHttpServletRequest(
|
||||
this.result.getRequest());
|
||||
public void handle(MvcResult result, PrintWriter writer) throws IOException {
|
||||
Map<String, Object> context = new HashMap<String, Object>();
|
||||
context.put("language", "bash");
|
||||
context.put("arguments", getCurlCommandArguments(result));
|
||||
|
||||
this.writer.print("curl '");
|
||||
TemplateEngine templateEngine = (TemplateEngine) result.getRequest()
|
||||
.getAttribute(TemplateEngine.class.getName());
|
||||
|
||||
writeAuthority(request);
|
||||
writePathAndQueryString(request);
|
||||
|
||||
this.writer.print("'");
|
||||
|
||||
writeOptionToIncludeHeadersInOutput();
|
||||
writeHttpMethodIfNecessary(request);
|
||||
writeHeaders(request);
|
||||
|
||||
if (request.isMultipartRequest()) {
|
||||
writeParts(request);
|
||||
}
|
||||
|
||||
writeContent(request);
|
||||
|
||||
this.writer.println();
|
||||
writer.print(templateEngine.compileTemplate("curl-request").render(context));
|
||||
}
|
||||
|
||||
private void writeAuthority(DocumentableHttpServletRequest request) {
|
||||
this.writer.print(String.format("%s://%s", request.getScheme(),
|
||||
request.getHost()));
|
||||
private String getCurlCommandArguments(MvcResult result) throws IOException {
|
||||
StringWriter command = new StringWriter();
|
||||
PrintWriter printer = new PrintWriter(command);
|
||||
DocumentableHttpServletRequest request = new DocumentableHttpServletRequest(
|
||||
result.getRequest());
|
||||
|
||||
printer.print("'");
|
||||
writeAuthority(request, printer);
|
||||
writePathAndQueryString(request, printer);
|
||||
printer.print("'");
|
||||
|
||||
writeOptionToIncludeHeadersInOutput(printer);
|
||||
writeHttpMethodIfNecessary(request, printer);
|
||||
writeHeaders(request, printer);
|
||||
|
||||
if (request.isMultipartRequest()) {
|
||||
writeParts(request, printer);
|
||||
}
|
||||
|
||||
writeContent(request, printer);
|
||||
|
||||
return command.toString();
|
||||
}
|
||||
|
||||
private void writeAuthority(DocumentableHttpServletRequest request,
|
||||
PrintWriter writer) {
|
||||
writer.print(String.format("%s://%s", request.getScheme(), request.getHost()));
|
||||
|
||||
if (isNonStandardPort(request)) {
|
||||
this.writer.print(String.format(":%d", request.getPort()));
|
||||
writer.print(String.format(":%d", request.getPort()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,75 +121,75 @@ public abstract class CurlDocumentation {
|
||||
|| (SCHEME_HTTPS.equals(request.getScheme()) && request.getPort() != STANDARD_PORT_HTTPS);
|
||||
}
|
||||
|
||||
private void writePathAndQueryString(DocumentableHttpServletRequest request) {
|
||||
private void writePathAndQueryString(DocumentableHttpServletRequest request,
|
||||
PrintWriter writer) {
|
||||
if (StringUtils.hasText(request.getContextPath())) {
|
||||
this.writer.print(String.format(
|
||||
writer.print(String.format(
|
||||
request.getContextPath().startsWith("/") ? "%s" : "/%s",
|
||||
request.getContextPath()));
|
||||
}
|
||||
|
||||
this.writer.print(request.getRequestUriWithQueryString());
|
||||
writer.print(request.getRequestUriWithQueryString());
|
||||
}
|
||||
|
||||
private void writeOptionToIncludeHeadersInOutput() {
|
||||
this.writer.print(" -i");
|
||||
private void writeOptionToIncludeHeadersInOutput(PrintWriter writer) {
|
||||
writer.print(" -i");
|
||||
}
|
||||
|
||||
private void writeHttpMethodIfNecessary(DocumentableHttpServletRequest request) {
|
||||
private void writeHttpMethodIfNecessary(DocumentableHttpServletRequest request,
|
||||
PrintWriter writer) {
|
||||
if (!request.isGetRequest()) {
|
||||
this.writer.print(String.format(" -X %s", request.getMethod()));
|
||||
writer.print(String.format(" -X %s", request.getMethod()));
|
||||
}
|
||||
}
|
||||
|
||||
private void writeHeaders(DocumentableHttpServletRequest request) {
|
||||
private void writeHeaders(DocumentableHttpServletRequest request,
|
||||
PrintWriter writer) {
|
||||
for (Entry<String, List<String>> entry : request.getHeaders().entrySet()) {
|
||||
for (String header : entry.getValue()) {
|
||||
this.writer.print(String.format(" -H '%s: %s'", entry.getKey(),
|
||||
header));
|
||||
writer.print(String.format(" -H '%s: %s'", entry.getKey(), header));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeParts(DocumentableHttpServletRequest request)
|
||||
private void writeParts(DocumentableHttpServletRequest request, PrintWriter writer)
|
||||
throws IOException {
|
||||
for (Entry<String, List<MultipartFile>> entry : request.getMultipartFiles()
|
||||
.entrySet()) {
|
||||
for (MultipartFile file : entry.getValue()) {
|
||||
this.writer.printf(" -F '%s=", file.getName());
|
||||
writer.printf(" -F '%s=", file.getName());
|
||||
if (!StringUtils.hasText(file.getOriginalFilename())) {
|
||||
this.writer.append(new String(file.getBytes()));
|
||||
writer.append(new String(file.getBytes()));
|
||||
}
|
||||
else {
|
||||
this.writer.printf("@%s", file.getOriginalFilename());
|
||||
writer.printf("@%s", file.getOriginalFilename());
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(file.getContentType())) {
|
||||
this.writer.append(";type=").append(file.getContentType());
|
||||
writer.append(";type=").append(file.getContentType());
|
||||
}
|
||||
this.writer.append("'");
|
||||
writer.append("'");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void writeContent(DocumentableHttpServletRequest request)
|
||||
throws IOException {
|
||||
private void writeContent(DocumentableHttpServletRequest request,
|
||||
PrintWriter writer) throws IOException {
|
||||
if (request.getContentLength() > 0) {
|
||||
this.writer
|
||||
.print(String.format(" -d '%s'", request.getContentAsString()));
|
||||
writer.print(String.format(" -d '%s'", request.getContentAsString()));
|
||||
}
|
||||
else if (request.isMultipartRequest()) {
|
||||
for (Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
|
||||
for (String value : entry.getValue()) {
|
||||
this.writer.print(String.format(" -F '%s=%s'", entry.getKey(),
|
||||
value));
|
||||
writer.print(String.format(" -F '%s=%s'", entry.getKey(), value));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (request.isPostRequest() || request.isPutRequest()) {
|
||||
String queryString = request.getParameterMapAsQueryString();
|
||||
if (StringUtils.hasText(queryString)) {
|
||||
this.writer.print(String.format(" -d '%s'", queryString));
|
||||
writer.print(String.format(" -d '%s'", queryString));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,15 +17,19 @@
|
||||
package org.springframework.restdocs.http;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.restdocs.snippet.DocumentationWriter;
|
||||
import org.springframework.restdocs.snippet.DocumentationWriter.DocumentationAction;
|
||||
import org.springframework.restdocs.snippet.SnippetWritingResultHandler;
|
||||
import org.springframework.restdocs.templates.TemplateEngine;
|
||||
import org.springframework.restdocs.util.DocumentableHttpServletRequest;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -39,6 +43,8 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
*/
|
||||
public abstract class HttpDocumentation {
|
||||
|
||||
private static final String MULTIPART_BOUNDARY = "6o2knFse3p53ty9dmcQvWAIx1zInP11uCfbm";
|
||||
|
||||
private HttpDocumentation() {
|
||||
|
||||
}
|
||||
@@ -51,15 +57,7 @@ public abstract class HttpDocumentation {
|
||||
* @return the handler that will produce the snippet
|
||||
*/
|
||||
public static SnippetWritingResultHandler documentHttpRequest(String outputDir) {
|
||||
return new SnippetWritingResultHandler(outputDir, "http-request") {
|
||||
|
||||
@Override
|
||||
public void handle(MvcResult result, DocumentationWriter writer)
|
||||
throws IOException {
|
||||
writer.codeBlock("http", new HttpRequestDocumentationAction(writer,
|
||||
result));
|
||||
}
|
||||
};
|
||||
return new HttpRequestWritingResultHandler(outputDir);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,71 +68,126 @@ public abstract class HttpDocumentation {
|
||||
* @return the handler that will produce the snippet
|
||||
*/
|
||||
public static SnippetWritingResultHandler documentHttpResponse(String outputDir) {
|
||||
return new SnippetWritingResultHandler(outputDir, "http-response") {
|
||||
return new HttpResponseWritingResultHandler(outputDir);
|
||||
|
||||
@Override
|
||||
public void handle(MvcResult result, DocumentationWriter writer)
|
||||
throws IOException {
|
||||
writer.codeBlock("http", new HttpResponseDocumentationAction(writer,
|
||||
result));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static class HttpRequestDocumentationAction implements DocumentationAction {
|
||||
private static final class HttpRequestWritingResultHandler extends
|
||||
SnippetWritingResultHandler {
|
||||
|
||||
private static final String MULTIPART_BOUNDARY = "6o2knFse3p53ty9dmcQvWAIx1zInP11uCfbm";
|
||||
|
||||
private final DocumentationWriter writer;
|
||||
|
||||
private final MvcResult result;
|
||||
|
||||
HttpRequestDocumentationAction(DocumentationWriter writer, MvcResult result) {
|
||||
this.writer = writer;
|
||||
this.result = result;
|
||||
private HttpRequestWritingResultHandler(String outputDir) {
|
||||
super(outputDir, "http-request");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void perform() throws IOException {
|
||||
public void handle(MvcResult result, PrintWriter writer) throws IOException {
|
||||
DocumentableHttpServletRequest request = new DocumentableHttpServletRequest(
|
||||
this.result.getRequest());
|
||||
this.writer.printf("%s %s HTTP/1.1%n", request.getMethod(),
|
||||
request.getRequestUriWithQueryString());
|
||||
result.getRequest());
|
||||
Map<String, Object> context = new HashMap<String, Object>();
|
||||
context.put("language", "http");
|
||||
context.put("method", result.getRequest().getMethod());
|
||||
context.put("path", request.getRequestUriWithQueryString());
|
||||
context.put("headers", getHeaders(request));
|
||||
context.put("requestBody", getRequestBody(request));
|
||||
|
||||
TemplateEngine templateEngine = (TemplateEngine) result.getRequest()
|
||||
.getAttribute(TemplateEngine.class.getName());
|
||||
|
||||
writer.print(templateEngine.compileTemplate("http-request").render(context));
|
||||
}
|
||||
|
||||
private List<Map<String, String>> getHeaders(
|
||||
DocumentableHttpServletRequest request) {
|
||||
List<Map<String, String>> headers = new ArrayList<>();
|
||||
if (requiresHostHeader(request)) {
|
||||
writeHeader(HttpHeaders.HOST, request.getHost());
|
||||
headers.add(header(HttpHeaders.HOST, request.getHost()));
|
||||
}
|
||||
|
||||
for (Entry<String, List<String>> header : request.getHeaders().entrySet()) {
|
||||
for (String value : header.getValue()) {
|
||||
if (header.getKey() == HttpHeaders.CONTENT_TYPE
|
||||
&& request.isMultipartRequest()) {
|
||||
writeHeader(header.getKey(), String.format("%s; boundary=%s",
|
||||
value, MULTIPART_BOUNDARY));
|
||||
headers.add(header(header.getKey(), String.format(
|
||||
"%s; boundary=%s", value, MULTIPART_BOUNDARY)));
|
||||
}
|
||||
else {
|
||||
this.writer.printf("%s: %s%n", header.getKey(), value);
|
||||
headers.add(header(header.getKey(), value));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if (requiresFormEncodingContentType(request)) {
|
||||
writeHeader(HttpHeaders.CONTENT_TYPE,
|
||||
MediaType.APPLICATION_FORM_URLENCODED_VALUE);
|
||||
headers.add(header(HttpHeaders.CONTENT_TYPE,
|
||||
MediaType.APPLICATION_FORM_URLENCODED_VALUE));
|
||||
}
|
||||
this.writer.println();
|
||||
return headers;
|
||||
}
|
||||
|
||||
private String getRequestBody(DocumentableHttpServletRequest request)
|
||||
throws IOException {
|
||||
StringWriter httpRequest = new StringWriter();
|
||||
PrintWriter writer = new PrintWriter(httpRequest);
|
||||
if (request.getContentLength() > 0) {
|
||||
this.writer.println(request.getContentAsString());
|
||||
writer.println();
|
||||
writer.print(request.getContentAsString());
|
||||
}
|
||||
else if (request.isPostRequest() || request.isPutRequest()) {
|
||||
if (request.isMultipartRequest()) {
|
||||
writeParts(request);
|
||||
writeParts(request, writer);
|
||||
}
|
||||
else {
|
||||
String queryString = request.getParameterMapAsQueryString();
|
||||
if (StringUtils.hasText(queryString)) {
|
||||
this.writer.println(queryString);
|
||||
writer.println();
|
||||
writer.print(queryString);
|
||||
}
|
||||
}
|
||||
}
|
||||
return httpRequest.toString();
|
||||
}
|
||||
|
||||
private void writeParts(DocumentableHttpServletRequest request, PrintWriter writer)
|
||||
throws IOException {
|
||||
writer.println();
|
||||
for (Entry<String, String[]> parameter : request.getParameterMap().entrySet()) {
|
||||
for (String value : parameter.getValue()) {
|
||||
writePartBoundary(writer);
|
||||
writePart(parameter.getKey(), value, null, writer);
|
||||
writer.println();
|
||||
}
|
||||
}
|
||||
for (Entry<String, List<MultipartFile>> entry : request.getMultipartFiles()
|
||||
.entrySet()) {
|
||||
for (MultipartFile file : entry.getValue()) {
|
||||
writePartBoundary(writer);
|
||||
writePart(file, writer);
|
||||
writer.println();
|
||||
}
|
||||
}
|
||||
writeMultipartEnd(writer);
|
||||
}
|
||||
|
||||
private void writePartBoundary(PrintWriter writer) {
|
||||
writer.printf("--%s%n", MULTIPART_BOUNDARY);
|
||||
}
|
||||
|
||||
private void writePart(String name, String value, String contentType,
|
||||
PrintWriter writer) {
|
||||
writer.printf("Content-Disposition: form-data; name=%s%n", name);
|
||||
if (StringUtils.hasText(contentType)) {
|
||||
writer.printf("Content-Type: %s%n", contentType);
|
||||
}
|
||||
writer.println();
|
||||
writer.print(value);
|
||||
}
|
||||
|
||||
private void writePart(MultipartFile part, PrintWriter writer) throws IOException {
|
||||
writePart(part.getName(), new String(part.getBytes()), part.getContentType(),
|
||||
writer);
|
||||
}
|
||||
|
||||
private void writeMultipartEnd(PrintWriter writer) {
|
||||
writer.printf("--%s--", MULTIPART_BOUNDARY);
|
||||
}
|
||||
|
||||
private boolean requiresHostHeader(DocumentableHttpServletRequest request) {
|
||||
@@ -148,79 +201,55 @@ public abstract class HttpDocumentation {
|
||||
&& StringUtils.hasText(request.getParameterMapAsQueryString());
|
||||
}
|
||||
|
||||
private void writeHeader(String name, String value) {
|
||||
this.writer.printf("%s: %s%n", name, value);
|
||||
}
|
||||
|
||||
private void writeParts(DocumentableHttpServletRequest request)
|
||||
throws IOException {
|
||||
for (Entry<String, String[]> parameter : request.getParameterMap().entrySet()) {
|
||||
for (String value : parameter.getValue()) {
|
||||
writePartBoundary();
|
||||
writePart(parameter.getKey(), value, null);
|
||||
this.writer.println();
|
||||
}
|
||||
}
|
||||
for (Entry<String, List<MultipartFile>> entry : request.getMultipartFiles()
|
||||
.entrySet()) {
|
||||
for (MultipartFile file : entry.getValue()) {
|
||||
writePartBoundary();
|
||||
writePart(file);
|
||||
this.writer.println();
|
||||
}
|
||||
}
|
||||
writeMultipartEnd();
|
||||
}
|
||||
|
||||
private void writePartBoundary() {
|
||||
this.writer.printf("--%s%n", MULTIPART_BOUNDARY);
|
||||
}
|
||||
|
||||
private void writePart(String name, String value, String contentType) {
|
||||
this.writer.printf("Content-Disposition: form-data; name=%s%n", name);
|
||||
if (StringUtils.hasText(contentType)) {
|
||||
this.writer.printf("Content-Type: %s%n", contentType);
|
||||
}
|
||||
this.writer.println();
|
||||
this.writer.print(value);
|
||||
}
|
||||
|
||||
private void writePart(MultipartFile part) throws IOException {
|
||||
writePart(part.getName(), new String(part.getBytes()), part.getContentType());
|
||||
}
|
||||
|
||||
private void writeMultipartEnd() {
|
||||
this.writer.printf("--%s--%n", MULTIPART_BOUNDARY);
|
||||
private Map<String, String> header(String name, String value) {
|
||||
Map<String, String> header = new HashMap<>();
|
||||
header.put("name", name);
|
||||
header.put("value", value);
|
||||
return header;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class HttpResponseDocumentationAction implements
|
||||
DocumentationAction {
|
||||
private static final class HttpResponseWritingResultHandler extends
|
||||
SnippetWritingResultHandler {
|
||||
|
||||
private final DocumentationWriter writer;
|
||||
|
||||
private final MvcResult result;
|
||||
|
||||
HttpResponseDocumentationAction(DocumentationWriter writer, MvcResult result) {
|
||||
this.writer = writer;
|
||||
this.result = result;
|
||||
private HttpResponseWritingResultHandler(String outputDir) {
|
||||
super(outputDir, "http-response");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void perform() throws IOException {
|
||||
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));
|
||||
public void handle(MvcResult result, PrintWriter writer) throws IOException {
|
||||
HttpStatus status = HttpStatus.valueOf(result.getResponse().getStatus());
|
||||
Map<String, Object> context = new HashMap<String, Object>();
|
||||
context.put(
|
||||
"responseBody",
|
||||
StringUtils.hasLength(result.getResponse().getContentAsString()) ? String
|
||||
.format("%n%s", result.getResponse().getContentAsString())
|
||||
: "");
|
||||
context.put("language", "http");
|
||||
context.put("statusCode", status.value());
|
||||
context.put("statusReason", status.getReasonPhrase());
|
||||
|
||||
List<Map<String, String>> headers = new ArrayList<>();
|
||||
context.put("headers", headers);
|
||||
|
||||
for (String headerName : result.getResponse().getHeaderNames()) {
|
||||
for (String header : result.getResponse().getHeaders(headerName)) {
|
||||
headers.add(header(headerName, header));
|
||||
}
|
||||
}
|
||||
this.writer.println();
|
||||
String content = this.result.getResponse().getContentAsString();
|
||||
if (StringUtils.hasText(content)) {
|
||||
this.writer.println(content);
|
||||
}
|
||||
|
||||
TemplateEngine templateEngine = (TemplateEngine) result.getRequest()
|
||||
.getAttribute(TemplateEngine.class.getName());
|
||||
|
||||
writer.print(templateEngine.compileTemplate("http-response").render(context));
|
||||
|
||||
}
|
||||
|
||||
private Map<String, String> header(String name, String value) {
|
||||
Map<String, String> header = new HashMap<>();
|
||||
header.put("name", name);
|
||||
header.put("value", value);
|
||||
return header;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,18 +17,17 @@
|
||||
package org.springframework.restdocs.hypermedia;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
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.DocumentationWriter.TableAction;
|
||||
import org.springframework.restdocs.snippet.DocumentationWriter.TableWriter;
|
||||
import org.springframework.restdocs.snippet.SnippetGenerationException;
|
||||
import org.springframework.restdocs.snippet.SnippetWritingResultHandler;
|
||||
import org.springframework.restdocs.templates.TemplateEngine;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -61,10 +60,9 @@ public class LinkSnippetResultHandler extends SnippetWritingResultHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handle(MvcResult result, DocumentationWriter writer)
|
||||
throws IOException {
|
||||
protected void handle(MvcResult result, PrintWriter writer) throws IOException {
|
||||
validate(extractLinks(result));
|
||||
writeDocumentationSnippet(writer);
|
||||
writeDocumentationSnippet(result, writer);
|
||||
}
|
||||
|
||||
private Map<String, List<Link>> extractLinks(MvcResult result) throws IOException {
|
||||
@@ -111,19 +109,13 @@ public class LinkSnippetResultHandler extends SnippetWritingResultHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private void writeDocumentationSnippet(DocumentationWriter writer) throws IOException {
|
||||
writer.table(new TableAction() {
|
||||
|
||||
@Override
|
||||
public void perform(TableWriter tableWriter) throws IOException {
|
||||
tableWriter.headers("Relation", "Description");
|
||||
for (Entry<String, LinkDescriptor> entry : LinkSnippetResultHandler.this.descriptorsByRel
|
||||
.entrySet()) {
|
||||
tableWriter.row(entry.getKey(), entry.getValue().getDescription());
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
private void writeDocumentationSnippet(MvcResult result, PrintWriter writer)
|
||||
throws IOException {
|
||||
TemplateEngine templateEngine = (TemplateEngine) result.getRequest()
|
||||
.getAttribute(TemplateEngine.class.getName());
|
||||
Map<String, Object> context = new HashMap<>();
|
||||
context.put("links", this.descriptorsByRel.values());
|
||||
writer.print(templateEngine.compileTemplate("links").render(context));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,16 +17,17 @@
|
||||
package org.springframework.restdocs.payload;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.Reader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.springframework.restdocs.snippet.DocumentationWriter;
|
||||
import org.springframework.restdocs.snippet.DocumentationWriter.TableAction;
|
||||
import org.springframework.restdocs.snippet.DocumentationWriter.TableWriter;
|
||||
import org.springframework.restdocs.snippet.SnippetWritingResultHandler;
|
||||
import org.springframework.restdocs.templates.TemplateEngine;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -35,7 +36,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
/**
|
||||
* A {@link SnippetWritingResultHandler} that produces a snippet documenting a RESTful
|
||||
* resource's request or response fields.
|
||||
*
|
||||
*
|
||||
* @author Andreas Evers
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@@ -49,11 +50,14 @@ public abstract class FieldSnippetResultHandler extends SnippetWritingResultHand
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private final String templateName;
|
||||
|
||||
private List<FieldDescriptor> fieldDescriptors;
|
||||
|
||||
FieldSnippetResultHandler(String outputDir, String filename,
|
||||
FieldSnippetResultHandler(String outputDir, String type,
|
||||
List<FieldDescriptor> descriptors) {
|
||||
super(outputDir, filename + "-fields");
|
||||
super(outputDir, type + "-fields");
|
||||
this.templateName = type + "-fields";
|
||||
for (FieldDescriptor descriptor : descriptors) {
|
||||
Assert.notNull(descriptor.getPath());
|
||||
Assert.hasText(descriptor.getDescription());
|
||||
@@ -63,49 +67,46 @@ public abstract class FieldSnippetResultHandler extends SnippetWritingResultHand
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handle(MvcResult result, DocumentationWriter writer)
|
||||
throws IOException {
|
||||
protected void handle(MvcResult result, PrintWriter writer) throws IOException {
|
||||
|
||||
this.fieldValidator.validate(getPayloadReader(result), this.fieldDescriptors);
|
||||
|
||||
final Object payload = extractPayload(result);
|
||||
Object payload = extractPayload(result);
|
||||
|
||||
writer.table(new TableAction() {
|
||||
|
||||
@Override
|
||||
public void perform(TableWriter tableWriter) throws IOException {
|
||||
tableWriter.headers("Path", "Type", "Description");
|
||||
for (Entry<String, FieldDescriptor> entry : FieldSnippetResultHandler.this.descriptorsByPath
|
||||
.entrySet()) {
|
||||
FieldDescriptor descriptor = entry.getValue();
|
||||
FieldType type = getFieldType(descriptor, payload);
|
||||
tableWriter.row(entry.getKey().toString(), type.toString(), entry
|
||||
.getValue().getDescription());
|
||||
}
|
||||
TemplateEngine templateEngine = (TemplateEngine) result.getRequest()
|
||||
.getAttribute(TemplateEngine.class.getName());
|
||||
Map<String, Object> context = new HashMap<>();
|
||||
List<Map<String, String>> fields = new ArrayList<>();
|
||||
context.put("fields", fields);
|
||||
for (Entry<String, FieldDescriptor> entry : this.descriptorsByPath.entrySet()) {
|
||||
FieldDescriptor descriptor = entry.getValue();
|
||||
FieldType type = getFieldType(descriptor, payload);
|
||||
Map<String, String> fieldModel = new HashMap<>();
|
||||
fieldModel.put("path", entry.getKey());
|
||||
fieldModel.put("type", type.toString());
|
||||
fieldModel.put("description", descriptor.getDescription());
|
||||
fields.add(fieldModel);
|
||||
}
|
||||
writer.print(templateEngine.compileTemplate(this.templateName).render(context));
|
||||
}
|
||||
|
||||
private FieldType getFieldType(FieldDescriptor descriptor, Object payload) {
|
||||
if (descriptor.getType() != null) {
|
||||
return descriptor.getType();
|
||||
}
|
||||
else {
|
||||
try {
|
||||
return FieldSnippetResultHandler.this.fieldTypeResolver.resolveFieldType(
|
||||
descriptor.getPath(), payload);
|
||||
}
|
||||
|
||||
private FieldType getFieldType(FieldDescriptor descriptor, Object payload) {
|
||||
if (descriptor.getType() != null) {
|
||||
return descriptor.getType();
|
||||
}
|
||||
else {
|
||||
try {
|
||||
return FieldSnippetResultHandler.this.fieldTypeResolver
|
||||
.resolveFieldType(descriptor.getPath(), payload);
|
||||
}
|
||||
catch (FieldDoesNotExistException ex) {
|
||||
String message = "Cannot determine the type of the field '"
|
||||
+ descriptor.getPath() + "' as it is not present in the"
|
||||
+ " payload. Please provide a type using"
|
||||
+ " FieldDescriptor.type(FieldType).";
|
||||
throw new FieldTypeRequiredException(message);
|
||||
}
|
||||
}
|
||||
catch (FieldDoesNotExistException ex) {
|
||||
String message = "Cannot determine the type of the field '"
|
||||
+ descriptor.getPath() + "' as it is not present in the"
|
||||
+ " payload. Please provide a type using"
|
||||
+ " FieldDescriptor.type(FieldType).";
|
||||
throw new FieldTypeRequiredException(message);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private Object extractPayload(MvcResult result) throws IOException {
|
||||
|
||||
@@ -17,17 +17,16 @@
|
||||
package org.springframework.restdocs.request;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.restdocs.snippet.DocumentationWriter;
|
||||
import org.springframework.restdocs.snippet.DocumentationWriter.TableAction;
|
||||
import org.springframework.restdocs.snippet.DocumentationWriter.TableWriter;
|
||||
import org.springframework.restdocs.snippet.SnippetGenerationException;
|
||||
import org.springframework.restdocs.snippet.SnippetWritingResultHandler;
|
||||
import org.springframework.restdocs.templates.TemplateEngine;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -52,10 +51,9 @@ public class QueryParametersSnippetResultHandler extends SnippetWritingResultHan
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handle(MvcResult result, DocumentationWriter writer)
|
||||
throws IOException {
|
||||
protected void handle(MvcResult result, PrintWriter writer) throws IOException {
|
||||
verifyParameterDescriptors(result);
|
||||
documentParameters(writer);
|
||||
documentParameters(result, writer);
|
||||
}
|
||||
|
||||
private void verifyParameterDescriptors(MvcResult result) {
|
||||
@@ -87,19 +85,13 @@ public class QueryParametersSnippetResultHandler extends SnippetWritingResultHan
|
||||
Assert.isTrue(actualParameters.equals(expectedParameters));
|
||||
}
|
||||
|
||||
private void documentParameters(DocumentationWriter writer) throws IOException {
|
||||
writer.table(new TableAction() {
|
||||
|
||||
@Override
|
||||
public void perform(TableWriter tableWriter) throws IOException {
|
||||
tableWriter.headers("Parameter", "Description");
|
||||
for (Entry<String, ParameterDescriptor> entry : QueryParametersSnippetResultHandler.this.descriptorsByName
|
||||
.entrySet()) {
|
||||
tableWriter.row(entry.getKey(), entry.getValue().getDescription());
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
private void documentParameters(MvcResult result, PrintWriter writer)
|
||||
throws IOException {
|
||||
TemplateEngine templateEngine = (TemplateEngine) result.getRequest()
|
||||
.getAttribute(TemplateEngine.class.getName());
|
||||
Map<String, Object> context = new HashMap<>();
|
||||
context.put("parameters", this.descriptorsByName.values());
|
||||
writer.print(templateEngine.compileTemplate("query-parameters").render(context));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
/*
|
||||
* 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.IOException;
|
||||
import java.io.Writer;
|
||||
|
||||
/**
|
||||
* A {@link DocumentationWriter} that produces output in <a
|
||||
* href="http://asciidoctor.org">Asciidoctor</a>.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class AsciidoctorWriter extends DocumentationWriter {
|
||||
|
||||
private static final String DELIMITER_CODE_BLOCK = "----";
|
||||
|
||||
private static final String DELIMITER_TABLE = "|===";
|
||||
|
||||
private final TableWriter tableWriter = new AsciidoctorTableWriter();
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shellCommand(final DocumentationAction action) throws IOException {
|
||||
codeBlock("bash", new DocumentationAction() {
|
||||
|
||||
@Override
|
||||
public void perform() throws IOException {
|
||||
AsciidoctorWriter.this.print("$ ");
|
||||
action.perform();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void codeBlock(String language, DocumentationAction action) throws IOException {
|
||||
println();
|
||||
if (language != null) {
|
||||
println("[source," + language + "]");
|
||||
}
|
||||
println(DELIMITER_CODE_BLOCK);
|
||||
action.perform();
|
||||
println(DELIMITER_CODE_BLOCK);
|
||||
println();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void table(TableAction action) throws IOException {
|
||||
println();
|
||||
println(DELIMITER_TABLE);
|
||||
action.perform(this.tableWriter);
|
||||
println(DELIMITER_TABLE);
|
||||
println();
|
||||
}
|
||||
|
||||
private final class AsciidoctorTableWriter implements TableWriter {
|
||||
|
||||
@Override
|
||||
public void headers(String... headers) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (String header : headers) {
|
||||
builder.append("|");
|
||||
builder.append(header);
|
||||
}
|
||||
println(builder.toString());
|
||||
println();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void row(String... entries) {
|
||||
for (String entry : entries) {
|
||||
print("|");
|
||||
println(entry);
|
||||
}
|
||||
println();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
/*
|
||||
* 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.IOException;
|
||||
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 IOException if the documentation fails
|
||||
*/
|
||||
public abstract void shellCommand(DocumentationAction action) throws IOException;
|
||||
|
||||
/**
|
||||
* 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 {@code action}, any necessary suffix is then
|
||||
* written.
|
||||
*
|
||||
* @param language the language in which the code is written
|
||||
* @param action the action that will produce the code
|
||||
* @throws IOException if the documentation fails
|
||||
*/
|
||||
public abstract void codeBlock(String language, DocumentationAction action)
|
||||
throws IOException;
|
||||
|
||||
/**
|
||||
* Calls the given {@code action} to document a table. Any prefix necessary for
|
||||
* documenting a table is written prior to calling the {@code action}. Having called
|
||||
* the {@code action}, any necessary suffix is then written.
|
||||
*
|
||||
* @param action the action that will produce the table
|
||||
* @throws IOException if the documentation fails
|
||||
*/
|
||||
public abstract void table(TableAction action) throws IOException;
|
||||
|
||||
/**
|
||||
* Encapsulates an action that outputs some documentation. Typically implemented as a
|
||||
* lamda or, pre-Java 8, as an anonymous inner class.
|
||||
*
|
||||
* @see DocumentationWriter#shellCommand
|
||||
* @see DocumentationWriter#codeBlock
|
||||
*/
|
||||
public interface DocumentationAction {
|
||||
|
||||
/**
|
||||
* Perform the encapsulated action
|
||||
*
|
||||
* @throws IOException if the action fails
|
||||
*/
|
||||
void perform() throws IOException;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Encapsulates an action that outputs a table.
|
||||
*
|
||||
* @see DocumentationWriter#table(TableAction)
|
||||
*/
|
||||
public interface TableAction {
|
||||
|
||||
/**
|
||||
* Perform the encapsulated action
|
||||
*
|
||||
* @param tableWriter the writer to be used to write the table
|
||||
* @throws IOException if the action fails
|
||||
*/
|
||||
void perform(TableWriter tableWriter) throws IOException;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A writer for producing a table
|
||||
*/
|
||||
public interface TableWriter {
|
||||
|
||||
/**
|
||||
* Writes the table's headers
|
||||
*
|
||||
* @param headers the headers
|
||||
*/
|
||||
void headers(String... headers);
|
||||
|
||||
/**
|
||||
* Writes a row in the table
|
||||
*
|
||||
* @param entries the entries in the row
|
||||
*/
|
||||
void row(String... entries);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import java.io.FileOutputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.Writer;
|
||||
|
||||
import org.springframework.restdocs.config.RestDocumentationContext;
|
||||
@@ -43,13 +44,13 @@ public abstract class SnippetWritingResultHandler implements ResultHandler {
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
protected abstract void handle(MvcResult result, DocumentationWriter writer)
|
||||
protected abstract void handle(MvcResult result, PrintWriter writer)
|
||||
throws IOException;
|
||||
|
||||
@Override
|
||||
public void handle(MvcResult result) throws IOException {
|
||||
try (Writer writer = createWriter()) {
|
||||
handle(result, new AsciidoctorWriter(writer));
|
||||
handle(result, new PrintWriter(writer));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.templates;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
/**
|
||||
* Standard implementation of {@link TemplateResourceResolver}.
|
||||
* <p>
|
||||
* Templates are resolved by first looking for a resource on the classpath named
|
||||
* {@code org/springframework/restdocs/templates/{name}.snippet}. If no such
|
||||
* resource exists {@code default-} is prepended to the name and the classpath is checked
|
||||
* again. The built-in snippet templates are all named {@code default- name}, thereby
|
||||
* allowing them to be overridden.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class StandardTemplateResourceResolver implements TemplateResourceResolver {
|
||||
|
||||
@Override
|
||||
public Resource resolveTemplateResource(String name) {
|
||||
ClassPathResource classPathResource = new ClassPathResource(
|
||||
"org/springframework/restdocs/templates/" + name + ".snippet");
|
||||
if (!classPathResource.exists()) {
|
||||
classPathResource = new ClassPathResource(
|
||||
"org/springframework/restdocs/templates/default-" + name + ".snippet");
|
||||
if (!classPathResource.exists()) {
|
||||
throw new IllegalStateException("Template named '" + name
|
||||
+ "' could not be resolved");
|
||||
}
|
||||
}
|
||||
return classPathResource;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.springframework.restdocs.templates;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A compiled {@code Template} that can be rendered to a {@link String}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*
|
||||
*/
|
||||
public interface Template {
|
||||
|
||||
/**
|
||||
* Renders the template to a {@link String} using the given {@code context} for
|
||||
* variable/property resolution.
|
||||
*
|
||||
* @param context The context to use
|
||||
* @return The rendered template
|
||||
*/
|
||||
String render(Map<String, Object> context);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.templates;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
|
||||
|
||||
/**
|
||||
* A {@code TemplateEngine} is used to render documentation snippets.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @see MustacheTemplateEngine
|
||||
*/
|
||||
public interface TemplateEngine {
|
||||
|
||||
/**
|
||||
* Compiles the template at the given {@code path}. Typically, a
|
||||
* {@link TemplateResourceResolver} will be used to resolve the path into a resource
|
||||
* that can be read and compiled.
|
||||
*
|
||||
* @param path the path of the template
|
||||
* @return the compiled {@code Template}
|
||||
* @throws IOException if compilation fails
|
||||
*/
|
||||
Template compileTemplate(String path) throws IOException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.templates;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
/**
|
||||
* A {@code TemplateResourceResolver} is responsible for resolving a name for a template
|
||||
* into a {@link Resource} from which the template can be read.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public interface TemplateResourceResolver {
|
||||
|
||||
/**
|
||||
* Resolves a {@link Resource} for the template with the given {@code name}.
|
||||
*
|
||||
* @param name the name of the template
|
||||
* @return the {@code Resource} from which the template can be read
|
||||
*/
|
||||
public Resource resolveTemplateResource(String name);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.templates.mustache;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.restdocs.templates.Template;
|
||||
|
||||
/**
|
||||
* An adapter that exposes a compiled <a href="https://mustache.github.io">Mustache</a>
|
||||
* template as a {@link Template}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class MustacheTemplate implements Template {
|
||||
|
||||
private final org.springframework.restdocs.mustache.Template delegate;
|
||||
|
||||
/**
|
||||
* Creates a new {@code MustacheTemplate} that adapts the given {@code delegate}. When
|
||||
* rendered, the given {@code defaultContext} will be combined with the render context
|
||||
* prior to executing the delegate.
|
||||
* @param delegate The delegate to adapt
|
||||
*/
|
||||
public MustacheTemplate(org.springframework.restdocs.mustache.Template delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String render(Map<String, Object> context) {
|
||||
return this.delegate.execute(context);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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.templates.mustache;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.restdocs.mustache.Mustache;
|
||||
import org.springframework.restdocs.mustache.Mustache.Compiler;
|
||||
import org.springframework.restdocs.templates.Template;
|
||||
import org.springframework.restdocs.templates.TemplateEngine;
|
||||
import org.springframework.restdocs.templates.TemplateResourceResolver;
|
||||
|
||||
/**
|
||||
* A <a href="https://mustache.github.io">Mustache</a>-based {@link TemplateEngine}
|
||||
* implemented using <a href="https://github.com/samskivert/jmustache">JMustache</a>.
|
||||
* <p>
|
||||
* Note that JMustache has been repackaged and embedded to prevent classpath conflicts.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class MustacheTemplateEngine implements TemplateEngine {
|
||||
|
||||
private final Compiler compiler = Mustache.compiler().escapeHTML(false);
|
||||
|
||||
private final TemplateResourceResolver templateResourceResolver;
|
||||
|
||||
/**
|
||||
* Creates a new {@link MustacheTemplateEngine} that will use the given
|
||||
* {@code templateResourceResolver} to resolve template paths.
|
||||
*
|
||||
* @param templateResourceResolver The resolve to use
|
||||
*/
|
||||
public MustacheTemplateEngine(TemplateResourceResolver templateResourceResolver) {
|
||||
this.templateResourceResolver = templateResourceResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Template compileTemplate(String name) throws IOException {
|
||||
Resource templateResource = this.templateResourceResolver
|
||||
.resolveTemplateResource(name);
|
||||
return new MustacheTemplate(this.compiler.compile(new InputStreamReader(
|
||||
templateResource.getInputStream())));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
[source,bash]
|
||||
----
|
||||
$ curl {{arguments}}
|
||||
----
|
||||
@@ -0,0 +1,10 @@
|
||||
|===
|
||||
|Path|Type|Description
|
||||
|
||||
{{#fields}}
|
||||
|{{path}}
|
||||
|{{type}}
|
||||
|{{description}}
|
||||
|
||||
{{/fields}}
|
||||
|===
|
||||
@@ -0,0 +1,8 @@
|
||||
[source,http]
|
||||
----
|
||||
{{method}} {{path}} HTTP/1.1
|
||||
{{#headers}}
|
||||
{{name}}: {{value}}
|
||||
{{/headers}}
|
||||
{{requestBody}}
|
||||
----
|
||||
@@ -0,0 +1,8 @@
|
||||
[source,http]
|
||||
----
|
||||
HTTP/1.1 {{statusCode}} {{statusReason}}
|
||||
{{#headers}}
|
||||
{{name}}: {{value}}
|
||||
{{/headers}}
|
||||
{{responseBody}}
|
||||
----
|
||||
@@ -0,0 +1,9 @@
|
||||
|===
|
||||
|Relation|Description
|
||||
|
||||
{{#links}}
|
||||
|{{rel}}
|
||||
|{{description}}
|
||||
|
||||
{{/links}}
|
||||
|===
|
||||
@@ -0,0 +1,9 @@
|
||||
|===
|
||||
|Parameter|Description
|
||||
|
||||
{{#parameters}}
|
||||
|{{name}}
|
||||
|{{description}}
|
||||
|
||||
{{/parameters}}
|
||||
|===
|
||||
@@ -0,0 +1,10 @@
|
||||
|===
|
||||
|Path|Type|Description
|
||||
|
||||
{{#fields}}
|
||||
|{{path}}
|
||||
|{{type}}
|
||||
|{{description}}
|
||||
|
||||
{{/fields}}
|
||||
|===
|
||||
@@ -0,0 +1,10 @@
|
||||
|===
|
||||
|Path|Type|Description
|
||||
|
||||
{{#fields}}
|
||||
|{{path}}
|
||||
|{{type}}
|
||||
|{{description}}
|
||||
|
||||
{{/fields}}
|
||||
|===
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.restdocs;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
@@ -31,6 +32,8 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -175,6 +178,29 @@ public class RestDocumentationIntegrationTests {
|
||||
+ " \"...\"%n } ]%n}")))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customSnippetTemplate() throws Exception {
|
||||
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
|
||||
.apply(new RestDocumentationConfigurer()).build();
|
||||
|
||||
ClassLoader classLoader = new URLClassLoader(new URL[] { new File(
|
||||
"src/test/resources/custom-snippet-templates").toURI().toURL() },
|
||||
getClass().getClassLoader());
|
||||
ClassLoader previous = Thread.currentThread().getContextClassLoader();
|
||||
Thread.currentThread().setContextClassLoader(classLoader);
|
||||
try {
|
||||
mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andDo(document("custom-snippet-template"));
|
||||
}
|
||||
finally {
|
||||
Thread.currentThread().setContextClassLoader(previous);
|
||||
}
|
||||
assertThat(new File(
|
||||
"build/generated-snippets/custom-snippet-template/curl-request.adoc"),
|
||||
is(snippet().withContents(equalTo("Custom curl request"))));
|
||||
}
|
||||
|
||||
private void assertExpectedSnippetFilesExist(File directory, String... snippets) {
|
||||
for (String snippet : snippets) {
|
||||
assertTrue(new File(directory, snippet).isFile());
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
/*
|
||||
* 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.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.io.Writer;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.restdocs.snippet.DocumentationWriter.DocumentationAction;
|
||||
import org.springframework.restdocs.snippet.DocumentationWriter.TableAction;
|
||||
import org.springframework.restdocs.snippet.DocumentationWriter.TableWriter;
|
||||
|
||||
/**
|
||||
* 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 IOException {
|
||||
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 IOException {
|
||||
AsciidoctorWriterTests.this.documentationWriter.println("foo");
|
||||
}
|
||||
});
|
||||
|
||||
String expectedOutput = String.format("%n[source,bash]%n----%n$ foo%n----%n%n");
|
||||
assertEquals(expectedOutput, this.output.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void table() throws Exception {
|
||||
this.documentationWriter.table(new TableAction() {
|
||||
|
||||
@Override
|
||||
public void perform(TableWriter tableWriter) throws IOException {
|
||||
tableWriter.headers("One", "Two", "Three");
|
||||
tableWriter.row("alpha", "bravo", "charlie");
|
||||
tableWriter.row("foo", "bar", "baz");
|
||||
}
|
||||
|
||||
});
|
||||
String expectedOutput = String
|
||||
.format("%n|===%n|One|Two|Three%n%n|alpha%n|bravo%n|charlie%n%n|foo%n|bar%n|baz%n%n|===%n%n");
|
||||
assertEquals(expectedOutput, this.output.toString());
|
||||
System.out.println(this.output.toString());
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.hamcrest.BaseMatcher;
|
||||
@@ -100,8 +101,12 @@ public class SnippetMatchers {
|
||||
|
||||
private String getLinesAsString() {
|
||||
StringWriter writer = new StringWriter();
|
||||
for (String line : this.lines) {
|
||||
writer.append(String.format("%s%n", line));
|
||||
Iterator<String> iterator = this.lines.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
writer.append(String.format("%s", iterator.next()));
|
||||
if (iterator.hasNext()) {
|
||||
writer.append(String.format("%n"));
|
||||
}
|
||||
}
|
||||
return writer.toString();
|
||||
}
|
||||
@@ -111,16 +116,14 @@ public class SnippetMatchers {
|
||||
extends AbstractSnippetContentMatcher {
|
||||
|
||||
protected AsciidoctorCodeBlockMatcher(String language) {
|
||||
this.addLine("");
|
||||
this.addLine("[source," + language + "]");
|
||||
this.addLine("----");
|
||||
this.addLine("----");
|
||||
this.addLine("");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public T content(String content) {
|
||||
this.addLine(-2, content);
|
||||
this.addLine(-1, content);
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
@@ -129,7 +132,7 @@ public class SnippetMatchers {
|
||||
public static abstract class HttpMatcher<T extends HttpMatcher<T>> extends
|
||||
AsciidoctorCodeBlockMatcher<HttpMatcher<T>> {
|
||||
|
||||
private int headerOffset = 4;
|
||||
private int headerOffset = 3;
|
||||
|
||||
protected HttpMatcher() {
|
||||
super("http");
|
||||
@@ -164,7 +167,6 @@ public class SnippetMatchers {
|
||||
public static class AsciidoctorTableMatcher extends AbstractSnippetContentMatcher {
|
||||
|
||||
private AsciidoctorTableMatcher(String... columns) {
|
||||
this.addLine("");
|
||||
this.addLine("|===");
|
||||
String header = "|"
|
||||
+ StringUtils
|
||||
@@ -172,14 +174,13 @@ public class SnippetMatchers {
|
||||
this.addLine(header);
|
||||
this.addLine("");
|
||||
this.addLine("|===");
|
||||
this.addLine("");
|
||||
}
|
||||
|
||||
public AsciidoctorTableMatcher row(String... entries) {
|
||||
for (String entry : entries) {
|
||||
this.addLine(-2, "|" + entry);
|
||||
this.addLine(-1, "|" + entry);
|
||||
}
|
||||
this.addLine(-2, "");
|
||||
this.addLine(-1, "");
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@ package org.springframework.restdocs.test;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.mock.web.MockServletContext;
|
||||
import org.springframework.restdocs.templates.StandardTemplateResourceResolver;
|
||||
import org.springframework.restdocs.templates.TemplateEngine;
|
||||
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.test.web.servlet.RequestBuilder;
|
||||
import org.springframework.web.servlet.FlashMap;
|
||||
@@ -76,6 +79,8 @@ public class StubMvcResult implements MvcResult {
|
||||
|
||||
private StubMvcResult(MockHttpServletRequest request, MockHttpServletResponse response) {
|
||||
this.request = request;
|
||||
this.request.setAttribute(TemplateEngine.class.getName(),
|
||||
new MustacheTemplateEngine(new StandardTemplateResourceResolver()));
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Custom curl request
|
||||
Reference in New Issue
Block a user