Initial commit

This commit is contained in:
Andy Wilkinson
2014-10-08 12:47:13 +01:00
commit b7904bcad7
64 changed files with 4937 additions and 0 deletions

View File

@@ -0,0 +1,55 @@
/*
* 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 java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import org.springframework.restdocs.core.DocumentationWriter.DocumentationAction;
public abstract class Documentation {
public static void document(String path, DocumentationAction action) throws Exception {
PrintStream printStream = createPrintStream(path);
try {
DocumentationContext.set(new DocumentationContext(printStream));
action.perform();
}
finally {
DocumentationContext.set(null);
printStream.close();
}
}
private static PrintStream createPrintStream(String name)
throws FileNotFoundException {
File outputFile = new File(name);
if (!outputFile.isAbsolute()) {
outputFile = makeAbsolute(outputFile);
}
outputFile.getParentFile().mkdirs();
return new PrintStream(new FileOutputStream(outputFile));
}
private static File makeAbsolute(File outputFile) {
return new File(new DocumentationProperties().getOutputDir(),
outputFile.getPath());
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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 java.io.PrintStream;
class DocumentationContext {
private static final InheritableThreadLocal<DocumentationContext> CONTEXTS = new InheritableThreadLocal<DocumentationContext>();
private final DocumentationWriter writer;
public DocumentationContext(PrintStream printStream) {
this.writer = new DocumentationWriter(printStream);
}
public DocumentationWriter getWriter() {
return this.writer;
}
public static DocumentationContext current() {
return CONTEXTS.get();
}
static void set(DocumentationContext context) {
CONTEXTS.set(context);
}
}

View File

@@ -0,0 +1,57 @@
/*
* 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 java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
class DocumentationProperties {
private final Properties properties = new Properties();
DocumentationProperties() {
this.properties.putAll(System.getProperties());
InputStream stream = getClass().getClassLoader().getResourceAsStream(
"documentation.properties");
if (stream != null) {
try {
this.properties.load(stream);
}
catch (IOException ex) {
throw new IllegalStateException(
"Failed to read documentation.properties", ex);
}
finally {
try {
stream.close();
}
catch (IOException e) {
// Continue
}
}
}
}
File getOutputDir() {
return new File(this.properties.getProperty(
"org.springframework.restdocs.outputDir", "generated-documentation"))
.getAbsoluteFile();
}
}

View File

@@ -0,0 +1,71 @@
/*
* 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 java.io.OutputStream;
import java.io.PrintWriter;
public class DocumentationWriter extends PrintWriter {
private boolean escapeNewline = false;
public DocumentationWriter(OutputStream stream) {
super(stream, true);
}
public void shellCommand(final DocumentationAction... actions) throws Exception {
codeBlock(new DocumentationAction() {
@Override
public void perform() throws Exception {
DocumentationWriter.this.print("$ ");
DocumentationWriter.this.escapeNewline = true;
try {
for (DocumentationAction action : actions) {
action.perform();
}
}
finally {
DocumentationWriter.this.escapeNewline = false;
}
}
});
}
@Override
public void write(String s) {
if (this.escapeNewline) {
s = s.replace("\n", "\\\n");
}
super.write(s);
}
public void codeBlock(DocumentationAction... actions) throws Exception {
println();
println("----");
for (DocumentationAction action : actions) {
action.perform();
}
println("----");
println();
}
public interface DocumentationAction {
void perform() throws Exception;
}
}

View File

@@ -0,0 +1,51 @@
/*
* 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 java.util.Enumeration;
import java.util.Iterator;
public final class IterableEnumeration<T> implements Iterable<T> {
private final Enumeration<T> enumeration;
public IterableEnumeration(Enumeration<T> enumeration) {
this.enumeration = enumeration;
}
@Override
public Iterator<T> iterator() {
return new Iterator<T>() {
@Override
public boolean hasNext() {
return IterableEnumeration.this.enumeration.hasMoreElements();
}
@Override
public T next() {
return IterableEnumeration.this.enumeration.nextElement();
}
};
}
public static <T> Iterable<T> iterable(Enumeration<T> enumeration) {
return new IterableEnumeration<T>(enumeration);
}
}

View File

@@ -0,0 +1,51 @@
/*
* 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;
public abstract class RestDocumentationRequestPostProcessors {
public static RequestPostProcessor port(final int port) {
return new RequestPostProcessor() {
@Override
public MockHttpServletRequest postProcessRequest(
MockHttpServletRequest request) {
request.setRemotePort(port);
request.setServerPort(port);
return request;
}
};
}
public static RequestPostProcessor host(final String host) {
return new RequestPostProcessor() {
@Override
public MockHttpServletRequest postProcessRequest(
MockHttpServletRequest request) {
request.setRemoteHost(host);
return request;
}
};
}
}

View File

@@ -0,0 +1,216 @@
/*
* 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 java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.StringWriter;
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.FileCopyUtils;
import org.springframework.web.bind.annotation.RequestMethod;
import static org.springframework.restdocs.core.IterableEnumeration.iterable;
public abstract class RestDocumentationResultHandlers {
public static CurlResultHandler documentCurlRequest(String path) {
return new CurlResultHandler(path) {
@Override
public void handle(MvcResult result, DocumentationWriter writer)
throws Exception {
writer.shellCommand(new CurlRequestDocumentationAction(writer, result,
getCurlConfiguration()));
}
};
}
public static CurlResultHandler documentCurlResponse(String path) {
return new CurlResultHandler(path) {
@Override
public void handle(MvcResult result, DocumentationWriter writer)
throws Exception {
writer.codeBlock(new CurlResponseDocumentationAction(writer, result,
getCurlConfiguration()));
}
};
}
public static CurlResultHandler documentCurlRequestAndResponse(String path) {
return new CurlResultHandler(path) {
@Override
public void handle(MvcResult result, DocumentationWriter writer)
throws Exception {
writer.shellCommand(new CurlRequestDocumentationAction(writer, result,
getCurlConfiguration()));
writer.codeBlock(new CurlResponseDocumentationAction(writer, result,
getCurlConfiguration()));
}
};
}
private static PrintStream createPrintStream(String path)
throws FileNotFoundException {
File outputFile = new File(path);
if (!outputFile.isAbsolute()) {
outputFile = makeAbsolute(outputFile);
}
outputFile.getParentFile().mkdirs();
return new PrintStream(new FileOutputStream(outputFile));
}
private static File makeAbsolute(File outputFile) {
return new File(new DocumentationProperties().getOutputDir(),
outputFile.getPath());
}
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 CurlResultHandler implements ResultHandler {
private final CurlConfiguration curlConfiguration = new CurlConfiguration();
private final String path;
private CurlResultHandler(String path) {
this.path = path;
}
CurlConfiguration getCurlConfiguration() {
return this.curlConfiguration;
}
public CurlResultHandler includeResponseHeaders() {
this.curlConfiguration.includeResponseHeaders = true;
return this;
}
@Override
public void handle(MvcResult result) throws Exception {
PrintStream printStream = createPrintStream(this.path);
try {
handle(result, new DocumentationWriter(printStream));
}
finally {
printStream.close();
}
}
abstract void handle(MvcResult result, DocumentationWriter writer)
throws Exception;
}
}