Add support for determining the link extractor from the content type

Previously, when documenting links, it was necessary to provide the
link extractor that should be used to get the links from the response.
This commit improves on this by adding support for determining the
link extractor to use based on the response's content type. Two
content types are currently supported; application/hal+json and
application/json. For other content types, an extractor can be
provided as before when calling withLinks.

Closes #7
This commit is contained in:
Andy Wilkinson
2015-01-13 16:41:38 +00:00
parent b57eb002dc
commit cae7e3fb58
6 changed files with 48 additions and 13 deletions

View File

@@ -19,7 +19,6 @@ 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.LinkExtractors.halLinks;
import static org.springframework.restdocs.core.RestDocumentation.linkWithRel;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
@@ -95,7 +94,7 @@ public class ApiDocumentation {
public void indexExample() throws Exception {
this.mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andDo(document("index-example").withLinks(halLinks(),
.andDo(document("index-example").withLinks(
linkWithRel("notes").description(
"The <<resources-notes,Notes resource>>"),
linkWithRel("tags").description(
@@ -175,7 +174,7 @@ public class ApiDocumentation {
.andExpect(jsonPath("_links.self.href", is(noteLocation)))
.andExpect(jsonPath("_links.tags", is(notNullValue())))
.andDo(document("note-get-example")
.withLinks(halLinks(),
.withLinks(
linkWithRel("self").description("This <<resources-note,note>>"),
linkWithRel("tags").description(
"This note's <<resources-note-tags,tags>>")));
@@ -262,7 +261,7 @@ public class ApiDocumentation {
this.mockMvc.perform(get(tagLocation))
.andExpect(status().isOk())
.andExpect(jsonPath("name", is(tag.get("name"))))
.andDo(document("tag-get-example").withLinks(halLinks(),
.andDo(document("tag-get-example").withLinks(
linkWithRel("self").description("This <<resources-tag,tag>>"),
linkWithRel("notes")
.description(

View File

@@ -18,7 +18,6 @@ package com.example.notes;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.springframework.restdocs.core.LinkExtractors.halLinks;
import static org.springframework.restdocs.core.RestDocumentation.document;
import static org.springframework.restdocs.core.RestDocumentation.linkWithRel;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@@ -95,7 +94,7 @@ public class ApiDocumentation {
public void indexExample() throws Exception {
this.mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andDo(document("index-example").withLinks(halLinks(),
.andDo(document("index-example").withLinks(
linkWithRel("notes").description(
"The <<resources-notes,Notes resource>>"),
linkWithRel("tags").description(
@@ -171,7 +170,7 @@ public class ApiDocumentation {
.andExpect(jsonPath("body", is(note.get("body"))))
.andExpect(jsonPath("_links.self.href", is(noteLocation)))
.andExpect(jsonPath("_links.note-tags", is(notNullValue())))
.andDo(document("note-get-example").withLinks(halLinks(),
.andDo(document("note-get-example").withLinks(
linkWithRel("self").description("This <<resources-note,note>>"),
linkWithRel("note-tags").description(
"This note's <<resources-note-tags,tags>>")));
@@ -258,7 +257,7 @@ public class ApiDocumentation {
this.mockMvc.perform(get(tagLocation))
.andExpect(status().isOk())
.andExpect(jsonPath("name", is(tag.get("name"))))
.andDo(document("tag-get-example").withLinks(halLinks(),
.andDo(document("tag-get-example").withLinks(
linkWithRel("self").description("This <<resources-tag,tag>>"),
linkWithRel("tagged-notes")
.description(

View File

@@ -120,6 +120,6 @@ sp_cleanup.use_lambda=true
sp_cleanup.use_parentheses_in_expressions=false
sp_cleanup.use_this_for_non_static_field_access=true
sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=false
sp_cleanup.use_this_for_non_static_method_access=true
sp_cleanup.use_this_for_non_static_method_access=false
sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=false
sp_cleanup.use_type_arguments=false

View File

@@ -24,16 +24,16 @@ 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 provided a selection of {@link LinkExtractor link extractors}
* 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 {
@@ -58,6 +58,23 @@ public class LinkExtractors {
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();

View File

@@ -47,6 +47,10 @@ public class RestDocumentationResultHandler implements ResultHandler {
}
}
public RestDocumentationResultHandler withLinks(LinkDescriptor... descriptors) {
return withLinks(null, descriptors);
}
public RestDocumentationResultHandler withLinks(LinkExtractor linkExtractor,
LinkDescriptor... descriptors) {
this.linkDocumentingResultHandler = new LinkDocumentingResultHandler(

View File

@@ -260,8 +260,24 @@ public abstract class RestDocumentationResultHandlers {
@Override
void handle(MvcResult result, DocumentationWriter writer) throws Exception {
Map<String, List<Link>> links = this.extractor.extractLinks(result
.getResponse());
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();