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:
@@ -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 java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
class DocumentationProperties {
|
||||
|
||||
private final Properties properties = new Properties();
|
||||
|
||||
DocumentationProperties() {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
this.properties.putAll(System.getProperties());
|
||||
}
|
||||
|
||||
File getOutputDir() {
|
||||
String outputDir = this.properties
|
||||
.getProperty("org.springframework.restdocs.outputDir");
|
||||
if (StringUtils.hasText(outputDir)) {
|
||||
return new File(outputDir).getAbsoluteFile();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,60 +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 java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
public class DocumentationWriter extends PrintWriter {
|
||||
|
||||
public DocumentationWriter(OutputStream stream) {
|
||||
super(stream, true);
|
||||
}
|
||||
|
||||
public void shellCommand(final DocumentationAction... actions) throws Exception {
|
||||
codeBlock("bash", new DocumentationAction() {
|
||||
|
||||
@Override
|
||||
public void perform() throws Exception {
|
||||
DocumentationWriter.this.print("$ ");
|
||||
for (DocumentationAction action : actions) {
|
||||
action.perform();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void codeBlock(String language, DocumentationAction... actions)
|
||||
throws Exception {
|
||||
println();
|
||||
if (language != null) {
|
||||
println("[source," + language + "]");
|
||||
}
|
||||
println("----");
|
||||
for (DocumentationAction action : actions) {
|
||||
action.perform();
|
||||
}
|
||||
println("----");
|
||||
println();
|
||||
}
|
||||
|
||||
public interface DocumentationAction {
|
||||
|
||||
void perform() throws Exception;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,56 +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 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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> Iterable<T> iterable(Enumeration<T> enumeration) {
|
||||
return new IterableEnumeration<T>(enumeration);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,95 +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 org.springframework.core.style.ToStringCreator;
|
||||
|
||||
/**
|
||||
* Representation of a link used in a Hypermedia-based API
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class Link {
|
||||
|
||||
private final String rel;
|
||||
|
||||
private final String href;
|
||||
|
||||
/**
|
||||
* Creates a new {@code Link} with the given {@code rel} and {@code href}
|
||||
*
|
||||
* @param rel The link's rel
|
||||
* @param href The link's href
|
||||
*/
|
||||
public Link(String rel, String href) {
|
||||
this.rel = rel;
|
||||
this.href = href;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the link's {@code rel}
|
||||
* @return the link's {@code rel}
|
||||
*/
|
||||
public String getRel() {
|
||||
return this.rel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the link's {@code href}
|
||||
* @return the link's {@code href}
|
||||
*/
|
||||
public String getHref() {
|
||||
return this.href;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + this.href.hashCode();
|
||||
result = prime * result + this.rel.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Link other = (Link) obj;
|
||||
if (!this.href.equals(other.href)) {
|
||||
return false;
|
||||
}
|
||||
if (!this.rel.equals(other.rel)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringCreator(this).append("rel", this.rel)
|
||||
.append("href", this.href).toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,41 +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;
|
||||
|
||||
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 this.rel;
|
||||
}
|
||||
|
||||
String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
}
|
||||
@@ -1,45 +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 java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
|
||||
/**
|
||||
* A {@code LinkExtractor} is used to extract {@link Link links} from a JSON response. The
|
||||
* expected format of the links in the response is determined by the implementation.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*
|
||||
*/
|
||||
public interface LinkExtractor {
|
||||
|
||||
/**
|
||||
* Extract the links from the given response, returning a {@code Map} of links where
|
||||
* the keys are the link rels.
|
||||
*
|
||||
* @param response The response from which the links are to be extracted
|
||||
* @return The extracted links, keyed by rel
|
||||
* @throws IOException if link extraction fails
|
||||
*/
|
||||
Map<String, List<Link>> extractLinks(MockHttpServletResponse response)
|
||||
throws IOException;
|
||||
|
||||
}
|
||||
@@ -1,180 +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 java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* Static factory methods providing a selection of {@link LinkExtractor link extractors}
|
||||
* for use when documentating a hypermedia-based API.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class LinkExtractors {
|
||||
|
||||
/**
|
||||
* Returns a {@code LinkExtractor} capable of extracting links in Hypermedia
|
||||
* Application Language (HAL) format where the links are found in a map named
|
||||
* {@code _links}.
|
||||
*
|
||||
* @return The extract for HAL-style links
|
||||
*/
|
||||
public static LinkExtractor halLinks() {
|
||||
return new HalLinkExtractor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@code LinkExtractor} capable of extracting links in Atom format where
|
||||
* the links are found in an array named {@code links}.
|
||||
*
|
||||
* @return The extractor for Atom-style links
|
||||
*/
|
||||
public static LinkExtractor atomLinks() {
|
||||
return new AtomLinkExtractor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code LinkExtractor} for the given {@code contentType} or {@code null}
|
||||
* if there is no extractor for the content type.
|
||||
*
|
||||
* @param contentType The content type
|
||||
* @return The extractor for the content type, or {@code null}
|
||||
*/
|
||||
public static LinkExtractor extractorForContentType(String contentType) {
|
||||
if (MediaType.APPLICATION_JSON_VALUE.equals(contentType)) {
|
||||
return atomLinks();
|
||||
}
|
||||
else if ("application/hal+json".equals(contentType)) {
|
||||
return halLinks();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static abstract class JsonContentLinkExtractor implements LinkExtractor {
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Map<String, List<Link>> extractLinks(MockHttpServletResponse response)
|
||||
throws IOException {
|
||||
Map<String, Object> jsonContent = this.objectMapper.readValue(
|
||||
response.getContentAsString(), Map.class);
|
||||
return extractLinks(jsonContent);
|
||||
}
|
||||
|
||||
protected abstract Map<String, List<Link>> extractLinks(Map<String, Object> json);
|
||||
}
|
||||
|
||||
private static class HalLinkExtractor extends JsonContentLinkExtractor {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Map<String, List<Link>> extractLinks(Map<String, Object> json) {
|
||||
Map<String, List<Link>> extractedLinks = new HashMap<>();
|
||||
Object possibleLinks = json.get("_links");
|
||||
if (possibleLinks instanceof Map) {
|
||||
Map<String, Object> links = (Map<String, Object>) possibleLinks;
|
||||
for (Entry<String, Object> entry : links.entrySet()) {
|
||||
String rel = entry.getKey();
|
||||
extractedLinks.put(rel, convertToLinks(entry.getValue(), rel));
|
||||
}
|
||||
}
|
||||
return extractedLinks;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static List<Link> convertToLinks(Object object, String rel) {
|
||||
List<Link> links = new ArrayList<>();
|
||||
if (object instanceof Collection) {
|
||||
Collection<Object> hrefObjects = (Collection<Object>) object;
|
||||
for (Object hrefObject : hrefObjects) {
|
||||
maybeAddLink(maybeCreateLink(rel, hrefObject), links);
|
||||
}
|
||||
}
|
||||
else {
|
||||
maybeAddLink(maybeCreateLink(rel, object), links);
|
||||
}
|
||||
return links;
|
||||
}
|
||||
|
||||
private static Link maybeCreateLink(String rel, Object possibleHref) {
|
||||
if (possibleHref instanceof String) {
|
||||
return new Link(rel, (String) possibleHref);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void maybeAddLink(Link possibleLink, List<Link> links) {
|
||||
if (possibleLink != null) {
|
||||
links.add(possibleLink);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class AtomLinkExtractor extends JsonContentLinkExtractor {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Map<String, List<Link>> extractLinks(Map<String, Object> json) {
|
||||
Map<String, List<Link>> extractedLinks = new HashMap<>();
|
||||
Object possibleLinks = json.get("links");
|
||||
if (possibleLinks instanceof Collection) {
|
||||
Collection<Object> linksCollection = (Collection<Object>) possibleLinks;
|
||||
for (Object linkObject : linksCollection) {
|
||||
if (linkObject instanceof Map) {
|
||||
Link link = maybeCreateLink((Map<String, Object>) linkObject);
|
||||
maybeStoreLink(link, extractedLinks);
|
||||
}
|
||||
}
|
||||
}
|
||||
return extractedLinks;
|
||||
}
|
||||
|
||||
private static Link maybeCreateLink(Map<String, Object> linkMap) {
|
||||
Object hrefObject = linkMap.get("href");
|
||||
Object relObject = linkMap.get("rel");
|
||||
if (relObject instanceof String && hrefObject instanceof String) {
|
||||
return new Link((String) relObject, (String) hrefObject);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void maybeStoreLink(Link link,
|
||||
Map<String, List<Link>> extractedLinks) {
|
||||
if (link != null) {
|
||||
List<Link> linksForRel = extractedLinks.get(link.getRel());
|
||||
if (linksForRel == null) {
|
||||
linksForRel = new ArrayList<Link>();
|
||||
extractedLinks.put(link.getRel(), linksForRel);
|
||||
}
|
||||
linksForRel.add(link);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +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;
|
||||
|
||||
public class RestDocumentation {
|
||||
|
||||
public static RestDocumentationResultHandler document(String outputDir)
|
||||
throws Exception {
|
||||
return new RestDocumentationResultHandler(outputDir);
|
||||
}
|
||||
|
||||
public static LinkDescriptor linkWithRel(String rel) {
|
||||
return new LinkDescriptor(rel);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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("|===");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,124 +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.assertEquals;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
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.util.FileCopyUtils;
|
||||
|
||||
/**
|
||||
* Tests for {@link LinkExtractors}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@RunWith(Parameterized.class)
|
||||
public class LinkExtractorsTests {
|
||||
|
||||
private final LinkExtractor linkExtractor;
|
||||
|
||||
private final String linkType;
|
||||
|
||||
@Parameters
|
||||
public static Collection<Object[]> data() {
|
||||
return Arrays.asList(new Object[] { LinkExtractors.halLinks(), "hal" },
|
||||
new Object[] { LinkExtractors.atomLinks(), "atom" });
|
||||
}
|
||||
|
||||
public LinkExtractorsTests(LinkExtractor linkExtractor, String linkType) {
|
||||
this.linkExtractor = linkExtractor;
|
||||
this.linkType = linkType;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void singleLink() throws IOException {
|
||||
Map<String, List<Link>> links = this.linkExtractor
|
||||
.extractLinks(createResponse("single-link"));
|
||||
assertLinks(Arrays.asList(new Link("alpha", "http://alpha.example.com")), links);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleLinksWithDifferentRels() throws IOException {
|
||||
Map<String, List<Link>> links = this.linkExtractor
|
||||
.extractLinks(createResponse("multiple-links-different-rels"));
|
||||
assertLinks(Arrays.asList(new Link("alpha", "http://alpha.example.com"),
|
||||
new Link("bravo", "http://bravo.example.com")), links);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleLinksWithSameRels() throws IOException {
|
||||
Map<String, List<Link>> links = this.linkExtractor
|
||||
.extractLinks(createResponse("multiple-links-same-rels"));
|
||||
assertLinks(Arrays.asList(new Link("alpha", "http://alpha.example.com/one"),
|
||||
new Link("alpha", "http://alpha.example.com/two")), links);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noLinks() throws IOException {
|
||||
Map<String, List<Link>> links = this.linkExtractor
|
||||
.extractLinks(createResponse("no-links"));
|
||||
assertLinks(Collections.<Link> emptyList(), links);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void linksInTheWrongFormat() throws IOException {
|
||||
Map<String, List<Link>> links = this.linkExtractor
|
||||
.extractLinks(createResponse("wrong-format"));
|
||||
assertLinks(Collections.<Link> emptyList(), links);
|
||||
}
|
||||
|
||||
private void assertLinks(List<Link> expectedLinks, Map<String, List<Link>> actualLinks) {
|
||||
Map<String, List<Link>> expectedLinksByRel = new HashMap<>();
|
||||
for (Link expectedLink : expectedLinks) {
|
||||
List<Link> expectedlinksWithRel = expectedLinksByRel.get(expectedLink
|
||||
.getRel());
|
||||
if (expectedlinksWithRel == null) {
|
||||
expectedlinksWithRel = new ArrayList<>();
|
||||
expectedLinksByRel.put(expectedLink.getRel(), expectedlinksWithRel);
|
||||
}
|
||||
expectedlinksWithRel.add(expectedLink);
|
||||
}
|
||||
assertEquals(expectedLinksByRel, actualLinks);
|
||||
}
|
||||
|
||||
private MockHttpServletResponse createResponse(String contentName) throws IOException {
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FileCopyUtils.copy(new FileReader(getPayloadFile(contentName)),
|
||||
response.getWriter());
|
||||
return response;
|
||||
}
|
||||
|
||||
private File getPayloadFile(String name) {
|
||||
return new File("src/test/resources/link-payloads/" + this.linkType + "/" + name
|
||||
+ ".json");
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"links": [ {
|
||||
"rel": "alpha",
|
||||
"href": "http://alpha.example.com"
|
||||
}, {
|
||||
"rel": "bravo",
|
||||
"href": "http://bravo.example.com"
|
||||
} ]
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"links": [ {
|
||||
"rel": "alpha",
|
||||
"href": "http://alpha.example.com/one"
|
||||
}, {
|
||||
"rel": "alpha",
|
||||
"href": "http://alpha.example.com/two"
|
||||
} ]
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{ }
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"links": [ {
|
||||
"rel": "alpha",
|
||||
"href": "http://alpha.example.com"
|
||||
} ]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"_links": {
|
||||
"alpha": ["http://alpha.example.com/one", "http://alpha.example.com/two"]
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"_links": {
|
||||
"alpha": "http://alpha.example.com",
|
||||
"bravo": "http://bravo.example.com"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"_links": {
|
||||
"alpha": ["http://alpha.example.com/one", "http://alpha.example.com/two"]
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{ }
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"_links": {
|
||||
"alpha": "http://alpha.example.com"
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"_links": [ {
|
||||
"rel": "alpha",
|
||||
"href": "http://alpha.example.com/one"
|
||||
}, {
|
||||
"rel": "alpha",
|
||||
"href": "http://alpha.example.com/two"
|
||||
} ]
|
||||
}
|
||||
Reference in New Issue
Block a user