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:
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user