From c823296db0edaf427adbc5a396e10a8e65ff4b12 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 23 Oct 2014 11:37:24 +0100 Subject: [PATCH] Remove the need for manual documentCurlRequest/Response calls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, whenever a MockMvc perform call was made and the request and response were to be documented, calls to documentCurlRequest and documentCurlResponse had to be made. This commit updates the REST documentation framework to make the documentation of the request and response automatic. It makes use of MockMvc’s MockMvcConfigurer that was introduced in Spring 4.1. Applying RestDocumentationConfiguration once will cause all requests and responses to be documented: this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(new RestDocumentationConfiguration()).build(); The path to which the requests and response documentation snippets will be written is determined by the class and method in which the MockMvc perform call is made and is currently of the form shortClassName/methodName(Request|Response|RequestResponse).asciidoc where shortClassName is the name of the documentation class without its package. This works for both @Test methods and any non-private methods that are called from the test method. The methods must be non-private as a CGLib proxy is used to intercept the calls and configure the output path. --- build.gradle | 2 +- rest-notes-spring-data-rest/build.gradle | 10 +- .../src/documentation/asciidoc/main.asciidoc | 42 +++--- .../notes/GettingStartedDocumentation.java | 136 ++++++------------ rest-notes-spring-hateoas/build.gradle | 10 +- .../src/documentation/asciidoc/main.asciidoc | 42 +++--- .../notes/GettingStartedDocumentation.java | 136 ++++++------------ .../com/example/notes/NotesController.java | 1 + .../restdocs/core/Documentation.java | 55 ------- .../restdocs/core/DocumentationContext.java | 42 ++++-- .../core/RestDocumentationConfiguration.java | 76 ++++++++++ .../RestDocumentationJUnit4ClassRunner.java | 70 +++++++++ ...estDocumentationRequestPostProcessors.java | 51 ------- .../core/RestDocumentationResultHandlers.java | 80 +++++++---- 14 files changed, 373 insertions(+), 380 deletions(-) delete mode 100644 spring-restdocs-core/src/main/java/org/springframework/restdocs/core/Documentation.java create mode 100644 spring-restdocs-core/src/main/java/org/springframework/restdocs/core/RestDocumentationConfiguration.java create mode 100644 spring-restdocs-core/src/main/java/org/springframework/restdocs/core/RestDocumentationJUnit4ClassRunner.java delete mode 100644 spring-restdocs-core/src/main/java/org/springframework/restdocs/core/RestDocumentationRequestPostProcessors.java diff --git a/build.gradle b/build.gradle index 8e35513f..0bf08fc8 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ allprojects { ext { - springVersion = '4.0.7.RELEASE' + springVersion = '4.1.1.RELEASE' } group = 'org.springframework.restdocs' } diff --git a/rest-notes-spring-data-rest/build.gradle b/rest-notes-spring-data-rest/build.gradle index 80765b54..95b693ae 100644 --- a/rest-notes-spring-data-rest/build.gradle +++ b/rest-notes-spring-data-rest/build.gradle @@ -1,11 +1,13 @@ buildscript { repositories { mavenLocal() - jcenter() + maven { + url 'https://repo.spring.io/libs-milestone' + } } dependencies { classpath 'org.springframework.restdocs:spring-restdocs-gradle-plugin:0.1.0.BUILD-SNAPSHOT' - classpath 'org.springframework.boot:spring-boot-gradle-plugin:1.1.7.RELEASE' + classpath 'org.springframework.boot:spring-boot-gradle-plugin:1.2.0.M2' } } @@ -16,7 +18,9 @@ apply plugin: 'org.springframework.restdocs' repositories { mavenLocal() - jcenter() + maven { + url 'https://repo.spring.io/libs-milestone' + } } group = 'com.example' diff --git a/rest-notes-spring-data-rest/src/documentation/asciidoc/main.asciidoc b/rest-notes-spring-data-rest/src/documentation/asciidoc/main.asciidoc index 63d390f2..8cb4fcc3 100644 --- a/rest-notes-spring-data-rest/src/documentation/asciidoc/main.asciidoc +++ b/rest-notes-spring-data-rest/src/documentation/asciidoc/main.asciidoc @@ -42,12 +42,12 @@ $ java -jar build/libs/*.jar You can check that the service is up and running by executing a simple request using cURL: -include::{generated}/index/access_with_curl_request.asciidoc[] +include::{generated}/GettingStartedDocumentation/indexRequest.asciidoc[] This request should yield the following response in the http://stateless.co/hal_specification.html[Hypertext Application Language (HAL)] format: -include::{generated}/index/access_with_curl_response.asciidoc[] +include::{generated}/GettingStartedDocumentation/indexResponse.asciidoc[] Note the `_links` in the JSON response. They are key to navigating the API. @@ -59,26 +59,26 @@ Now that you've started the service and verified that it works, the next step is it to create a new note. As you saw above, the URI for working with notes is included as a link when you perform a `GET` request against the root of the service: -include::{generated}/index/access_with_curl_response.asciidoc[] +include::{generated}/GettingStartedDocumentation/indexResponse.asciidoc[] To create a note, you need to execute a `POST` request to this URI including a JSON payload containing the title and body of the note: -include::{generated}/notes/create_with_curl_request.asciidoc[] +include::{generated}/GettingStartedDocumentation/createNoteRequest.asciidoc[] The response from this request should have a status code of `201 Created` and contain a `Location` header whose value is the URI of the newly created note: -include::{generated}/notes/create_with_curl_response.asciidoc[] +include::{generated}/GettingStartedDocumentation/createNoteResponse.asciidoc[] To work with the newly created note you use the URI in the `Location` header. For example, you can access the note's details by performing a `GET` request: -include::{generated}/notes/get_with_curl_request.asciidoc[] +include::{generated}/GettingStartedDocumentation/getNoteRequest.asciidoc[] This request will produce a response with the note's details in its body: -include::{generated}/notes/get_with_curl_response.asciidoc[] +include::{generated}/GettingStartedDocumentation/getNoteResponse.asciidoc[] Note the `tags` link which we'll make use of later. @@ -92,26 +92,26 @@ to tag a note, you must first create the tag. Referring back to the response for the service's index, the URI for working with tags is include as a link: -include::{generated}/index/access_with_curl_response.asciidoc[] +include::{generated}/GettingStartedDocumentation/indexResponse.asciidoc[] To create a tag you need to execute a `POST` request to this URI, including a JSON payload containing the name of the tag: -include::{generated}/tags/create_with_curl_request.asciidoc[] +include::{generated}/GettingStartedDocumentation/createTagRequest.asciidoc[] The response from this request should have a status code of `201 Created` and contain a `Location` header whose value is the URI of the newly created tag: -include::{generated}/tags/create_with_curl_response.asciidoc[] +include::{generated}/GettingStartedDocumentation/createTagResponse.asciidoc[] To work with the newly created tag you use the URI in the `Location` header. For example you can access the tag's details by performing a `GET` request: -include::{generated}/tags/get_with_curl_request.asciidoc[] +include::{generated}/GettingStartedDocumentation/getTagRequest.asciidoc[] This request will produce a response with the tag's details in its body: -include::{generated}/tags/get_with_curl_response.asciidoc[] +include::{generated}/GettingStartedDocumentation/getTagResponse.asciidoc[] @@ -132,24 +132,24 @@ with it. Once again we execute a `POST` request. However, this time, in an array named tags, we include the URI of the tag we just created: -include::{generated}/notes/create_tagged_with_curl_request.asciidoc[] +include::{generated}/GettingStartedDocumentation/createTaggedNoteRequest.asciidoc[] Once again, the response's `Location` header tells us the URI of the newly created note: -include::{generated}/notes/create_tagged_with_curl_response.asciidoc[] +include::{generated}/GettingStartedDocumentation/createTaggedNoteResponse.asciidoc[] As before, a `GET` request executed against this URI will retrieve the note's details: -include::{generated}/notes/get_tagged_with_curl.asciidoc[] +include::{generated}/GettingStartedDocumentation/getTaggedNoteRequestResponse.asciidoc[] To verify that the tag has been associated with the note, we can perform a `GET` request against the URI from the `tags` link: -include::{generated}/notes/get_tags_for_tagged_with_curl_request.asciidoc[] +include::{generated}/GettingStartedDocumentation/getTagsRequest.asciidoc[] The response embeds information about the tag that we've just associated with the note: -include::{generated}/notes/get_tags_for_tagged_with_curl_response.asciidoc[] +include::{generated}/GettingStartedDocumentation/getTagsResponse.asciidoc[] @@ -159,17 +159,17 @@ An existing note can be tagged by executing a `PATCH` request against the note's a body that contains the array of tags to be associated with the note. We'll used the URI of the untagged note that we created earlier: -include::{generated}/notes/tag_existing_with_curl_request.asciidoc[] +include::{generated}/GettingStartedDocumentation/tagExistingNoteRequest.asciidoc[] This request should produce a `204 No Content` response: -include::{generated}/notes/tag_existing_with_curl_response.asciidoc[] +include::{generated}/GettingStartedDocumentation/tagExistingNoteResponse.asciidoc[] When we first created this note, we noted the tags link included in its details: -include::{generated}/notes/get_with_curl_response.asciidoc[] +include::{generated}/GettingStartedDocumentation/createNoteResponse.asciidoc[] We can use that link now and execute a `GET` request to see that the note now has a single tag: -include::{generated}/notes/get_tags_for_existing_with_curl.asciidoc[] +include::{generated}/GettingStartedDocumentation/getTagsForExistingNoteRequestResponse.asciidoc[] diff --git a/rest-notes-spring-data-rest/src/documentation/java/com/example/notes/GettingStartedDocumentation.java b/rest-notes-spring-data-rest/src/documentation/java/com/example/notes/GettingStartedDocumentation.java index a69b8966..079313d8 100644 --- a/rest-notes-spring-data-rest/src/documentation/java/com/example/notes/GettingStartedDocumentation.java +++ b/rest-notes-spring-data-rest/src/documentation/java/com/example/notes/GettingStartedDocumentation.java @@ -19,14 +19,9 @@ package com.example.notes; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; -import static org.springframework.restdocs.core.RestDocumentationRequestPostProcessors.port; -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 static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -42,7 +37,8 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.hateoas.MediaTypes; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.restdocs.core.RestDocumentationConfiguration; +import org.springframework.restdocs.core.RestDocumentationJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; @@ -53,7 +49,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.jayway.jsonpath.JsonPath; -@RunWith(SpringJUnit4ClassRunner.class) +@RunWith(RestDocumentationJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) @WebAppConfiguration public class GettingStartedDocumentation { @@ -68,17 +64,16 @@ public class GettingStartedDocumentation { @Before public void setUp() { - this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build(); + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) + .apply(new RestDocumentationConfiguration()).build(); } @Test public void index() throws Exception { - this.mockMvc.perform(get("/").with(port(8080)).accept(MediaTypes.HAL_JSON)) + this.mockMvc.perform(get("/").accept(MediaTypes.HAL_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("_links.notes", is(notNullValue()))) - .andExpect(jsonPath("_links.tags", is(notNullValue()))) - .andDo(documentCurlRequest("index/access_with_curl_request.asciidoc")) - .andDo(documentCurlResponse("index/access_with_curl_response.asciidoc")); + .andExpect(jsonPath("_links.tags", is(notNullValue()))); } @Test @@ -98,63 +93,49 @@ public class GettingStartedDocumentation { getTagsForExistingNote(noteLocation); } - private String createNote() throws Exception { + String createNote() throws Exception { Map note = new HashMap(); note.put("title", "Note creation with cURL"); note.put("body", "An example of how to create a note using cURL"); String noteLocation = this.mockMvc .perform( - post("/notes").with(port(8080)).contentType(MediaTypes.HAL_JSON) - .content(objectMapper.writeValueAsString(note))) + post("/notes").contentType(MediaTypes.HAL_JSON).content( + objectMapper.writeValueAsString(note))) .andExpect(status().isCreated()) - .andExpect(header().string("Location", notNullValue())) - .andDo(documentCurlRequest("notes/create_with_curl_request.asciidoc") - .includeResponseHeaders()) - .andDo(documentCurlResponse("notes/create_with_curl_response.asciidoc") - .includeResponseHeaders()).andReturn().getResponse() - .getHeader("Location"); + .andExpect(header().string("Location", notNullValue())).andReturn() + .getResponse().getHeader("Location"); return noteLocation; } - private void getNote(String noteLocation) throws Exception { - this.mockMvc.perform(get(noteLocation).with(port(8080))) - .andExpect(status().isOk()) + void getNote(String noteLocation) throws Exception { + this.mockMvc.perform(get(noteLocation)).andExpect(status().isOk()) .andExpect(jsonPath("title", is(notNullValue()))) .andExpect(jsonPath("body", is(notNullValue()))) - .andExpect(jsonPath("_links.tags", is(notNullValue()))) - .andDo(documentCurlRequest("notes/get_with_curl_request.asciidoc")) - .andDo(documentCurlResponse("notes/get_with_curl_response.asciidoc")); + .andExpect(jsonPath("_links.tags", is(notNullValue()))); } - private String createTag() throws Exception, JsonProcessingException { + String createTag() throws Exception, JsonProcessingException { Map tag = new HashMap(); tag.put("name", "getting-started"); String tagLocation = this.mockMvc .perform( - post("/tags").with(port(8080)).contentType(MediaTypes.HAL_JSON) - .content(objectMapper.writeValueAsString(tag))) + post("/tags").contentType(MediaTypes.HAL_JSON).content( + objectMapper.writeValueAsString(tag))) .andExpect(status().isCreated()) - .andExpect(header().string("Location", notNullValue())) - .andDo(documentCurlRequest("tags/create_with_curl_request.asciidoc") - .includeResponseHeaders()) - .andDo(documentCurlResponse("tags/create_with_curl_response.asciidoc") - .includeResponseHeaders()).andReturn().getResponse() - .getHeader("Location"); + .andExpect(header().string("Location", notNullValue())).andReturn() + .getResponse().getHeader("Location"); return tagLocation; } - private void getTag(String tagLocation) throws Exception { - this.mockMvc.perform(get(tagLocation).with(port(8080))) - .andExpect(status().isOk()) + void getTag(String tagLocation) throws Exception { + this.mockMvc.perform(get(tagLocation)).andExpect(status().isOk()) .andExpect(jsonPath("name", is(notNullValue()))) - .andExpect(jsonPath("_links.notes", is(notNullValue()))) - .andDo(documentCurlRequest("tags/get_with_curl_request.asciidoc")) - .andDo(documentCurlResponse("tags/get_with_curl_response.asciidoc")); + .andExpect(jsonPath("_links.notes", is(notNullValue()))); } - private String createTaggedNote(String tag) throws Exception { + String createTaggedNote(String tag) throws Exception { Map note = new HashMap(); note.put("title", "Tagged note creation with cURL"); note.put("body", "An example of how to create a tagged note using cURL"); @@ -162,77 +143,48 @@ public class GettingStartedDocumentation { String noteLocation = this.mockMvc .perform( - post("/notes").with(port(8080)).contentType(MediaTypes.HAL_JSON) - .content(objectMapper.writeValueAsString(note))) + post("/notes").contentType(MediaTypes.HAL_JSON).content( + objectMapper.writeValueAsString(note))) .andExpect(status().isCreated()) - .andExpect(header().string("Location", notNullValue())) - .andDo(documentCurlRequest( - "notes/create_tagged_with_curl_request.asciidoc") - .includeResponseHeaders()) - .andDo(documentCurlResponse( - "notes/create_tagged_with_curl_response.asciidoc") - .includeResponseHeaders()).andReturn().getResponse() - .getHeader("Location"); + .andExpect(header().string("Location", notNullValue())).andReturn() + .getResponse().getHeader("Location"); return noteLocation; } - private void getTaggedNote(String tagLocation) throws Exception { - this.mockMvc - .perform(get(tagLocation).with(port(8080))) - .andExpect(status().isOk()) + void getTaggedNote(String tagLocation) throws Exception { + this.mockMvc.perform(get(tagLocation)).andExpect(status().isOk()) .andExpect(jsonPath("title", is(notNullValue()))) .andExpect(jsonPath("body", is(notNullValue()))) - .andExpect(jsonPath("_links.tags", is(notNullValue()))) - .andDo(documentCurlRequestAndResponse("notes/get_tagged_with_curl.asciidoc")); + .andExpect(jsonPath("_links.tags", is(notNullValue()))); } - private void getTags(String taggedNoteLocation) throws Exception { + void getTags(String taggedNoteLocation) throws Exception { String tagsLocation = getLink(this.mockMvc.perform(get(taggedNoteLocation)) .andReturn(), "tags"); - this.mockMvc - .perform(get(tagsLocation).with(port(8080))) - .andExpect(status().isOk()) - .andExpect(jsonPath("_embedded.tags", hasSize(1))) - .andDo(documentCurlRequest("notes/get_tags_for_tagged_with_curl_request.asciidoc")) - .andDo(documentCurlResponse("notes/get_tags_for_tagged_with_curl_response.asciidoc")); + this.mockMvc.perform(get(tagsLocation)).andExpect(status().isOk()) + .andExpect(jsonPath("_embedded.tags", hasSize(1))); } - private void tagExistingNote(String noteLocation, String tagLocation) - throws Exception { + void tagExistingNote(String noteLocation, String tagLocation) throws Exception { Map update = new HashMap(); update.put("tags", Arrays.asList(tagLocation)); - this.mockMvc - .perform( - patch(noteLocation).with(port(8080)) - .contentType(MediaTypes.HAL_JSON) - .content(objectMapper.writeValueAsString(update))) - .andExpect(status().isNoContent()) - .andDo(documentCurlRequest( - "notes/tag_existing_with_curl_request.asciidoc") - .includeResponseHeaders()) - .andDo(documentCurlResponse( - "notes/tag_existing_with_curl_response.asciidoc") - .includeResponseHeaders()); + this.mockMvc.perform( + patch(noteLocation).contentType(MediaTypes.HAL_JSON).content( + objectMapper.writeValueAsString(update))).andExpect( + status().isNoContent()); } - private void getTaggedExistingNote(String tagLocation) throws Exception { - this.mockMvc - .perform(get(tagLocation).with(port(8080))) - .andExpect(status().isOk()) - .andDo(documentCurlRequestAndResponse("notes/get_tagged_existing_with_curl.asciidoc")); + void getTaggedExistingNote(String tagLocation) throws Exception { + this.mockMvc.perform(get(tagLocation)).andExpect(status().isOk()); } - private void getTagsForExistingNote(String taggedNoteLocation) throws Exception { + void getTagsForExistingNote(String taggedNoteLocation) throws Exception { String tagsLocation = getLink(this.mockMvc.perform(get(taggedNoteLocation)) .andReturn(), "tags"); - this.mockMvc - .perform(get(tagsLocation).with(port(8080))) - .andExpect(status().isOk()) - .andExpect(jsonPath("_embedded.tags", hasSize(1))) - .andDo(print()) - .andDo(documentCurlRequestAndResponse("notes/get_tags_for_existing_with_curl.asciidoc")); + this.mockMvc.perform(get(tagsLocation)).andExpect(status().isOk()) + .andExpect(jsonPath("_embedded.tags", hasSize(1))); } private String getLink(MvcResult result, String href) diff --git a/rest-notes-spring-hateoas/build.gradle b/rest-notes-spring-hateoas/build.gradle index 70f0e028..eb992a86 100644 --- a/rest-notes-spring-hateoas/build.gradle +++ b/rest-notes-spring-hateoas/build.gradle @@ -1,11 +1,13 @@ buildscript { repositories { mavenLocal() - jcenter() + maven { + url "https://repo.spring.io/libs-milestone" + } } dependencies { classpath 'org.springframework.restdocs:spring-restdocs-gradle-plugin:0.1.0.BUILD-SNAPSHOT' - classpath 'org.springframework.boot:spring-boot-gradle-plugin:1.1.7.RELEASE' + classpath 'org.springframework.boot:spring-boot-gradle-plugin:1.2.0.M2' } } @@ -16,7 +18,9 @@ apply plugin: 'org.springframework.restdocs' repositories { mavenLocal() - jcenter() + maven { + url "https://repo.spring.io/libs-milestone" + } } group = 'com.example' diff --git a/rest-notes-spring-hateoas/src/documentation/asciidoc/main.asciidoc b/rest-notes-spring-hateoas/src/documentation/asciidoc/main.asciidoc index 67788b7c..1f0be40d 100644 --- a/rest-notes-spring-hateoas/src/documentation/asciidoc/main.asciidoc +++ b/rest-notes-spring-hateoas/src/documentation/asciidoc/main.asciidoc @@ -42,11 +42,11 @@ $ java -jar build/libs/*.jar You can check that the service is up and running by executing a simple request using cURL: -include::{generated}/index/access_with_curl_request.asciidoc[] +include::{generated}/GettingStartedDocumentation/indexRequest.asciidoc[] This request should yield the following response: -include::{generated}/index/access_with_curl_response.asciidoc[] +include::{generated}/GettingStartedDocumentation/indexResponse.asciidoc[] Note the `_links` in the JSON response. They are key to navigating the API. @@ -58,26 +58,26 @@ Now that you've started the service and verified that it works, the next step is it to create a new note. As you saw above, the URI for working with notes is included as a link when you perform a `GET` request against the root of the service: -include::{generated}/index/access_with_curl_response.asciidoc[] +include::{generated}/GettingStartedDocumentation/indexResponse.asciidoc[] To create a note you need to execute a `POST` request to this URI, including a JSON payload containing the title and body of the note: -include::{generated}/notes/create_with_curl_request.asciidoc[] +include::{generated}/GettingStartedDocumentation/createNoteRequest.asciidoc[] The response from this request should have a status code of `201 Created` and contain a `Location` header whose value is the URI of the newly created note: -include::{generated}/notes/create_with_curl_response.asciidoc[] +include::{generated}/GettingStartedDocumentation/createNoteResponse.asciidoc[] To work with the newly created note you use the URI in the `Location` header. For example you can access the note's details by performing a `GET` request: -include::{generated}/notes/get_with_curl_request.asciidoc[] +include::{generated}/GettingStartedDocumentation/getNoteRequest.asciidoc[] This request will produce a response with the note's details in its body: -include::{generated}/notes/get_with_curl_response.asciidoc[] +include::{generated}/GettingStartedDocumentation/getNoteResponse.asciidoc[] Note the `tags` link which we'll make use of later. @@ -91,26 +91,26 @@ to tag a note, you must first create the tag. Referring back to the response for the service's index, the URI for working with tags is include as a link: -include::{generated}/index/access_with_curl_response.asciidoc[] +include::{generated}/GettingStartedDocumentation/indexResponse.asciidoc[] To create a tag you need to execute a `POST` request to this URI, including a JSON payload containing the name of the tag: -include::{generated}/tags/create_with_curl_request.asciidoc[] +include::{generated}/GettingStartedDocumentation/createTagRequest.asciidoc[] The response from this request should have a status code of `201 Created` and contain a `Location` header whose value is the URI of the newly created tag: -include::{generated}/tags/create_with_curl_response.asciidoc[] +include::{generated}/GettingStartedDocumentation/createTagResponse.asciidoc[] To work with the newly created tag you use the URI in the `Location` header. For example you can access the tag's details by performing a `GET` request: -include::{generated}/tags/get_with_curl_request.asciidoc[] +include::{generated}/GettingStartedDocumentation/getTagRequest.asciidoc[] This request will produce a response with the tag's details in its body: -include::{generated}/tags/get_with_curl_response.asciidoc[] +include::{generated}/GettingStartedDocumentation/getTagResponse.asciidoc[] @@ -131,23 +131,23 @@ with it. Once again we execute a `POST` request, but this time, in an array named tags, we include the URI of the tag we just created: -include::{generated}/notes/create_tagged_with_curl_request.asciidoc[] +include::{generated}/GettingStartedDocumentation/createTaggedNoteRequest.asciidoc[] Once again, the response's `Location` header tells use the URI of the newly created note: -include::{generated}/notes/create_tagged_with_curl_response.asciidoc[] +include::{generated}/GettingStartedDocumentation/createTaggedNoteResponse.asciidoc[] As before, a `GET` request executed against this URI will retrieve the note's details: -include::{generated}/notes/get_tagged_with_curl.asciidoc[] +include::{generated}/GettingStartedDocumentation/getTaggedNoteRequestResponse.asciidoc[] To see the note's tags, execute a `GET` request against the URI of the note's tags link: -include::{generated}/notes/get_tags_for_tagged_with_curl_request.asciidoc[] +include::{generated}/GettingStartedDocumentation/getTagsRequest.asciidoc[] The response shows that, as expected, the note has a single tag: -include::{generated}/notes/get_tags_for_tagged_with_curl_response.asciidoc[] +include::{generated}/GettingStartedDocumentation/getTagsResponse.asciidoc[] @@ -157,17 +157,17 @@ An existing note can be tagged by executing a `PATCH` request against the note's a body that contains the array of tags to be associated with the note. We'll used the URI of the untagged note that we created earlier: -include::{generated}/notes/tag_existing_with_curl_request.asciidoc[] +include::{generated}/GettingStartedDocumentation/tagExistingNoteRequest.asciidoc[] This request should produce a `200 OK` response: -include::{generated}/notes/tag_existing_with_curl_response.asciidoc[] +include::{generated}/GettingStartedDocumentation/tagExistingNoteResponse.asciidoc[] When we first created this note, we noted the tags link included in its details: -include::{generated}/notes/get_with_curl_response.asciidoc[] +include::{generated}/GettingStartedDocumentation/createNoteResponse.asciidoc[] We can use that link now and execute a `GET` request to see that the note now has a single tag: -include::{generated}/notes/get_tags_for_existing_with_curl.asciidoc[] +include::{generated}/GettingStartedDocumentation/getTagsForExistingNoteRequestResponse.asciidoc[] diff --git a/rest-notes-spring-hateoas/src/documentation/java/com/example/notes/GettingStartedDocumentation.java b/rest-notes-spring-hateoas/src/documentation/java/com/example/notes/GettingStartedDocumentation.java index 8b533bb4..4c2424a6 100644 --- a/rest-notes-spring-hateoas/src/documentation/java/com/example/notes/GettingStartedDocumentation.java +++ b/rest-notes-spring-hateoas/src/documentation/java/com/example/notes/GettingStartedDocumentation.java @@ -18,10 +18,6 @@ package com.example.notes; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; -import static org.springframework.restdocs.core.RestDocumentationRequestPostProcessors.port; -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 static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; @@ -40,8 +36,10 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.hateoas.MediaTypes; import org.springframework.http.MediaType; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.restdocs.core.RestDocumentationConfiguration; +import org.springframework.restdocs.core.RestDocumentationJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; @@ -52,7 +50,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.jayway.jsonpath.JsonPath; -@RunWith(SpringJUnit4ClassRunner.class) +@RunWith(RestDocumentationJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) @WebAppConfiguration public class GettingStartedDocumentation { @@ -67,18 +65,16 @@ public class GettingStartedDocumentation { @Before public void setUp() { - this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build(); + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) + .apply(new RestDocumentationConfiguration()).build(); } @Test public void index() throws Exception { - this.mockMvc - .perform(get("/").with(port(8080)).accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()).andDo(print()) + this.mockMvc.perform(get("/").accept(MediaTypes.HAL_JSON)) + .andExpect(status().isOk()) .andExpect(jsonPath("links[?(@.rel==notes)]", is(notNullValue()))) - .andExpect(jsonPath("links[?(@.rel==tags)]", is(notNullValue()))) - .andDo(documentCurlRequest("index/access_with_curl_request.asciidoc")) - .andDo(documentCurlResponse("index/access_with_curl_response.asciidoc")); + .andExpect(jsonPath("links[?(@.rel==tags)]", is(notNullValue()))); } @Test @@ -98,65 +94,49 @@ public class GettingStartedDocumentation { getTagsForExistingNote(noteLocation); } - private String createNote() throws Exception { + String createNote() throws Exception { Map note = new HashMap(); note.put("title", "Note creation with cURL"); note.put("body", "An example of how to create a note using cURL"); String noteLocation = this.mockMvc .perform( - post("/notes").with(port(8080)) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(note))) + post("/notes").contentType(MediaTypes.HAL_JSON).content( + objectMapper.writeValueAsString(note))) .andExpect(status().isCreated()) - .andExpect(header().string("Location", notNullValue())) - .andDo(documentCurlRequest("notes/create_with_curl_request.asciidoc") - .includeResponseHeaders()) - .andDo(documentCurlResponse("notes/create_with_curl_response.asciidoc") - .includeResponseHeaders()).andReturn().getResponse() - .getHeader("Location"); + .andExpect(header().string("Location", notNullValue())).andReturn() + .getResponse().getHeader("Location"); return noteLocation; } - private void getNote(String noteLocation) throws Exception { - this.mockMvc.perform(get(noteLocation).with(port(8080))) - .andExpect(status().isOk()) + void getNote(String noteLocation) throws Exception { + this.mockMvc.perform(get(noteLocation)).andExpect(status().isOk()) .andExpect(jsonPath("title", is(notNullValue()))) .andExpect(jsonPath("body", is(notNullValue()))) - .andExpect(jsonPath("links[?(@.rel==tags)]", is(notNullValue()))) - .andDo(documentCurlRequest("notes/get_with_curl_request.asciidoc")) - .andDo(documentCurlResponse("notes/get_with_curl_response.asciidoc")); + .andExpect(jsonPath("links[?(@.rel==tags)]", is(notNullValue()))); } - private String createTag() throws Exception, JsonProcessingException { + String createTag() throws Exception, JsonProcessingException { Map tag = new HashMap(); tag.put("name", "getting-started"); String tagLocation = this.mockMvc .perform( - post("/tags").with(port(8080)) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(tag))) + post("/tags").contentType(MediaTypes.HAL_JSON).content( + objectMapper.writeValueAsString(tag))) .andExpect(status().isCreated()) - .andExpect(header().string("Location", notNullValue())) - .andDo(documentCurlRequest("tags/create_with_curl_request.asciidoc") - .includeResponseHeaders()) - .andDo(documentCurlResponse("tags/create_with_curl_response.asciidoc") - .includeResponseHeaders()).andReturn().getResponse() - .getHeader("Location"); + .andExpect(header().string("Location", notNullValue())).andReturn() + .getResponse().getHeader("Location"); return tagLocation; } - private void getTag(String tagLocation) throws Exception { - this.mockMvc.perform(get(tagLocation).with(port(8080))) - .andExpect(status().isOk()) + void getTag(String tagLocation) throws Exception { + this.mockMvc.perform(get(tagLocation)).andExpect(status().isOk()) .andExpect(jsonPath("name", is(notNullValue()))) - .andExpect(jsonPath("links[?(@.rel==notes)]", is(notNullValue()))) - .andDo(documentCurlRequest("tags/get_with_curl_request.asciidoc")) - .andDo(documentCurlResponse("tags/get_with_curl_response.asciidoc")); + .andExpect(jsonPath("links[?(@.rel==notes)]", is(notNullValue()))); } - private String createTaggedNote(String tag) throws Exception { + String createTaggedNote(String tag) throws Exception { Map note = new HashMap(); note.put("title", "Tagged note creation with cURL"); note.put("body", "An example of how to create a tagged note using cURL"); @@ -164,77 +144,49 @@ public class GettingStartedDocumentation { String noteLocation = this.mockMvc .perform( - post("/notes").with(port(8080)) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(note))) + post("/notes").contentType(MediaType.APPLICATION_JSON).content( + objectMapper.writeValueAsString(note))) .andExpect(status().isCreated()) - .andExpect(header().string("Location", notNullValue())) - .andDo(documentCurlRequest( - "notes/create_tagged_with_curl_request.asciidoc") - .includeResponseHeaders()) - .andDo(documentCurlResponse( - "notes/create_tagged_with_curl_response.asciidoc") - .includeResponseHeaders()).andReturn().getResponse() - .getHeader("Location"); + .andExpect(header().string("Location", notNullValue())).andReturn() + .getResponse().getHeader("Location"); return noteLocation; } - private void getTaggedNote(String tagLocation) throws Exception { - this.mockMvc - .perform(get(tagLocation).with(port(8080))) - .andExpect(status().isOk()) + void getTaggedNote(String tagLocation) throws Exception { + this.mockMvc.perform(get(tagLocation)).andExpect(status().isOk()) .andExpect(jsonPath("title", is(notNullValue()))) .andExpect(jsonPath("body", is(notNullValue()))) - .andExpect(jsonPath("links[?(@.rel==tags)]", is(notNullValue()))) - .andDo(documentCurlRequestAndResponse("notes/get_tagged_with_curl.asciidoc")); + .andExpect(jsonPath("links[?(@.rel==tags)]", is(notNullValue()))); } - private void getTags(String taggedNoteLocation) throws Exception { + void getTags(String taggedNoteLocation) throws Exception { String tagsLocation = getLink(this.mockMvc.perform(get(taggedNoteLocation)) .andReturn(), "tags"); - this.mockMvc - .perform(get(tagsLocation).with(port(8080))) - .andExpect(status().isOk()) - .andExpect(jsonPath("links[?(@.rel==tag)]", is(notNullValue()))) - .andDo(documentCurlRequest("notes/get_tags_for_tagged_with_curl_request.asciidoc")) - .andDo(documentCurlResponse("notes/get_tags_for_tagged_with_curl_response.asciidoc")); + this.mockMvc.perform(get(tagsLocation)).andExpect(status().isOk()) + .andExpect(jsonPath("links[?(@.rel==tag)]", is(notNullValue()))); } - private void tagExistingNote(String noteLocation, String tagLocation) - throws Exception { + void tagExistingNote(String noteLocation, String tagLocation) throws Exception { Map update = new HashMap(); update.put("tags", Arrays.asList(tagLocation)); this.mockMvc .perform( - patch(noteLocation).with(port(8080)) - .contentType(MediaType.APPLICATION_JSON) + patch(noteLocation).contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(update))) - .andExpect(status().isOk()) - .andDo(documentCurlRequest( - "notes/tag_existing_with_curl_request.asciidoc") - .includeResponseHeaders()) - .andDo(documentCurlResponse( - "notes/tag_existing_with_curl_response.asciidoc") - .includeResponseHeaders()); + .andDo(print()).andExpect(status().isNoContent()); } - private void getTaggedExistingNote(String tagLocation) throws Exception { - this.mockMvc - .perform(get(tagLocation).with(port(8080))) - .andExpect(status().isOk()) - .andDo(documentCurlRequestAndResponse("notes/get_tagged_existing_with_curl.asciidoc")); + void getTaggedExistingNote(String tagLocation) throws Exception { + this.mockMvc.perform(get(tagLocation)).andExpect(status().isOk()); } - private void getTagsForExistingNote(String taggedNoteLocation) throws Exception { + void getTagsForExistingNote(String taggedNoteLocation) throws Exception { String tagsLocation = getLink(this.mockMvc.perform(get(taggedNoteLocation)) .andReturn(), "tags"); - this.mockMvc - .perform(get(tagsLocation).with(port(8080))) - .andExpect(status().isOk()) - .andExpect(jsonPath("links[?(@.rel==tag)]", is(notNullValue()))) - .andDo(documentCurlRequestAndResponse("notes/get_tags_for_existing_with_curl.asciidoc")); + this.mockMvc.perform(get(tagsLocation)).andExpect(status().isOk()) + .andExpect(jsonPath("links[?(@.rel==tag)]", is(notNullValue()))); } private String getLink(MvcResult result, String rel) diff --git a/rest-notes-spring-hateoas/src/main/java/com/example/notes/NotesController.java b/rest-notes-spring-hateoas/src/main/java/com/example/notes/NotesController.java index 4d38b2a6..de31f824 100644 --- a/rest-notes-spring-hateoas/src/main/java/com/example/notes/NotesController.java +++ b/rest-notes-spring-hateoas/src/main/java/com/example/notes/NotesController.java @@ -94,6 +94,7 @@ public class NotesController { } @RequestMapping(value = "/{id}", method = RequestMethod.PATCH) + @ResponseStatus(HttpStatus.NO_CONTENT) void updateNote(@PathVariable("id") long id, @RequestBody NotePatchInput noteInput) { Note note = this.noteRepository.findOne(id); if (noteInput.getTagUris() != null) { diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/core/Documentation.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/core/Documentation.java deleted file mode 100644 index 32a1d51b..00000000 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/core/Documentation.java +++ /dev/null @@ -1,55 +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.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()); - } -} diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/core/DocumentationContext.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/core/DocumentationContext.java index 40ab9496..2b3ef0b1 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/core/DocumentationContext.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/core/DocumentationContext.java @@ -16,27 +16,47 @@ package org.springframework.restdocs.core; -import java.io.PrintStream; +import java.lang.reflect.Method; +import java.util.Stack; class DocumentationContext { - private static final InheritableThreadLocal CONTEXTS = new InheritableThreadLocal(); + private static final InheritableThreadLocal> CONTEXTS = new InheritableThreadLocal>() { - private final DocumentationWriter writer; + @Override + protected Stack initialValue() { + return new Stack(); + } - public DocumentationContext(PrintStream printStream) { - this.writer = new DocumentationWriter(printStream); - } + }; - public DocumentationWriter getWriter() { - return this.writer; + private final Class documentationClass; + + private final Method documentationMethod; + + public DocumentationContext(Class documentationClass, Method documentationMethod) { + this.documentationClass = documentationClass; + this.documentationMethod = documentationMethod; } public static DocumentationContext current() { - return CONTEXTS.get(); + return CONTEXTS.get().peek(); } - static void set(DocumentationContext context) { - CONTEXTS.set(context); + public Class getDocumentationClass() { + return documentationClass; } + + public Method getDocumentationMethod() { + return documentationMethod; + } + + static void push(DocumentationContext context) { + CONTEXTS.get().push(context); + } + + static void pop() { + CONTEXTS.get().pop(); + } + } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/core/RestDocumentationConfiguration.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/core/RestDocumentationConfiguration.java new file mode 100644 index 00000000..50093fc3 --- /dev/null +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/core/RestDocumentationConfiguration.java @@ -0,0 +1,76 @@ +/* + * 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 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.MockMvcConfigurer; +import org.springframework.web.context.WebApplicationContext; + +public class RestDocumentationConfiguration implements MockMvcConfigurer { + + 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 void afterConfigurerAdded(ConfigurableMockMvcBuilder builder) { + builder.alwaysDo(documentCurlRequest().includeResponseHeaders()) + .alwaysDo(documentCurlResponse().includeResponseHeaders()) + .alwaysDo(documentCurlRequestAndResponse().includeResponseHeaders()); + } + + @Override + public RequestPostProcessor beforeMockMvcCreated( + ConfigurableMockMvcBuilder builder, WebApplicationContext context) { + return new RequestPostProcessor() { + + @Override + public MockHttpServletRequest postProcessRequest( + MockHttpServletRequest request) { + request.setScheme(scheme); + request.setRemotePort(port); + request.setServerPort(port); + request.setRemoteHost(host); + return request; + } + }; + } + +} diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/core/RestDocumentationJUnit4ClassRunner.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/core/RestDocumentationJUnit4ClassRunner.java new file mode 100644 index 00000000..18577e42 --- /dev/null +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/core/RestDocumentationJUnit4ClassRunner.java @@ -0,0 +1,70 @@ +/* + * 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.lang.reflect.Method; + +import org.junit.runners.model.InitializationError; +import org.springframework.cglib.proxy.Enhancer; +import org.springframework.cglib.proxy.MethodInterceptor; +import org.springframework.cglib.proxy.MethodProxy; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +public class RestDocumentationJUnit4ClassRunner extends SpringJUnit4ClassRunner { + + public RestDocumentationJUnit4ClassRunner(Class clazz) throws InitializationError { + super(clazz); + } + + protected Object createTest() throws Exception { + Object testInstance = createProxiedTestInstance(); + getTestContextManager().prepareTestInstance(testInstance); + return testInstance; + } + + private Object createProxiedTestInstance() { + Enhancer enhancer = new Enhancer(); + enhancer.setSuperclass(getTestClass().getJavaClass()); + enhancer.setClassLoader(getTestClass().getJavaClass().getClassLoader()); + enhancer.setCallback(new DocumentationContextManagingMethodInterceptor( + getTestClass().getJavaClass())); + return enhancer.create(); + } + + private static class DocumentationContextManagingMethodInterceptor implements + MethodInterceptor { + + private final Class testClass; + + private DocumentationContextManagingMethodInterceptor(Class testClass) { + this.testClass = testClass; + } + + @Override + public Object intercept(Object target, Method method, Object[] args, + MethodProxy methodProxy) throws Throwable { + DocumentationContext.push(new DocumentationContext(this.testClass, method)); + try { + return methodProxy.invokeSuper(target, args); + } + finally { + DocumentationContext.pop(); + } + } + + } +} \ No newline at end of file diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/core/RestDocumentationRequestPostProcessors.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/core/RestDocumentationRequestPostProcessors.java deleted file mode 100644 index 8f96795c..00000000 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/core/RestDocumentationRequestPostProcessors.java +++ /dev/null @@ -1,51 +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; - -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; - } - }; - - } - -} diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/core/RestDocumentationResultHandlers.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/core/RestDocumentationResultHandlers.java index e196d53b..e5e390ce 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/core/RestDocumentationResultHandlers.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/core/RestDocumentationResultHandlers.java @@ -16,6 +16,8 @@ package org.springframework.restdocs.core; +import static org.springframework.restdocs.core.IterableEnumeration.iterable; + import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; @@ -31,12 +33,10 @@ 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) { + public static CurlResultHandler documentCurlRequest() { + return new CurlResultHandler("Request.asciidoc") { @Override public void handle(MvcResult result, DocumentationWriter writer) throws Exception { @@ -46,8 +46,8 @@ public abstract class RestDocumentationResultHandlers { }; } - public static CurlResultHandler documentCurlResponse(String path) { - return new CurlResultHandler(path) { + public static CurlResultHandler documentCurlResponse() { + return new CurlResultHandler("Response.asciidoc") { @Override public void handle(MvcResult result, DocumentationWriter writer) throws Exception { @@ -57,8 +57,8 @@ public abstract class RestDocumentationResultHandlers { }; } - public static CurlResultHandler documentCurlRequestAndResponse(String path) { - return new CurlResultHandler(path) { + public static CurlResultHandler documentCurlRequestAndResponse() { + return new CurlResultHandler("RequestResponse.asciidoc") { @Override public void handle(MvcResult result, DocumentationWriter writer) throws Exception { @@ -70,22 +70,6 @@ public abstract class RestDocumentationResultHandlers { }; } - 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 { @@ -182,11 +166,11 @@ public abstract class RestDocumentationResultHandlers { public static abstract class CurlResultHandler implements ResultHandler { private final CurlConfiguration curlConfiguration = new CurlConfiguration(); - - private final String path; - - private CurlResultHandler(String path) { - this.path = path; + + private String suffix; + + public CurlResultHandler(String suffix) { + this.suffix = suffix; } CurlConfiguration getCurlConfiguration() { @@ -200,7 +184,7 @@ public abstract class RestDocumentationResultHandlers { @Override public void handle(MvcResult result) throws Exception { - PrintStream printStream = createPrintStream(this.path); + PrintStream printStream = createPrintStream(this.suffix); try { handle(result, new DocumentationWriter(printStream)); } @@ -208,6 +192,42 @@ public abstract class RestDocumentationResultHandlers { printStream.close(); } } + + private PrintStream createPrintStream(String suffix) + throws FileNotFoundException { + DocumentationContext context = DocumentationContext.current(); + if (context == null) { + throw new IllegalStateException(); + } + + String path = resolveOutputPath(context); + + 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 String resolveOutputPath(DocumentationContext context) { + String shortClassName = getShortClassName(context.getDocumentationClass()); + return shortClassName + "/" + context.getDocumentationMethod().getName() + this.suffix; + } + + private String getShortClassName(Class clazz) { + int index = clazz.getName().lastIndexOf('.'); + if (index >= 0) { + return clazz.getName().substring(index + 1); + } + return clazz.getName(); + } abstract void handle(MvcResult result, DocumentationWriter writer) throws Exception;