Add support for automating the documentation of HAL Links

This commit is contained in:
Andy Wilkinson
2014-11-04 21:21:40 +00:00
parent d06095ee72
commit 2b858c5cd9
8 changed files with 244 additions and 61 deletions

View File

@@ -0,0 +1,41 @@
/*
* 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;
public class LinkDescriptor {
private final String rel;
private String description;
public LinkDescriptor(String rel) {
this.rel = rel;
}
public LinkDescriptor description(String description) {
this.description = description;
return this;
}
String getRel() {
return rel;
}
String getDescription() {
return description;
}
}

View File

@@ -16,19 +16,23 @@
package org.springframework.restdocs.core;
import org.springframework.test.web.servlet.ResultActions;
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 org.springframework.test.web.servlet.ResultActions;
public class RestDocumentation {
public static ResultActions document(String outputDir, ResultActions resultActions)
throws Exception {
return resultActions
public static RestDocumentationResultActions document(String outputDir,
ResultActions resultActions) throws Exception {
return new RestDocumentationResultActions(outputDir, resultActions)
.andDo(documentCurlRequest(outputDir).includeResponseHeaders())
.andDo(documentCurlResponse(outputDir).includeResponseHeaders())
.andDo(documentCurlRequestAndResponse(outputDir).includeResponseHeaders());
}
public static LinkDescriptor linkWithRel(String rel) {
return new LinkDescriptor(rel);
}
}

View File

@@ -0,0 +1,63 @@
/*
* 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.Arrays;
import org.springframework.restdocs.core.RestDocumentationResultHandlers.LinkDocumentingResultHandler;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.ResultHandler;
import org.springframework.test.web.servlet.ResultMatcher;
public class RestDocumentationResultActions implements ResultActions {
private final ResultActions delegate;
private final String outputDir;
public RestDocumentationResultActions(String outputDir, ResultActions delegate) {
this.outputDir = outputDir;
this.delegate = delegate;
}
@Override
public RestDocumentationResultActions andExpect(ResultMatcher matcher)
throws Exception {
this.delegate.andExpect(matcher);
return this;
}
@Override
public RestDocumentationResultActions andDo(ResultHandler handler) throws Exception {
this.delegate.andDo(handler);
return this;
}
@Override
public MvcResult andReturn() {
return this.delegate.andReturn();
}
public RestDocumentationResultActions andDocumentHalLinks(LinkDescriptor... descriptors)
throws Exception {
this.delegate.andDo(new LinkDocumentingResultHandler(this.outputDir, Arrays
.asList(descriptors)));
return this;
}
}

View File

@@ -17,6 +17,7 @@
package org.springframework.restdocs.core;
import static org.springframework.restdocs.core.IterableEnumeration.iterable;
import static org.junit.Assert.fail;
import java.io.File;
import java.io.FileNotFoundException;
@@ -24,15 +25,24 @@ 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;
import com.fasterxml.jackson.databind.ObjectMapper;
public abstract class RestDocumentationResultHandlers {
public static CurlResultHandler documentCurlRequest(String outputDir) {
@@ -163,27 +173,19 @@ public abstract class RestDocumentationResultHandlers {
}
public static abstract class CurlResultHandler implements ResultHandler {
private final CurlConfiguration curlConfiguration = new CurlConfiguration();
public static abstract class RestDocumentationResultHandler implements ResultHandler {
private String outputDir;
private String fileName;
public CurlResultHandler(String outputDir, String fileName) {
public RestDocumentationResultHandler(String outputDir, String fileName) {
this.outputDir = outputDir;
this.fileName = fileName;
}
CurlConfiguration getCurlConfiguration() {
return this.curlConfiguration;
}
public CurlResultHandler includeResponseHeaders() {
this.curlConfiguration.includeResponseHeaders = true;
return this;
}
abstract void handle(MvcResult result, DocumentationWriter writer)
throws Exception;
@Override
public void handle(MvcResult result) throws Exception {
@@ -196,7 +198,7 @@ public abstract class RestDocumentationResultHandlers {
}
}
private PrintStream createPrintStream()
protected PrintStream createPrintStream()
throws FileNotFoundException {
File outputFile = new File(this.outputDir, this.fileName + ".asciidoc");
@@ -212,8 +214,80 @@ public abstract class RestDocumentationResultHandlers {
return new File(new DocumentationProperties().getOutputDir(),
outputFile.getPath());
}
}
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 ObjectMapper objectMapper = new ObjectMapper();
private final Map<String, LinkDescriptor> descriptorsByRel = new HashMap<String, LinkDescriptor>();
public LinkDocumentingResultHandler(String outputDir, List<LinkDescriptor> descriptors) {
super(outputDir, "links");
for (LinkDescriptor descriptor: descriptors) {
Assert.hasText(descriptor.getRel());
Assert.hasText(descriptor.getDescription());
this.descriptorsByRel.put(descriptor.getRel(), descriptor);
}
}
@SuppressWarnings("unchecked")
@Override
void handle(MvcResult result, DocumentationWriter writer) throws Exception {
Map<String, Object> json = this.objectMapper.readValue(result.getResponse().getContentAsString(), Map.class);
Map<String, Object> links = (Map<String, Object>) json.get("_links");
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("|===");
}
abstract void handle(MvcResult result, DocumentationWriter writer)
throws Exception;
}
}