Improve code structure, javadoc, and build configuration

This commit makes extensive changes to the structure of the code. It
introduces a number of separate packages to provide better separation
of the various areas of functionality. Alongside this change the project
has been renamed from spring-restdocs-core to spring-restdocs.

The build has been improved to provide support for building the samples
from the main build using the buildSamples task. While this change has
been made, the samples remain standalone projects so that their
configuration is not dependent on the main project’s build. Running
buildSamples will build the samples using both Maven and Gradle.

All of the main project’s classes now have javadoc and licence/copyright
headers.
This commit is contained in:
Andy Wilkinson
2015-02-16 16:46:56 +00:00
parent f405848179
commit 42270b127c
89 changed files with 1137 additions and 533 deletions

View File

@@ -1,33 +1,25 @@
allprojects {
ext {
springVersion = '4.1.1.RELEASE'
}
group = 'org.springframework.restdocs'
}
project(':spring-restdocs') {
ext {
jacksonVersion = '2.3.4'
junitVersion = '4.11'
servletApiVersion = '3.1.0'
springVersion = '4.1.4.RELEASE'
}
group = 'org.springframework.restdocs'
project(':spring-restdocs-core') {
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'maven'
sourceCompatibility = 1.7
targetCompatibility = 1.7
dependencies {
compile 'junit:junit:4.11'
compile "org.springframework:spring-test:$springVersion"
compile "org.springframework:spring-web:$springVersion"
compile 'javax.servlet:javax.servlet-api:3.1.0'
compile 'com.fasterxml.jackson.core:jackson-databind:2.3.4'
}
}
subprojects {
repositories {
jcenter()
}
apply plugin: 'eclipse'
apply plugin: 'maven'
task sourcesJar(type: Jar) {
classifier = 'sources'
from project.sourceSets.main.allSource
@@ -45,4 +37,57 @@ subprojects {
eclipseJdt.onlyIf { false }
cleanEclipseJdt.onlyIf { false }
dependencies {
compile "junit:junit:$junitVersion"
compile "org.springframework:spring-test:$springVersion"
compile "org.springframework:spring-web:$springVersion"
compile "javax.servlet:javax.servlet-api:$servletApiVersion"
compile "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion"
}
}
task buildSamples {
description = 'Assembles and tests the sample projects using both Maven and Gradle'
group = 'Build'
dependsOn 'buildMavenSamples'
dependsOn 'buildGradleSamples'
}
task buildMavenSamples {
dependsOn 'buildRestNotesSpringHateoasSampleWithMaven'
dependsOn 'buildRestNotesSpringDataRestSampleWithMaven'
}
task buildGradleSamples {
dependsOn 'buildRestNotesSpringHateoasSampleWithGradle'
dependsOn 'buildRestNotesSpringDataRestSampleWithGradle'
}
task buildRestNotesSpringHateoasSampleWithGradle(type: GradleBuild) {
dependsOn 'spring-restdocs:install'
dir = 'samples/rest-notes-spring-hateoas'
tasks = ['clean', 'build']
}
task buildRestNotesSpringDataRestSampleWithGradle(type: GradleBuild) {
dependsOn 'spring-restdocs:install'
dir = 'samples/rest-notes-spring-data-rest'
tasks = ['clean', 'build']
}
task buildRestNotesSpringHateoasSampleWithMaven(type: Exec) {
dependsOn 'spring-restdocs:install'
workingDir 'samples/rest-notes-spring-hateoas'
commandLine 'mvn', 'clean', 'package'
}
task buildRestNotesSpringDataRestSampleWithMaven(type: Exec) {
dependsOn 'spring-restdocs:install'
workingDir 'samples/rest-notes-spring-data-rest'
commandLine 'mvn', 'clean', 'package'
}
wrapper {
gradleVersion = '2.2'
}

View File

@@ -1,6 +1,6 @@
#Wed Oct 08 10:04:39 BST 2014
#Mon Feb 16 13:50:18 GMT 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-2.2-bin.zip

View File

@@ -31,7 +31,7 @@ dependencies {
testCompile 'com.jayway.jsonpath:json-path'
testCompile 'org.springframework.boot:spring-boot-starter-test'
testCompile 'org.springframework.restdocs:spring-restdocs-core:0.1.0.BUILD-SNAPSHOT'
testCompile 'org.springframework.restdocs:spring-restdocs:0.1.0.BUILD-SNAPSHOT'
}

View File

@@ -48,7 +48,7 @@
</dependency>
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-core</artifactId>
<artifactId>spring-restdocs</artifactId>
<version>0.1.0.BUILD-SNAPSHOT</version>
<scope>test</scope>
</dependency>

View File

@@ -18,8 +18,8 @@ package com.example.notes;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.springframework.restdocs.core.RestDocumentation.document;
import static org.springframework.restdocs.core.RestDocumentation.linkWithRel;
import static org.springframework.restdocs.RestDocumentation.document;
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
@@ -39,7 +39,7 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.hateoas.MediaTypes;
import org.springframework.restdocs.core.RestDocumentationConfiguration;
import org.springframework.restdocs.RestDocumentationConfigurer;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
@@ -70,7 +70,7 @@ public class ApiDocumentation {
@Before
public void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(new RestDocumentationConfiguration()).build();
.apply(new RestDocumentationConfigurer()).build();
}
@Test

View File

@@ -19,7 +19,7 @@ package com.example.notes;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.springframework.restdocs.core.RestDocumentation.document;
import static org.springframework.restdocs.RestDocumentation.document;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
@@ -38,7 +38,7 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.hateoas.MediaTypes;
import org.springframework.restdocs.core.RestDocumentationConfiguration;
import org.springframework.restdocs.RestDocumentationConfigurer;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
@@ -66,7 +66,7 @@ public class GettingStartedDocumentation {
@Before
public void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(new RestDocumentationConfiguration()).build();
.apply(new RestDocumentationConfigurer()).build();
}
@Test

View File

@@ -8,7 +8,7 @@ buildscript {
}
plugins {
id "org.asciidoctor.convert" version "1.5.2"
id "org.asciidoctor.convert" version "1.5.2"
}
apply plugin: 'java'
@@ -34,7 +34,7 @@ dependencies {
testCompile 'com.jayway.jsonpath:json-path'
testCompile 'org.springframework.boot:spring-boot-starter-test'
testCompile 'org.springframework.restdocs:spring-restdocs-core:0.1.0.BUILD-SNAPSHOT'
testCompile 'org.springframework.restdocs:spring-restdocs:0.1.0.BUILD-SNAPSHOT'
}
ext {

View File

@@ -64,7 +64,7 @@
</dependency>
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-core</artifactId>
<artifactId>spring-restdocs</artifactId>
<version>0.1.0.BUILD-SNAPSHOT</version>
<scope>test</scope>
</dependency>

View File

@@ -18,8 +18,8 @@ package com.example.notes;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.springframework.restdocs.core.RestDocumentation.document;
import static org.springframework.restdocs.core.RestDocumentation.linkWithRel;
import static org.springframework.restdocs.RestDocumentation.document;
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
@@ -39,7 +39,7 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.hateoas.MediaTypes;
import org.springframework.restdocs.core.RestDocumentationConfiguration;
import org.springframework.restdocs.RestDocumentationConfigurer;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
@@ -70,7 +70,7 @@ public class ApiDocumentation {
@Before
public void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(new RestDocumentationConfiguration()).build();
.apply(new RestDocumentationConfigurer()).build();
}
@Test

View File

@@ -19,7 +19,7 @@ package com.example.notes;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.springframework.restdocs.core.RestDocumentation.document;
import static org.springframework.restdocs.RestDocumentation.document;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
@@ -39,7 +39,7 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.hateoas.MediaTypes;
import org.springframework.restdocs.core.RestDocumentationConfiguration;
import org.springframework.restdocs.RestDocumentationConfigurer;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
@@ -67,7 +67,7 @@ public class GettingStartedDocumentation {
@Before
public void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(new RestDocumentationConfiguration()).build();
.apply(new RestDocumentationConfigurer()).build();
}
@Test

View File

@@ -1,3 +1,3 @@
rootProject.name = 'spring-restdocs'
rootProject.name = 'spring-restdocs-build'
include 'spring-restdocs-core'
include 'spring-restdocs'

View File

@@ -1,65 +0,0 @@
/*
* Copyright 2014 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 org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder;
import org.springframework.test.web.servlet.setup.MockMvcConfigurerAdapter;
import org.springframework.web.context.WebApplicationContext;
public class RestDocumentationConfiguration extends MockMvcConfigurerAdapter {
private String scheme = "http";
private String host = "localhost";
private int port = 8080;
public RestDocumentationConfiguration withScheme(String scheme) {
this.scheme = scheme;
return this;
}
public RestDocumentationConfiguration withHost(String host) {
this.host = host;
return this;
}
public RestDocumentationConfiguration withPort(int port) {
this.port = port;
return this;
}
@Override
public RequestPostProcessor beforeMockMvcCreated(
ConfigurableMockMvcBuilder<?> builder, WebApplicationContext context) {
return new RequestPostProcessor() {
@Override
public MockHttpServletRequest postProcessRequest(
MockHttpServletRequest request) {
request.setScheme(RestDocumentationConfiguration.this.scheme);
request.setRemotePort(RestDocumentationConfiguration.this.port);
request.setServerPort(RestDocumentationConfiguration.this.port);
request.setRemoteHost(RestDocumentationConfiguration.this.host);
return request;
}
};
}
}

View File

@@ -1,61 +0,0 @@
/*
* Copyright 2014 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.springframework.restdocs.core.RestDocumentationResultHandlers.documentCurlRequest;
import static org.springframework.restdocs.core.RestDocumentationResultHandlers.documentCurlRequestAndResponse;
import static org.springframework.restdocs.core.RestDocumentationResultHandlers.documentCurlResponse;
import java.util.Arrays;
import org.springframework.restdocs.core.RestDocumentationResultHandlers.LinkDocumentingResultHandler;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultHandler;
public class RestDocumentationResultHandler implements ResultHandler {
private final String outputDir;
private ResultHandler linkDocumentingResultHandler;
public RestDocumentationResultHandler(String outputDir) {
this.outputDir = outputDir;
}
@Override
public void handle(MvcResult result) throws Exception {
documentCurlRequest(this.outputDir).includeResponseHeaders().handle(result);
documentCurlResponse(this.outputDir).includeResponseHeaders().handle(result);
documentCurlRequestAndResponse(this.outputDir).includeResponseHeaders().handle(
result);
if (this.linkDocumentingResultHandler != null) {
this.linkDocumentingResultHandler.handle(result);
}
}
public RestDocumentationResultHandler withLinks(LinkDescriptor... descriptors) {
return withLinks(null, descriptors);
}
public RestDocumentationResultHandler withLinks(LinkExtractor linkExtractor,
LinkDescriptor... descriptors) {
this.linkDocumentingResultHandler = new LinkDocumentingResultHandler(
this.outputDir, linkExtractor, Arrays.asList(descriptors));
return this;
}
}

View File

@@ -1,319 +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.core;
import static org.junit.Assert.fail;
import static org.springframework.restdocs.core.IterableEnumeration.iterable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.StringWriter;
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.http.HttpStatus;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.restdocs.core.DocumentationWriter.DocumentationAction;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultHandler;
import org.springframework.util.Assert;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.RequestMethod;
public abstract class RestDocumentationResultHandlers {
public static CurlResultHandler documentCurlRequest(String outputDir) {
return new CurlResultHandler(outputDir, "request") {
@Override
public void handle(MvcResult result, DocumentationWriter writer)
throws Exception {
writer.shellCommand(new CurlRequestDocumentationAction(writer, result,
getCurlConfiguration()));
}
};
}
public static CurlResultHandler documentCurlResponse(String outputDir) {
return new CurlResultHandler(outputDir, "response") {
@Override
public void handle(MvcResult result, DocumentationWriter writer)
throws Exception {
writer.codeBlock("http", new CurlResponseDocumentationAction(writer,
result, getCurlConfiguration()));
}
};
}
public static CurlResultHandler documentCurlRequestAndResponse(String outputDir) {
return new CurlResultHandler(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.includeResponseHeaders) {
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 writer = new StringWriter();
FileCopyUtils.copy(request.getReader(), writer);
return writer.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.includeResponseHeaders) {
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());
}
}
private static class CurlConfiguration {
private boolean includeResponseHeaders = false;
}
public static abstract class RestDocumentationResultHandler implements ResultHandler {
private String outputDir;
private String fileName;
public RestDocumentationResultHandler(String outputDir, String fileName) {
this.outputDir = outputDir;
this.fileName = fileName;
}
abstract void handle(MvcResult result, DocumentationWriter writer)
throws Exception;
@Override
public void handle(MvcResult result) throws Exception {
PrintStream printStream = createPrintStream();
try {
handle(result, new DocumentationWriter(printStream));
}
finally {
printStream.close();
}
}
protected PrintStream createPrintStream() throws FileNotFoundException {
File outputFile = new File(this.outputDir, this.fileName + ".asciidoc");
if (!outputFile.isAbsolute()) {
outputFile = makeAbsolute(outputFile);
}
if (outputFile != null) {
outputFile.getParentFile().mkdirs();
return new PrintStream(new FileOutputStream(outputFile));
}
return System.out;
}
private static File makeAbsolute(File outputFile) {
File outputDir = new DocumentationProperties().getOutputDir();
if (outputDir != null) {
return new File(outputDir, outputFile.getPath());
}
return null;
}
}
public static abstract class CurlResultHandler extends RestDocumentationResultHandler {
private final CurlConfiguration curlConfiguration = new CurlConfiguration();
public CurlResultHandler(String outputDir, String fileName) {
super(outputDir, fileName);
}
CurlConfiguration getCurlConfiguration() {
return this.curlConfiguration;
}
public CurlResultHandler includeResponseHeaders() {
this.curlConfiguration.includeResponseHeaders = true;
return this;
}
}
static class LinkDocumentingResultHandler extends RestDocumentationResultHandler {
private final Map<String, LinkDescriptor> descriptorsByRel = new HashMap<String, LinkDescriptor>();
private final LinkExtractor extractor;
public LinkDocumentingResultHandler(String outputDir,
LinkExtractor linkExtractor, List<LinkDescriptor> 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
void handle(MvcResult result, DocumentationWriter writer) throws Exception {
Map<String, List<Link>> 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<String> actualRels = links.keySet();
Set<String> expectedRels = this.descriptorsByRel.keySet();
Set<String> undocumentedRels = new HashSet<String>(actualRels);
undocumentedRels.removeAll(expectedRels);
Set<String> missingRels = new HashSet<String>(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<String, LinkDescriptor> entry : this.descriptorsByRel.entrySet()) {
writer.println();
writer.println("| " + entry.getKey());
writer.println("| " + entry.getValue().getDescription());
}
writer.println("|===");
}
}
}

View File

@@ -0,0 +1,45 @@
/*
* 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;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
/**
* Static factory methods for documenting RESTful APIs using Spring MVC Test
*
* @author Andy Wilkinson
*/
public abstract class RestDocumentation {
private RestDocumentation() {
}
/**
* Documents the API call to the given {@code outputDir}.
*
* @param outputDir The directory to which the documentation will be written
* @return a Mock MVC {@code ResultHandler} that will produce the documentation
* @see MockMvc#perform(org.springframework.test.web.servlet.RequestBuilder)
* @see ResultActions#andDo(org.springframework.test.web.servlet.ResultHandler)
*/
public static RestDocumentationResultHandler document(String outputDir) {
return new RestDocumentationResultHandler(outputDir);
}
}

View File

@@ -0,0 +1,112 @@
/*
* 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;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder;
import org.springframework.test.web.servlet.setup.MockMvcConfigurer;
import org.springframework.test.web.servlet.setup.MockMvcConfigurerAdapter;
import org.springframework.web.context.WebApplicationContext;
/**
* A {@link MockMvcConfigurer} that can be used to configure the documentation
*
* @author Andy Wilkinson
* @see ConfigurableMockMvcBuilder#apply(MockMvcConfigurer)
*
*/
public class RestDocumentationConfigurer extends MockMvcConfigurerAdapter {
/**
* The default scheme for documented URIs
* @see #withScheme(String)
*/
public static final String DEFAULT_SCHEME = "http";
/**
* The defalt host for documented URIs
* @see #withHost(String)
*/
public static final String DEFAULT_HOST = "localhost";
/**
* The default port for documented URIs
* @see #withPort(int)
*/
public static final int DEFAULT_PORT = 8080;
private String scheme = DEFAULT_SCHEME;
private String host = DEFAULT_HOST;
private int port = DEFAULT_PORT;
/**
* Configures any documented URIs to use the given {@code scheme}. The default is
* {@code http}.
*
* @param scheme The URI scheme
* @return {@code this}
*/
public RestDocumentationConfigurer withScheme(String scheme) {
this.scheme = scheme;
return this;
}
/**
* Configures any documented URIs to use the given {@code host}. The default is
* {@code localhost}.
*
* @param host The URI host
* @return {@code this}
*/
public RestDocumentationConfigurer withHost(String host) {
this.host = host;
return this;
}
/**
* Configures any documented URIs to use the given {@code port}. The default is
* {@code 8080}.
*
* @param port The URI port
* @return {@code this}
*/
public RestDocumentationConfigurer withPort(int port) {
this.port = port;
return this;
}
@Override
public RequestPostProcessor beforeMockMvcCreated(
ConfigurableMockMvcBuilder<?> builder, WebApplicationContext context) {
return new RequestPostProcessor() {
@Override
public MockHttpServletRequest postProcessRequest(
MockHttpServletRequest request) {
request.setScheme(RestDocumentationConfigurer.this.scheme);
request.setRemotePort(RestDocumentationConfigurer.this.port);
request.setServerPort(RestDocumentationConfigurer.this.port);
request.setRemoteHost(RestDocumentationConfigurer.this.host);
return request;
}
};
}
}

View File

@@ -0,0 +1,100 @@
/*
* 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;
import static org.springframework.restdocs.curl.CurlDocumentation.documentCurlRequest;
import static org.springframework.restdocs.curl.CurlDocumentation.documentCurlRequestAndResponse;
import static org.springframework.restdocs.curl.CurlDocumentation.documentCurlResponse;
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.documentLinks;
import java.util.ArrayList;
import java.util.List;
import org.springframework.restdocs.hypermedia.HypermediaDocumentation;
import org.springframework.restdocs.hypermedia.LinkDescriptor;
import org.springframework.restdocs.hypermedia.LinkExtractor;
import org.springframework.restdocs.hypermedia.LinkExtractors;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultHandler;
/**
* A Spring MVC Test {@code ResultHandler} for documenting RESTful APIs.
*
* @author Andy Wilkinson
* @see RestDocumentation#document(String)
*/
public class RestDocumentationResultHandler implements ResultHandler {
private final String outputDir;
private List<ResultHandler> delegates;
RestDocumentationResultHandler(String outputDir) {
this.outputDir = outputDir;
this.delegates = new ArrayList<ResultHandler>();
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.
* <p>
* 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}.
* <p>
* 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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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());
}
}
}

View File

@@ -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;
}
}

View File

@@ -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));
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.restdocs.core;
package org.springframework.restdocs.hypermedia;
import org.springframework.core.style.ToStringCreator;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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();

View File

@@ -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<String, LinkDescriptor> descriptorsByRel = new HashMap<String, LinkDescriptor>();
private final LinkExtractor extractor;
LinkSnippetResultHandler(String outputDir, LinkExtractor linkExtractor,
List<LinkDescriptor> 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<String, List<Link>> 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<String> actualRels = links.keySet();
Set<String> expectedRels = this.descriptorsByRel.keySet();
Set<String> undocumentedRels = new HashSet<String>(actualRels);
undocumentedRels.removeAll(expectedRels);
Set<String> missingRels = new HashSet<String>(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<String, LinkDescriptor> entry : this.descriptorsByRel.entrySet()) {
writer.println();
writer.println("| " + entry.getKey());
writer.println("| " + entry.getValue().getDescription());
}
writer.println("|===");
}
}

View File

@@ -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 <a
* href="http://asciidoctor.org">Asciidoctor</a>.
*
* @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;
}
}

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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<T> implements Iterable<T> {
/**
* An adapter to expose an {@link Enumeration} as an {@link Iterable}.
*
* @author Andy Wilkinson
*
* @param <T> the type of the Enumeration's contents
*/
public class IterableEnumeration<T> implements Iterable<T> {
private final Enumeration<T> enumeration;
public IterableEnumeration(Enumeration<T> enumeration) {
private IterableEnumeration(Enumeration<T> enumeration) {
this.enumeration = enumeration;
}
@@ -49,6 +56,13 @@ public final class IterableEnumeration<T> implements Iterable<T> {
};
}
/**
* Creates a new {@code Iterable} that will iterate over the given {@code enumeration}
*
* @param <T> the type of the enumeration's elements
* @param enumeration The enumeration to expose as an {@code Iterable}
* @return the iterable
*/
public static <T> Iterable<T> iterable(Enumeration<T> enumeration) {
return new IterableEnumeration<T>(enumeration);
}

View File

@@ -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;
/**

View File

@@ -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());
}
}

View File

@@ -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());
}
}