diff --git a/docs/src/docs/asciidoc/configuration.adoc b/docs/src/docs/asciidoc/configuration.adoc index ed9b2103..9654df1a 100644 --- a/docs/src/docs/asciidoc/configuration.adoc +++ b/docs/src/docs/asciidoc/configuration.adoc @@ -48,6 +48,26 @@ include::{examples-dir}/com/example/CustomEncoding.java[tags=custom-encoding] +[[configuration-default-snippets]] +=== Default snippets + +Three snippets are produced by default: + +- `curl-request` +- `http-request` +- `http-response` + +This default configuration is applied by `RestDocumentationConfigurer`. You can use its +API to change the configuration. For example, to only produce the `curl-request` snippet +by default: + +[source,java,indent=0] +---- +include::{examples-dir}/com/example/CustomDefaultSnippetsConfiguration.java[tags=custom-default-snippets] +---- + + + [[configuration-output-directory]] === Snippet output directory diff --git a/docs/src/docs/asciidoc/documenting-your-api.adoc b/docs/src/docs/asciidoc/documenting-your-api.adoc index 8a526df7..0018d7d0 100644 --- a/docs/src/docs/asciidoc/documenting-your-api.adoc +++ b/docs/src/docs/asciidoc/documenting-your-api.adoc @@ -15,7 +15,8 @@ https://en.wikipedia.org/wiki/HATEOAS[Hypermedia-based] API: ---- include::{examples-dir}/com/example/Hypermedia.java[tag=links] ---- -<1> Use `withLinks` is to describe the expected links. +<1> Produce a snippet describing the response's links. Uses the static `links` method on +`org.springframework.restdocs.hypermedia.HypermediaDocumentation`. <2> Expect a link whose rel is `alpha`. Uses the static `linkWithRel` method on `org.springframework.restdocs.hypermedia.HypermediaDocumentation`. <3> Expect a link whose rel is `bravo`. @@ -40,14 +41,14 @@ Two link formats are understood by default: content type of the response is compatible with `application/hal+json`. If you are using Atom or HAL-format links but with a different content type you can -provide one of the built-in `LinkExtractor` implementations to `withLinks`. For example: +provide one of the built-in `LinkExtractor` implementations to `links`. For example: [source,java,indent=0] ---- include::{examples-dir}/com/example/Hypermedia.java[tag=explicit-extractor] ---- <1> Indicate that the links are in HAL format. Uses the static `halLinks` method on -`org.springframework.restdocs.hypermedia.LinkExtractors`. +`org.springframework.restdocs.hypermedia.HypermediaDocumentation`. If your API represents its links in a format other than Atom or HAL you can provide your own implementation of the `LinkExtractor` interface to extract the links from the @@ -176,13 +177,14 @@ include::{examples-dir}/com/example/Payload.java[tags=explicit-type] [[documenting-your-api-query-parameters]] === Query parameters -A request's query parameters can be documented using `withQueryParameters` +A request's query parameters can be documented using `queryParameters` [source,java,indent=0] ---- include::{examples-dir}/com/example/QueryParameters.java[tags=query-parameters] ---- -<1> Use `withQueryParameters` to describe the query parameters. +<1> Produce a snippet describing the request's query parameters. Uses the static +`queryParameters` method on `org.springframework.restdocs.request.RequestDocumentation`. <2> Document a parameter named `page`. Uses the static `parameterWithName` method on `org.springframework.restdocs.request.RequestDocumentation`. <3> Document a parameter named `per_page`. @@ -199,14 +201,15 @@ is not found in the request. [[documenting-your-api-path-parameters]] === Path parameters -A request's path parameters can be documented using `withPathParameters` +A request's path parameters can be documented using `pathParameters` [source,java,indent=0] ---- include::{examples-dir}/com/example/PathParameters.java[tags=path-parameters] ---- <1> Build the request. Uses the static `get` method on `RestDocumentationRequestBuilders`. -<2> Use `withPathParameters` to describe the path parameters. +<2> Produce a snippet describing the request's path parameters. Uses the static +`pathParameters` method on `org.springframework.restdocs.request.RequestDocumentation`. <3> Document a parameter named `longitude`. Uses the static `parameterWithName` method on `org.springframework.restdocs.request.RequestDocumentation`. <4> Document a parameter named `latitude`. @@ -244,6 +247,9 @@ documented | Contains the HTTP response that was returned |=== +You can configure which snippets are produced by default. Please refer to the +<> for more information. + [[documentating-your-api-parameterized-output-directories]] diff --git a/docs/src/docs/asciidoc/getting-started.adoc b/docs/src/docs/asciidoc/getting-started.adoc index bbb0af76..f07f9da6 100644 --- a/docs/src/docs/asciidoc/getting-started.adoc +++ b/docs/src/docs/asciidoc/getting-started.adoc @@ -200,7 +200,7 @@ be included in the project's jar: copy-resources - + ${project.build.outputDirectory}/static/docs @@ -245,7 +245,7 @@ The `MockMvc` instance is configured using a `RestDocumentationConfigurer`. An i of this class can be obtained from the static `documentationConfiguration()` method on `org.springframework.restdocs.RestDocumentation`. `RestDocumentationConfigurer` applies sensible defaults and also provides an API for customizing the configuration. Refer to the -<> for more information. +<> for more information. diff --git a/docs/src/test/java/com/example/CustomDefaultSnippetsConfiguration.java b/docs/src/test/java/com/example/CustomDefaultSnippetsConfiguration.java new file mode 100644 index 00000000..0a74af66 --- /dev/null +++ b/docs/src/test/java/com/example/CustomDefaultSnippetsConfiguration.java @@ -0,0 +1,29 @@ +package com.example; + +import static org.springframework.restdocs.RestDocumentation.documentationConfiguration; +import static org.springframework.restdocs.curl.CurlDocumentation.curlRequest; + +import org.junit.Before; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +public class CustomDefaultSnippetsConfiguration { + + @Autowired + private WebApplicationContext context; + + private MockMvc mockMvc; + + @Before + public void setUp() { + // tag::custom-default-snippets[] + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) + .apply(documentationConfiguration().snippets() + .withDefaults(curlRequest())) + .build(); + // end::custom-default-snippets[] + } + +} diff --git a/docs/src/test/java/com/example/Hypermedia.java b/docs/src/test/java/com/example/Hypermedia.java index 09b8e2d2..103529ea 100644 --- a/docs/src/test/java/com/example/Hypermedia.java +++ b/docs/src/test/java/com/example/Hypermedia.java @@ -20,7 +20,8 @@ import static org.springframework.restdocs.RestDocumentation.document; import static org.springframework.restdocs.RestDocumentationRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; -import static org.springframework.restdocs.hypermedia.LinkExtractors.halLinks; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.halLinks; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; @@ -29,13 +30,13 @@ public class Hypermedia { private MockMvc mockMvc; - public void links() throws Exception { + public void defaultExtractor() throws Exception { // tag::links[] this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) - .andDo(document("index").withLinks( // <1> + .andDo(document("index", links( // <1> linkWithRel("alpha").description("Link to the alpha resource"), // <2> - linkWithRel("bravo").description("Link to the bravo resource"))); // <3> + linkWithRel("bravo").description("Link to the bravo resource")))); // <3> // end::links[] } @@ -43,9 +44,9 @@ public class Hypermedia { this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) //tag::explicit-extractor[] - .andDo(document("index").withLinks(halLinks(), // <1> + .andDo(document("index", links(halLinks(), // <1> linkWithRel("alpha").description("Link to the alpha resource"), - linkWithRel("bravo").description("Link to the bravo resource"))); + linkWithRel("bravo").description("Link to the bravo resource")))); // end::explicit-extractor[] } diff --git a/docs/src/test/java/com/example/PathParameters.java b/docs/src/test/java/com/example/PathParameters.java index 4260b6d7..8bff0a44 100644 --- a/docs/src/test/java/com/example/PathParameters.java +++ b/docs/src/test/java/com/example/PathParameters.java @@ -18,6 +18,7 @@ package com.example; import static org.springframework.restdocs.RestDocumentation.document; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; import static org.springframework.restdocs.RestDocumentationRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -27,14 +28,14 @@ public class PathParameters { private MockMvc mockMvc; - public void pathParameters() throws Exception { + public void pathParametersSnippet() throws Exception { // tag::path-parameters[] this.mockMvc.perform(get("/locations/{latitude}/{longitude}", 51.5072, 0.1275)) // <1> .andExpect(status().isOk()) - .andDo(document("locations").withPathParameters( // <2> + .andDo(document("locations", pathParameters( // <2> parameterWithName("latitude").description("The location's latitude"), // <3> parameterWithName("longitude").description("The location's longitude") // <4> - )); + ))); // end::path-parameters[] } diff --git a/docs/src/test/java/com/example/Payload.java b/docs/src/test/java/com/example/Payload.java index 121dd114..a317ec1c 100644 --- a/docs/src/test/java/com/example/Payload.java +++ b/docs/src/test/java/com/example/Payload.java @@ -18,6 +18,8 @@ package com.example; import static org.springframework.restdocs.RestDocumentation.document; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; import static org.springframework.restdocs.RestDocumentationRequestBuilders.get; @@ -26,7 +28,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import org.springframework.http.MediaType; import org.springframework.restdocs.payload.FieldType; -import org.springframework.restdocs.snippet.Attributes; import org.springframework.test.web.servlet.MockMvc; public class Payload { @@ -37,9 +38,9 @@ private MockMvc mockMvc; // tag::response[] this.mockMvc.perform(get("/user/5").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) - .andDo(document("index").withResponseFields( // <1> + .andDo(document("index", responseFields( // <1> fieldWithPath("contact").description("The user's contact details"), // <2> - fieldWithPath("contact.email").description("The user's email address"))); // <3> + fieldWithPath("contact.email").description("The user's email address")))); // <3> // end::response[] } @@ -47,11 +48,11 @@ private MockMvc mockMvc; this.mockMvc.perform(get("/user/5").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) // tag::explicit-type[] - .andDo(document("index").withResponseFields( + .andDo(document("index", responseFields( fieldWithPath("contact.email") .type(FieldType.STRING) // <1> .optional() - .description("The user's email address"))); + .description("The user's email address")))); // end::explicit-type[] } @@ -59,7 +60,7 @@ private MockMvc mockMvc; this.mockMvc.perform(post("/users/").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) // tag::constraints[] - .andDo(document("create-user").withRequestFields( + .andDo(document("create-user", requestFields( attributes( key("title").value("Fields for user creation")), // <1> fieldWithPath("name") @@ -69,7 +70,7 @@ private MockMvc mockMvc; fieldWithPath("email") .description("The user's email address") .attributes( - key("constraints").value("Must be a valid email address")))); // <3> + key("constraints").value("Must be a valid email address"))))); // <3> // end::constraints[] } diff --git a/docs/src/test/java/com/example/QueryParameters.java b/docs/src/test/java/com/example/QueryParameters.java index f4af6037..4afcc35d 100644 --- a/docs/src/test/java/com/example/QueryParameters.java +++ b/docs/src/test/java/com/example/QueryParameters.java @@ -18,6 +18,7 @@ package com.example; import static org.springframework.restdocs.RestDocumentation.document; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; import static org.springframework.restdocs.RestDocumentationRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -27,14 +28,14 @@ public class QueryParameters { private MockMvc mockMvc; - public void queryParameters() throws Exception { + public void queryParametersSnippet() throws Exception { // tag::query-parameters[] this.mockMvc.perform(get("/users?page=2&per_page=100")) .andExpect(status().isOk()) - .andDo(document("users").withQueryParameters( // <1> + .andDo(document("users", queryParameters( // <1> parameterWithName("page").description("The page to retrieve"), // <2> parameterWithName("per_page").description("Entries per page") // <3> - )); + ))); // end::query-parameters[] } diff --git a/samples/rest-notes-spring-data-rest/src/test/java/com/example/notes/ApiDocumentation.java b/samples/rest-notes-spring-data-rest/src/test/java/com/example/notes/ApiDocumentation.java index 5fa7364a..f58d92ad 100644 --- a/samples/rest-notes-spring-data-rest/src/test/java/com/example/notes/ApiDocumentation.java +++ b/samples/rest-notes-spring-data-rest/src/test/java/com/example/notes/ApiDocumentation.java @@ -21,7 +21,10 @@ import static org.hamcrest.Matchers.notNullValue; import static org.springframework.restdocs.RestDocumentation.documentationConfiguration; import static org.springframework.restdocs.RestDocumentation.document; import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; import static org.springframework.restdocs.RestDocumentationRequestBuilders.get; import static org.springframework.restdocs.RestDocumentationRequestBuilders.patch; import static org.springframework.restdocs.RestDocumentationRequestBuilders.post; @@ -100,26 +103,26 @@ public class ApiDocumentation { .andExpect(jsonPath("timestamp", is(notNullValue()))) .andExpect(jsonPath("status", is(400))) .andExpect(jsonPath("path", is(notNullValue()))) - .andDo(document("error-example") - .withResponseFields( + .andDo(document("error-example", + responseFields( fieldWithPath("error").description("The HTTP error that occurred, e.g. `Bad Request`"), fieldWithPath("message").description("A description of the cause of the error"), fieldWithPath("path").description("The path to which the request was made"), fieldWithPath("status").description("The HTTP status code, e.g. `400`"), - fieldWithPath("timestamp").description("The time, in milliseconds, at which the error occurred"))); + fieldWithPath("timestamp").description("The time, in milliseconds, at which the error occurred")))); } @Test public void indexExample() throws Exception { this.mockMvc.perform(get("/")) .andExpect(status().isOk()) - .andDo(document("index-example") - .withLinks( + .andDo(document("index-example", + links( linkWithRel("notes").description("The <>"), linkWithRel("tags").description("The <>"), - linkWithRel("profile").description("The ALPS profile for the service")) - .withResponseFields( - fieldWithPath("_links").description("<> to other resources"))); + linkWithRel("profile").description("The ALPS profile for the service")), + responseFields( + fieldWithPath("_links").description("<> to other resources")))); } @@ -135,9 +138,9 @@ public class ApiDocumentation { this.mockMvc.perform(get("/notes")) .andExpect(status().isOk()) - .andDo(document("notes-list-example") - .withResponseFields( - fieldWithPath("_embedded.notes").description("An array of <>"))); + .andDo(document("notes-list-example", + responseFields( + fieldWithPath("_embedded.notes").description("An array of <>")))); } @Test @@ -161,11 +164,11 @@ public class ApiDocumentation { post("/notes").contentType(MediaTypes.HAL_JSON).content( this.objectMapper.writeValueAsString(note))).andExpect( status().isCreated()) - .andDo(document("notes-create-example") - .withRequestFields( + .andDo(document("notes-create-example", + requestFields( fieldWithPath("title").description("The title of the note"), fieldWithPath("body").description("The body of the note"), - fieldWithPath("tags").description("An array of tag resource URIs"))); + fieldWithPath("tags").description("An array of tag resource URIs")))); } @Test @@ -198,14 +201,14 @@ public class ApiDocumentation { .andExpect(jsonPath("body", is(note.get("body")))) .andExpect(jsonPath("_links.self.href", is(noteLocation))) .andExpect(jsonPath("_links.tags", is(notNullValue()))) - .andDo(document("note-get-example") - .withLinks( + .andDo(document("note-get-example", + links( linkWithRel("self").description("This <>"), - linkWithRel("tags").description("This note's tags")) - .withResponseFields( + linkWithRel("tags").description("This note's tags")), + responseFields( fieldWithPath("title").description("The title of the note"), fieldWithPath("body").description("The body of the note"), - fieldWithPath("_links").description("<> to other resources"))); + fieldWithPath("_links").description("<> to other resources")))); } @Test @@ -219,9 +222,9 @@ public class ApiDocumentation { this.mockMvc.perform(get("/tags")) .andExpect(status().isOk()) - .andDo(document("tags-list-example") - .withResponseFields( - fieldWithPath("_embedded.tags").description("An array of <>"))); + .andDo(document("tags-list-example", + responseFields( + fieldWithPath("_embedded.tags").description("An array of <>")))); } @Test @@ -233,9 +236,9 @@ public class ApiDocumentation { post("/tags").contentType(MediaTypes.HAL_JSON).content( this.objectMapper.writeValueAsString(tag))) .andExpect(status().isCreated()) - .andDo(document("tags-create-example") - .withRequestFields( - fieldWithPath("name").description("The name of the tag"))); + .andDo(document("tags-create-example", + requestFields( + fieldWithPath("name").description("The name of the tag")))); } @Test @@ -274,12 +277,11 @@ public class ApiDocumentation { patch(noteLocation).contentType(MediaTypes.HAL_JSON).content( this.objectMapper.writeValueAsString(noteUpdate))) .andExpect(status().isNoContent()) - .andDo(document("note-update-example") - .withRequestFields( + .andDo(document("note-update-example", + requestFields( fieldWithPath("title").description("The title of the note").type(FieldType.STRING).optional(), fieldWithPath("body").description("The body of the note").type(FieldType.STRING).optional(), - fieldWithPath("tags").description("An array of tag resource URIs").optional())); - + fieldWithPath("tags").description("An array of tag resource URIs").optional()))); } @Test @@ -297,13 +299,13 @@ public class ApiDocumentation { this.mockMvc.perform(get(tagLocation)) .andExpect(status().isOk()) .andExpect(jsonPath("name", is(tag.get("name")))) - .andDo(document("tag-get-example") - .withLinks( + .andDo(document("tag-get-example", + links( linkWithRel("self").description("This <>"), - linkWithRel("notes").description("The <> that have this tag")) - .withResponseFields( + linkWithRel("notes").description("The <> that have this tag")), + responseFields( fieldWithPath("name").description("The name of the tag"), - fieldWithPath("_links").description("<> to other resources"))); + fieldWithPath("_links").description("<> to other resources")))); } @Test @@ -325,9 +327,9 @@ public class ApiDocumentation { patch(tagLocation).contentType(MediaTypes.HAL_JSON).content( this.objectMapper.writeValueAsString(tagUpdate))) .andExpect(status().isNoContent()) - .andDo(document("tag-update-example") - .withRequestFields( - fieldWithPath("name").description("The name of the tag"))); + .andDo(document("tag-update-example", + requestFields( + fieldWithPath("name").description("The name of the tag")))); } private void createNote(String title, String body) { diff --git a/samples/rest-notes-spring-hateoas/src/test/java/com/example/notes/ApiDocumentation.java b/samples/rest-notes-spring-hateoas/src/test/java/com/example/notes/ApiDocumentation.java index 1901ea85..c8c7dca2 100644 --- a/samples/rest-notes-spring-hateoas/src/test/java/com/example/notes/ApiDocumentation.java +++ b/samples/rest-notes-spring-hateoas/src/test/java/com/example/notes/ApiDocumentation.java @@ -21,7 +21,10 @@ import static org.hamcrest.Matchers.notNullValue; import static org.springframework.restdocs.RestDocumentation.document; import static org.springframework.restdocs.RestDocumentation.documentationConfiguration; import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.restdocs.RestDocumentationRequestBuilders.get; import static org.springframework.restdocs.RestDocumentationRequestBuilders.patch; import static org.springframework.restdocs.RestDocumentationRequestBuilders.post; @@ -89,25 +92,25 @@ public class ApiDocumentation { .andExpect(jsonPath("timestamp", is(notNullValue()))) .andExpect(jsonPath("status", is(400))) .andExpect(jsonPath("path", is(notNullValue()))) - .andDo(document("error-example") - .withResponseFields( + .andDo(document("error-example", + responseFields( fieldWithPath("error").description("The HTTP error that occurred, e.g. `Bad Request`"), fieldWithPath("message").description("A description of the cause of the error"), fieldWithPath("path").description("The path to which the request was made"), fieldWithPath("status").description("The HTTP status code, e.g. `400`"), - fieldWithPath("timestamp").description("The time, in milliseconds, at which the error occurred"))); + fieldWithPath("timestamp").description("The time, in milliseconds, at which the error occurred")))); } @Test public void indexExample() throws Exception { this.mockMvc.perform(get("/")) .andExpect(status().isOk()) - .andDo(document("index-example") - .withLinks( + .andDo(document("index-example", + links( linkWithRel("notes").description("The <>"), - linkWithRel("tags").description("The <>")) - .withResponseFields( - fieldWithPath("_links").description("<> to other resources"))); + linkWithRel("tags").description("The <>")), + responseFields( + fieldWithPath("_links").description("<> to other resources")))); } @Test @@ -122,9 +125,9 @@ public class ApiDocumentation { this.mockMvc.perform(get("/notes")) .andExpect(status().isOk()) - .andDo(document("notes-list-example") - .withResponseFields( - fieldWithPath("_embedded.notes").description("An array of <>"))); + .andDo(document("notes-list-example", + responseFields( + fieldWithPath("_embedded.notes").description("An array of <>")))); } @Test @@ -148,11 +151,11 @@ public class ApiDocumentation { post("/notes").contentType(MediaTypes.HAL_JSON).content( this.objectMapper.writeValueAsString(note))) .andExpect(status().isCreated()) - .andDo(document("notes-create-example") - .withRequestFields( + .andDo(document("notes-create-example", + requestFields( fieldWithPath("title").description("The title of the note"), fieldWithPath("body").description("The body of the note"), - fieldWithPath("tags").description("An array of tag resource URIs"))); + fieldWithPath("tags").description("An array of tag resource URIs")))); } @Test @@ -185,14 +188,14 @@ 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( + .andDo(document("note-get-example", + links( linkWithRel("self").description("This <>"), - linkWithRel("note-tags").description("This note's <>")) - .withResponseFields( + linkWithRel("note-tags").description("This note's <>")), + responseFields( fieldWithPath("title").description("The title of the note"), fieldWithPath("body").description("The body of the note"), - fieldWithPath("_links").description("<> to other resources"))); + fieldWithPath("_links").description("<> to other resources")))); } @@ -207,9 +210,9 @@ public class ApiDocumentation { this.mockMvc.perform(get("/tags")) .andExpect(status().isOk()) - .andDo(document("tags-list-example") - .withResponseFields( - fieldWithPath("_embedded.tags").description("An array of <>"))); + .andDo(document("tags-list-example", + responseFields( + fieldWithPath("_embedded.tags").description("An array of <>")))); } @Test @@ -221,9 +224,9 @@ public class ApiDocumentation { post("/tags").contentType(MediaTypes.HAL_JSON).content( this.objectMapper.writeValueAsString(tag))) .andExpect(status().isCreated()) - .andDo(document("tags-create-example") - .withRequestFields( - fieldWithPath("name").description("The name of the tag"))); + .andDo(document("tags-create-example", + requestFields( + fieldWithPath("name").description("The name of the tag")))); } @Test @@ -262,11 +265,11 @@ public class ApiDocumentation { patch(noteLocation).contentType(MediaTypes.HAL_JSON).content( this.objectMapper.writeValueAsString(noteUpdate))) .andExpect(status().isNoContent()) - .andDo(document("note-update-example") - .withRequestFields( + .andDo(document("note-update-example", + requestFields( fieldWithPath("title").description("The title of the note").type(FieldType.STRING).optional(), fieldWithPath("body").description("The body of the note").type(FieldType.STRING).optional(), - fieldWithPath("tags").description("An array of tag resource URIs").optional())); + fieldWithPath("tags").description("An array of tag resource URIs").optional()))); } @Test @@ -284,13 +287,13 @@ public class ApiDocumentation { this.mockMvc.perform(get(tagLocation)) .andExpect(status().isOk()) .andExpect(jsonPath("name", is(tag.get("name")))) - .andDo(document("tag-get-example") - .withLinks( + .andDo(document("tag-get-example", + links( linkWithRel("self").description("This <>"), - linkWithRel("tagged-notes").description("The <> that have this tag")) - .withResponseFields( + linkWithRel("tagged-notes").description("The <> that have this tag")), + responseFields( fieldWithPath("name").description("The name of the tag"), - fieldWithPath("_links").description("<> to other resources"))); + fieldWithPath("_links").description("<> to other resources")))); } @Test @@ -312,9 +315,9 @@ public class ApiDocumentation { patch(tagLocation).contentType(MediaTypes.HAL_JSON).content( this.objectMapper.writeValueAsString(tagUpdate))) .andExpect(status().isNoContent()) - .andDo(document("tag-update-example") - .withRequestFields( - fieldWithPath("name").description("The name of the tag"))); + .andDo(document("tag-update-example", + requestFields( + fieldWithPath("name").description("The name of the tag")))); } private void createNote(String title, String body) { diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/ResponseModifier.java b/spring-restdocs/src/main/java/org/springframework/restdocs/ResponseModifier.java index e2002904..a72ded80 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/ResponseModifier.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/ResponseModifier.java @@ -27,6 +27,7 @@ import org.springframework.cglib.proxy.MethodProxy; import org.springframework.core.BridgeMethodResolver; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.restdocs.response.ResponsePostProcessor; +import org.springframework.restdocs.snippet.Snippet; import org.springframework.test.web.servlet.MvcResult; import org.springframework.util.ReflectionUtils; @@ -47,19 +48,22 @@ public final class ResponseModifier { /** * Provides a {@link RestDocumentationResultHandler} that can be used to document the - * request and modified result. - * @param identifier An identifier for the API call that is being documented + * request and modified response. + * @param identifier an identifier for the API call that is being documented + * @param snippets the snippets to use to document the call * @return the result handler that will produce the documentation */ - public RestDocumentationResultHandler andDocument(String identifier) { - return new ResponseModifyingRestDocumentationResultHandler(identifier); + public RestDocumentationResultHandler andDocument(String identifier, + Snippet... snippets) { + return new ResponseModifyingRestDocumentationResultHandler(identifier, snippets); } class ResponseModifyingRestDocumentationResultHandler extends RestDocumentationResultHandler { - public ResponseModifyingRestDocumentationResultHandler(String identifier) { - super(identifier); + private ResponseModifyingRestDocumentationResultHandler(String identifier, + Snippet... snippets) { + super(identifier, snippets); } @Override diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentation.java b/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentation.java index 58f279d1..d5977b3c 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentation.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentation.java @@ -19,6 +19,7 @@ package org.springframework.restdocs; import org.springframework.restdocs.config.RestDocumentationConfigurer; import org.springframework.restdocs.response.ResponsePostProcessor; import org.springframework.restdocs.response.ResponsePostProcessors; +import org.springframework.restdocs.snippet.Snippet; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultActions; @@ -39,6 +40,7 @@ public abstract class RestDocumentation { /** * Provides access to a {@link MockMvcConfigurer} that can be used to configure the * REST documentation when building a {@link MockMvc} instance. + * * @see ConfigurableMockMvcBuilder#apply(MockMvcConfigurer) * @return the configurer */ @@ -47,15 +49,18 @@ public abstract class RestDocumentation { } /** - * Documents the API call using the given {@code identifier}. + * Documents the API call with the given {@code identifier} using the given + * {@code handlers}. * - * @param identifier An identifier for the API call that is being documented + * @param identifier an identifier for the API call that is being documented + * @param snippets the snippets that will document the API call * @return a Mock MVC {@code ResultHandler} that will produce the documentation * @see MockMvc#perform(org.springframework.test.web.servlet.RequestBuilder) * @see ResultActions#andDo(org.springframework.test.web.servlet.ResultHandler) */ - public static RestDocumentationResultHandler document(String identifier) { - return new RestDocumentationResultHandler(identifier); + public static RestDocumentationResultHandler document(String identifier, + Snippet... snippets) { + return new RestDocumentationResultHandler(identifier, snippets); } /** diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentationRequestBuilders.java b/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentationRequestBuilders.java index e702fdaf..160006d4 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentationRequestBuilders.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentationRequestBuilders.java @@ -19,6 +19,7 @@ package org.springframework.restdocs; import java.net.URI; import org.springframework.http.HttpMethod; +import org.springframework.restdocs.request.RequestDocumentation; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; @@ -26,13 +27,13 @@ import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; /** * A drop-in replacement for {@link MockMvcRequestBuilders} that captures a request's URL * template and makes it available for documentation. Required when - * {@link RestDocumentationResultHandler#withPathParameters(org.springframework.restdocs .request.ParameterDescriptor...) - * documenting path parameters} and recommended for general usage. + * {@link RequestDocumentation#pathParameters(org.springframework.restdocs.request.ParameterDescriptor...) + * ) documenting path parameters} and recommended for general usage. * * @author Andy Wilkinson * @see MockMvcRequestBuilders - * @see RestDocumentationResultHandler#withPathParameters(org.springframework.restdocs.request.ParameterDescriptor...) - * @see RestDocumentationResultHandler#withPathParameters(java.util.Map, + * @see RequestDocumentation#pathParameters(org.springframework.restdocs.request.ParameterDescriptor...) + * @see RequestDocumentation#pathParameters(java.util.Map, * org.springframework.restdocs.request.ParameterDescriptor...) */ public abstract class RestDocumentationRequestBuilders { diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentationResultHandler.java b/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentationResultHandler.java index c3930548..68d59555 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentationResultHandler.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentationResultHandler.java @@ -16,28 +16,11 @@ package org.springframework.restdocs; -import static org.springframework.restdocs.curl.CurlDocumentation.documentCurlRequest; -import static org.springframework.restdocs.http.HttpDocumentation.documentHttpRequest; -import static org.springframework.restdocs.http.HttpDocumentation.documentHttpResponse; -import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.documentLinks; -import static org.springframework.restdocs.payload.PayloadDocumentation.documentRequestFields; -import static org.springframework.restdocs.payload.PayloadDocumentation.documentResponseFields; -import static org.springframework.restdocs.request.RequestDocumentation.documentPathParameters; -import static org.springframework.restdocs.request.RequestDocumentation.documentQueryParameters; - import java.util.ArrayList; +import java.util.Arrays; import java.util.List; -import java.util.Map; -import org.springframework.restdocs.hypermedia.HypermediaDocumentation; -import org.springframework.restdocs.hypermedia.LinkDescriptor; -import org.springframework.restdocs.hypermedia.LinkExtractor; -import org.springframework.restdocs.hypermedia.LinkExtractors; -import org.springframework.restdocs.payload.FieldDescriptor; -import org.springframework.restdocs.payload.PayloadDocumentation; -import org.springframework.restdocs.request.ParameterDescriptor; -import org.springframework.restdocs.request.RequestDocumentation; -import org.springframework.restdocs.snippet.SnippetWritingResultHandler; +import org.springframework.restdocs.snippet.Snippet; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultHandler; @@ -46,317 +29,32 @@ import org.springframework.test.web.servlet.ResultHandler; * * @author Andy Wilkinson * @author Andreas Evers - * @see RestDocumentation#document(String) + * @see RestDocumentation#document(String, Snippet...) */ public class RestDocumentationResultHandler implements ResultHandler { private final String identifier; - private SnippetWritingResultHandler curlRequest; + private final List snippets; - private SnippetWritingResultHandler httpRequest; - - private SnippetWritingResultHandler httpResponse; - - private List delegates = new ArrayList<>(); - - RestDocumentationResultHandler(String identifier) { + RestDocumentationResultHandler(String identifier, Snippet... snippets) { this.identifier = identifier; - this.curlRequest = documentCurlRequest(this.identifier, null); - this.httpRequest = documentHttpRequest(this.identifier, null); - this.httpResponse = documentHttpResponse(this.identifier, null); - } - - /** - * Customizes the default curl request snippet generation to make the given attributes - * available. - * - * @param attributes the attributes - * @return {@code this} - */ - public RestDocumentationResultHandler withCurlRequest(Map attributes) { - this.curlRequest = documentCurlRequest(this.identifier, attributes); - return this; - } - - /** - * Customizes the default HTTP request snippet generation to make the given attributes - * available. - * - * @param attributes the attributes - * @return {@code this} - */ - public RestDocumentationResultHandler withHttpRequest(Map attributes) { - this.httpRequest = documentHttpRequest(this.identifier, attributes); - return this; - } - - /** - * Customizes the default HTTP response snippet generation to make the given - * attributes available. - * - * @param attributes the attributes - * @return {@code this} - */ - public RestDocumentationResultHandler withHttpResponse(Map attributes) { - this.httpResponse = documentHttpResponse(this.identifier, attributes); - return this; - } - - /** - * Document the links in the response using the given {@code descriptors}. The links - * are extracted from the response based on its content type. - *

- * If a link is present in the response but is not described by one of the descriptors - * a failure will occur when this handler is invoked. Similarly, if a link is - * described but is not present in the response a failure will also occur when this - * handler is invoked. - * - * @param descriptors the link descriptors - * @return {@code this} - * @see HypermediaDocumentation#linkWithRel(String) - * @see LinkExtractors#extractorForContentType(String) - */ - public RestDocumentationResultHandler withLinks(LinkDescriptor... descriptors) { - return withLinks(null, null, descriptors); - } - - /** - * Document the links in the response using the given {@code descriptors}. The links - * are extracted from the response using the given {@code linkExtractor}. - *

- * If a link is present in the response but is not described by one of the descriptors - * a failure will occur when this handler is invoked. Similarly, if a link is - * described but is not present in the response a failure will also occur when this - * handler is invoked. - * - * @param linkExtractor used to extract the links from the response - * @param descriptors the link descriptors - * @return {@code this} - * @see HypermediaDocumentation#linkWithRel(String) - */ - public RestDocumentationResultHandler withLinks(LinkExtractor linkExtractor, - LinkDescriptor... descriptors) { - return this.withLinks(null, linkExtractor, descriptors); - } - - /** - * Document the links in the response using the given {@code descriptors}. The links - * are extracted from the response based on its content type. The given - * {@code attributes} are made available during the generation of the links snippet. - *

- * If a link is present in the response but is not described by one of the descriptors - * a failure will occur when this handler is invoked. Similarly, if a link is - * described but is not present in the response a failure will also occur when this - * handler is invoked. - * - * @param attributes the attributes - * @param descriptors the link descriptors - * @return {@code this} - * @see HypermediaDocumentation#linkWithRel(String) - * @see LinkExtractors#extractorForContentType(String) - */ - public RestDocumentationResultHandler withLinks(Map attributes, - LinkDescriptor... descriptors) { - return withLinks(attributes, null, descriptors); - } - - /** - * Document the links in the response using the given {@code descriptors}. The links - * are extracted from the response using the given {@code linkExtractor}. The given - * {@code attributes} are made available during the generation of the links snippet. - *

- * If a link is present in the response but is not described by one of the descriptors - * a failure will occur when this handler is invoked. Similarly, if a link is - * described but is not present in the response a failure will also occur when this - * handler is invoked. - * - * @param attributes the attributes - * @param linkExtractor used to extract the links from the response - * @param descriptors the link descriptors - * @return {@code this} - * @see HypermediaDocumentation#linkWithRel(String) - */ - public RestDocumentationResultHandler withLinks(Map attributes, - LinkExtractor linkExtractor, LinkDescriptor... descriptors) { - this.delegates.add(documentLinks(this.identifier, attributes, linkExtractor, - descriptors)); - return this; - } - - /** - * Document the fields in the request using the given {@code descriptors}. - *

- * If a field is present in the request but is not documented by one of the - * descriptors a failure will occur when this handler is invoked. Similarly, if a - * field is documented, is not marked as optional, and is not present in the request a - * failure will also occur. For payloads with a hierarchical structure, documenting a - * field is sufficient for all of its descendants to also be treated as having been - * documented. - * - * @param descriptors the link descriptors - * @return {@code this} - * @see PayloadDocumentation#fieldWithPath(String) - */ - public RestDocumentationResultHandler withRequestFields( - FieldDescriptor... descriptors) { - return this.withRequestFields(null, descriptors); - } - - /** - * Document the fields in the request using the given {@code descriptors}. The given - * {@code attributes} are made available during the generation of the request fields - * snippet. - *

- * If a field is present in the request but is not documented by one of the - * descriptors a failure will occur when this handler is invoked. Similarly, if a - * field is documented, is not marked as optional, and is not present in the request a - * failure will also occur. For payloads with a hierarchical structure, documenting a - * field is sufficient for all of its descendants to also be treated as having been - * documented. - * - * @param descriptors the link descriptors - * @param attributes the attributes - * @return {@code this} - * @see PayloadDocumentation#fieldWithPath(String) - */ - public RestDocumentationResultHandler withRequestFields( - Map attributes, FieldDescriptor... descriptors) { - this.delegates - .add(documentRequestFields(this.identifier, attributes, descriptors)); - return this; - } - - /** - * Document the fields in the response using the given {@code descriptors}. - *

- * If a field is present in the response but is not documented by one of the - * descriptors a failure will occur when this handler is invoked. Similarly, if a - * field is documented, is not marked as optional, and is not present in the response - * a failure will also occur. For payloads with a hierarchical structure, documenting - * a field is sufficient for all of its descendants to also be treated as having been - * documented. - * - * @param descriptors the link descriptors - * @return {@code this} - * @see PayloadDocumentation#fieldWithPath(String) - */ - public RestDocumentationResultHandler withResponseFields( - FieldDescriptor... descriptors) { - return this.withResponseFields(null, descriptors); - } - - /** - * Document the fields in the response using the given {@code descriptors}. The given - * {@code attributes} are made available during the generation of the request fields - * snippet. - *

- * If a field is present in the response but is not documented by one of the - * descriptors a failure will occur when this handler is invoked. Similarly, if a - * field is documented, is not marked as optional, and is not present in the response - * a failure will also occur. For payloads with a hierarchical structure, documenting - * a field is sufficient for all of its descendants to also be treated as having been - * documented. - * - * @param descriptors the link descriptors - * @param attributes the attributes - * @return {@code this} - * @see PayloadDocumentation#fieldWithPath(String) - */ - public RestDocumentationResultHandler withResponseFields( - Map attributes, FieldDescriptor... descriptors) { - this.delegates.add(documentResponseFields(this.identifier, attributes, - descriptors)); - return this; - } - - /** - * Documents the parameters in the request's query string using the given - * {@code descriptors}. - *

- * If a parameter is present in the query string but is not described by one of the - * descriptors a failure will occur when this handler is invoked. Similarly, if a - * parameter is described but is not present in the request a failure will also occur - * when this handler is invoked. - * - * @param descriptors the parameter descriptors - * @return {@code this} - * @see RequestDocumentation#parameterWithName(String) - */ - public RestDocumentationResultHandler withQueryParameters( - ParameterDescriptor... descriptors) { - return this.withQueryParameters(null, descriptors); - } - - /** - * Documents the parameters in the request's query string using the given - * {@code descriptors}. The given {@code attributes} are made available during the - * generation of the query parameters snippet. - *

- * If a parameter is present in the query string but is not described by one of the - * descriptors a failure will occur when this handler is invoked. Similarly, if a - * parameter is described but is not present in the request a failure will also occur - * when this handler is invoked. - * - * @param descriptors the parameter descriptors - * @param attributes the attributes - * @return {@code this} - * @see RequestDocumentation#parameterWithName(String) - */ - public RestDocumentationResultHandler withQueryParameters( - Map attributes, ParameterDescriptor... descriptors) { - this.delegates.add(documentQueryParameters(this.identifier, attributes, - descriptors)); - return this; - } - - /** - * Documents the parameters in the request's path using the given {@code descriptors}. - *

- * If a parameter is present in the path but is not described by one of the - * descriptors a failure will occur when this handler is invoked. Similarly, if a - * parameter is described but is not present in the request a failure will also occur - * when this handler is invoked. - * - * @param descriptors the parameter descriptors - * @return {@code this} - * @see RequestDocumentation#parameterWithName(String) - */ - public RestDocumentationResultHandler withPathParameters( - ParameterDescriptor... descriptors) { - return this.withPathParameters(null, descriptors); - } - - /** - * Documents the parameters in the request's path using the given {@code descriptors}. - * The given {@code attributes} are made available during the generation of the path - * parameters snippet. - *

- * If a parameter is present in the path but is not described by one of the - * descriptors a failure will occur when this handler is invoked. Similarly, if a - * parameter is described but is not present in the request a failure will also occur - * when this handler is invoked. - * - * @param descriptors the parameter descriptors - * @param attributes the attributes - * @return {@code this} - * @see RequestDocumentation#parameterWithName(String) - */ - public RestDocumentationResultHandler withPathParameters( - Map attributes, ParameterDescriptor... descriptors) { - this.delegates.add(documentPathParameters(this.identifier, attributes, - descriptors)); - return this; + this.snippets = Arrays.asList(snippets); } @Override public void handle(MvcResult result) throws Exception { - this.curlRequest.handle(result); - this.httpRequest.handle(result); - this.httpResponse.handle(result); - for (ResultHandler delegate : this.delegates) { - delegate.handle(result); + for (Snippet snippet : getSnippets(result)) { + snippet.document(this.identifier, result); } } + @SuppressWarnings("unchecked") + private List getSnippets(MvcResult result) { + List combinedSnippets = new ArrayList<>((List) result.getRequest() + .getAttribute("org.springframework.restdocs.defaultSnippets")); + combinedSnippets.addAll(this.snippets); + return combinedSnippets; + } + } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/config/AbstractConfigurer.java b/spring-restdocs/src/main/java/org/springframework/restdocs/config/AbstractConfigurer.java index a8120c52..f8453058 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/config/AbstractConfigurer.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/config/AbstractConfigurer.java @@ -27,7 +27,7 @@ import org.springframework.mock.web.MockHttpServletRequest; abstract class AbstractConfigurer { /** - * Applies the configuration, possibly be modifying the given {@code request} + * Applies the configuration, possibly by modifying the given {@code request} * @param request the request that may be modified */ abstract void apply(MockHttpServletRequest request); diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/config/RestDocumentationConfigurer.java b/spring-restdocs/src/main/java/org/springframework/restdocs/config/RestDocumentationConfigurer.java index ba930420..2f9417b9 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/config/RestDocumentationConfigurer.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/config/RestDocumentationConfigurer.java @@ -49,9 +49,9 @@ public class RestDocumentationConfigurer extends MockMvcConfigurerAdapter { private final RequestPostProcessor requestPostProcessor; - private TemplateEngineConfigurer templateEngineConfigurer = new TemplateEngineConfigurer(); + private final TemplateEngineConfigurer templateEngineConfigurer = new TemplateEngineConfigurer(); - private WriterResolverConfigurer writerResolverConfigurer = new WriterResolverConfigurer(); + private final WriterResolverConfigurer writerResolverConfigurer = new WriterResolverConfigurer(); /** * Creates a new {@link RestDocumentationConfigurer}. @@ -65,19 +65,44 @@ public class RestDocumentationConfigurer extends MockMvcConfigurerAdapter { this.templateEngineConfigurer)); } + /** + * Returns a {@link UriConfigurer} that can be used to configure the request URIs that + * will be documented. + * + * @return the URI configurer + */ public UriConfigurer uris() { return this.uriConfigurer; } + /** + * Returns a {@link SnippetConfigurer} that can be used to configure the snippets that + * will be generated. + * + * @return the snippet configurer + */ public SnippetConfigurer snippets() { return this.snippetConfigurer; } + /** + * Configures the {@link TemplateEngine} that will be used for snippet rendering. + * + * @param templateEngine the template engine to use + * @return {@code this} + */ public RestDocumentationConfigurer templateEngine(TemplateEngine templateEngine) { this.templateEngineConfigurer.setTemplateEngine(templateEngine); return this; } + /** + * Configures the {@link WriterResolver} that will be used to resolve a writer for a + * snippet. + * + * @param writerResolver The writer resolver to use + * @return {@code this} + */ public RestDocumentationConfigurer writerResolver(WriterResolver writerResolver) { this.writerResolverConfigurer.setWriterResolver(writerResolver); return this; @@ -175,4 +200,5 @@ public class RestDocumentationConfigurer extends MockMvcConfigurerAdapter { } } + } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/config/RestDocumentationContext.java b/spring-restdocs/src/main/java/org/springframework/restdocs/config/RestDocumentationContext.java index f5b3f2dc..2a95a7f1 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/config/RestDocumentationContext.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/config/RestDocumentationContext.java @@ -33,10 +33,12 @@ public final class RestDocumentationContext { private final TestContext testContext; - public RestDocumentationContext() { - this(null); - } - + /** + * Creates a new {@code RestDocumentationContext} backed by the given + * {@code testContext}. + * + * @param testContext the test context + */ public RestDocumentationContext(TestContext testContext) { this.testContext = testContext; } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/config/RestDocumentationContextPlaceholderResolver.java b/spring-restdocs/src/main/java/org/springframework/restdocs/config/RestDocumentationContextPlaceholderResolver.java index 337caf86..6a9645cd 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/config/RestDocumentationContextPlaceholderResolver.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/config/RestDocumentationContextPlaceholderResolver.java @@ -46,6 +46,12 @@ public class RestDocumentationContextPlaceholderResolver implements PlaceholderR private final RestDocumentationContext context; + /** + * Creates a new placeholder resolver that will resolve placeholders using the given + * {@code context}. + * + * @param context the context to use + */ public RestDocumentationContextPlaceholderResolver(RestDocumentationContext context) { this.context = context; } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/config/SnippetConfigurer.java b/spring-restdocs/src/main/java/org/springframework/restdocs/config/SnippetConfigurer.java index ae992f7f..992086f7 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/config/SnippetConfigurer.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/config/SnippetConfigurer.java @@ -16,7 +16,13 @@ package org.springframework.restdocs.config; +import java.util.Arrays; +import java.util.List; + import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.restdocs.curl.CurlDocumentation; +import org.springframework.restdocs.http.HttpDocumentation; +import org.springframework.restdocs.snippet.Snippet; import org.springframework.restdocs.snippet.WriterResolver; /** @@ -28,6 +34,10 @@ import org.springframework.restdocs.snippet.WriterResolver; public class SnippetConfigurer extends AbstractNestedConfigurer { + private List defaultSnippets = Arrays.asList( + CurlDocumentation.curlRequest(), HttpDocumentation.httpRequest(), + HttpDocumentation.httpResponse()); + /** * The default encoding for documentation snippets * @see #withEncoding(String) @@ -43,7 +53,7 @@ public class SnippetConfigurer extends /** * Configures any documentation snippets to be written using the given * {@code encoding}. The default is UTF-8. - * @param encoding The encoding + * @param encoding the encoding * @return {@code this} */ public SnippetConfigurer withEncoding(String encoding) { @@ -55,5 +65,18 @@ public class SnippetConfigurer extends void apply(MockHttpServletRequest request) { ((WriterResolver) request.getAttribute(WriterResolver.class.getName())) .setEncoding(this.snippetEncoding); + request.setAttribute("org.springframework.restdocs.defaultSnippets", + this.defaultSnippets); + } + + /** + * Configures the documentation snippets that will be produced by default. + * + * @param defaultSnippets the default snippets + * @return {@code this} + */ + public SnippetConfigurer withDefaults(Snippet... defaultSnippets) { + this.defaultSnippets = Arrays.asList(defaultSnippets); + return this; } } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/curl/CurlDocumentation.java b/spring-restdocs/src/main/java/org/springframework/restdocs/curl/CurlDocumentation.java index 12b40b7e..be692cc9 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/curl/CurlDocumentation.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/curl/CurlDocumentation.java @@ -16,19 +16,9 @@ package org.springframework.restdocs.curl; -import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.HashMap; -import java.util.List; import java.util.Map; -import java.util.Map.Entry; -import org.springframework.restdocs.snippet.DocumentableHttpServletRequest; -import org.springframework.restdocs.snippet.SnippetWritingResultHandler; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.util.StringUtils; -import org.springframework.web.multipart.MultipartFile; +import org.springframework.restdocs.snippet.Snippet; /** * Static factory methods for documenting a RESTful API as if it were being driven using @@ -46,151 +36,25 @@ public abstract class CurlDocumentation { } /** - * Produces a documentation snippet containing the request formatted as a cURL command + * Returns a handler that will produce a snippet containing the curl request for the + * API call. + * + * @return the handler that will produce the snippet + */ + public static Snippet curlRequest() { + return new CurlRequestSnippet(); + } + + /** + * Returns a handler that will produce a snippet containing the curl request for the + * API call. The given {@code attributes} will be available during snippet generation. * - * @param identifier An identifier for the API call that is being documented * @param attributes Attributes made available during rendering of the curl request * snippet * @return the handler that will produce the snippet */ - public static SnippetWritingResultHandler documentCurlRequest(String identifier, - Map attributes) { - return new CurlRequestWritingResultHandler(identifier, attributes); - } - - private static final class CurlRequestWritingResultHandler extends - SnippetWritingResultHandler { - - private static final String SCHEME_HTTP = "http"; - - private static final String SCHEME_HTTPS = "https"; - - private static final int STANDARD_PORT_HTTP = 80; - - private static final int STANDARD_PORT_HTTPS = 443; - - private CurlRequestWritingResultHandler(String identifier, - Map attributes) { - super(identifier, "curl-request", attributes); - } - - @Override - public Map doHandle(MvcResult result) throws IOException { - Map model = new HashMap(); - model.put("arguments", getCurlCommandArguments(result)); - return model; - } - - private String getCurlCommandArguments(MvcResult result) throws IOException { - StringWriter command = new StringWriter(); - PrintWriter printer = new PrintWriter(command); - DocumentableHttpServletRequest request = new DocumentableHttpServletRequest( - result.getRequest()); - - printer.print("'"); - writeAuthority(request, printer); - writePathAndQueryString(request, printer); - printer.print("'"); - - writeOptionToIncludeHeadersInOutput(printer); - writeHttpMethodIfNecessary(request, printer); - writeHeaders(request, printer); - - if (request.isMultipartRequest()) { - writeParts(request, printer); - } - - writeContent(request, printer); - - return command.toString(); - } - - private void writeAuthority(DocumentableHttpServletRequest request, - PrintWriter writer) { - writer.print(String.format("%s://%s", request.getScheme(), request.getHost())); - - if (isNonStandardPort(request)) { - writer.print(String.format(":%d", request.getPort())); - } - } - - private boolean isNonStandardPort(DocumentableHttpServletRequest request) { - return (SCHEME_HTTP.equals(request.getScheme()) && request.getPort() != STANDARD_PORT_HTTP) - || (SCHEME_HTTPS.equals(request.getScheme()) && request.getPort() != STANDARD_PORT_HTTPS); - } - - private void writePathAndQueryString(DocumentableHttpServletRequest request, - PrintWriter writer) { - if (StringUtils.hasText(request.getContextPath())) { - writer.print(String.format( - request.getContextPath().startsWith("/") ? "%s" : "/%s", - request.getContextPath())); - } - - writer.print(request.getRequestUriWithQueryString()); - } - - private void writeOptionToIncludeHeadersInOutput(PrintWriter writer) { - writer.print(" -i"); - } - - private void writeHttpMethodIfNecessary(DocumentableHttpServletRequest request, - PrintWriter writer) { - if (!request.isGetRequest()) { - writer.print(String.format(" -X %s", request.getMethod())); - } - } - - private void writeHeaders(DocumentableHttpServletRequest request, - PrintWriter writer) { - for (Entry> entry : request.getHeaders().entrySet()) { - for (String header : entry.getValue()) { - writer.print(String.format(" -H '%s: %s'", entry.getKey(), header)); - } - } - } - - private void writeParts(DocumentableHttpServletRequest request, PrintWriter writer) - throws IOException { - for (Entry> entry : request.getMultipartFiles() - .entrySet()) { - for (MultipartFile file : entry.getValue()) { - writer.printf(" -F '%s=", file.getName()); - if (!StringUtils.hasText(file.getOriginalFilename())) { - writer.append(new String(file.getBytes())); - } - else { - writer.printf("@%s", file.getOriginalFilename()); - } - - if (StringUtils.hasText(file.getContentType())) { - writer.append(";type=").append(file.getContentType()); - } - writer.append("'"); - } - } - - } - - private void writeContent(DocumentableHttpServletRequest request, - PrintWriter writer) throws IOException { - if (request.getContentLength() > 0) { - writer.print(String.format(" -d '%s'", request.getContentAsString())); - } - else if (request.isMultipartRequest()) { - for (Entry entry : request.getParameterMap().entrySet()) { - for (String value : entry.getValue()) { - writer.print(String.format(" -F '%s=%s'", entry.getKey(), value)); - } - } - } - else if (request.isPostRequest() || request.isPutRequest()) { - String queryString = request.getParameterMapAsQueryString(); - if (StringUtils.hasText(queryString)) { - writer.print(String.format(" -d '%s'", queryString)); - } - } - } + public static Snippet curlRequest(Map attributes) { + return new CurlRequestSnippet(attributes); } } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/curl/CurlRequestSnippet.java b/spring-restdocs/src/main/java/org/springframework/restdocs/curl/CurlRequestSnippet.java new file mode 100644 index 00000000..a1e5e8fd --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/curl/CurlRequestSnippet.java @@ -0,0 +1,171 @@ +/* + * 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.curl; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.springframework.restdocs.snippet.DocumentableHttpServletRequest; +import org.springframework.restdocs.snippet.Snippet; +import org.springframework.restdocs.snippet.TemplatedSnippet; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.util.StringUtils; +import org.springframework.web.multipart.MultipartFile; + +/** + * A {@link Snippet} that documents the curl command for a request. + * + * @author Andy Wilkinson + */ +class CurlRequestSnippet extends TemplatedSnippet { + + private static final String SCHEME_HTTP = "http"; + + private static final String SCHEME_HTTPS = "https"; + + private static final int STANDARD_PORT_HTTP = 80; + + private static final int STANDARD_PORT_HTTPS = 443; + + CurlRequestSnippet() { + this(null); + } + + CurlRequestSnippet(Map attributes) { + super("curl-request", attributes); + } + + @Override + public Map document(MvcResult result) throws IOException { + Map model = new HashMap(); + model.put("arguments", getCurlCommandArguments(result)); + return model; + } + + private String getCurlCommandArguments(MvcResult result) throws IOException { + StringWriter command = new StringWriter(); + PrintWriter printer = new PrintWriter(command); + DocumentableHttpServletRequest request = new DocumentableHttpServletRequest( + result.getRequest()); + + printer.print("'"); + writeAuthority(request, printer); + writePathAndQueryString(request, printer); + printer.print("'"); + + writeOptionToIncludeHeadersInOutput(printer); + writeHttpMethodIfNecessary(request, printer); + writeHeaders(request, printer); + + if (request.isMultipartRequest()) { + writeParts(request, printer); + } + + writeContent(request, printer); + + return command.toString(); + } + + private void writeAuthority(DocumentableHttpServletRequest request, PrintWriter writer) { + writer.print(String.format("%s://%s", request.getScheme(), request.getHost())); + + if (isNonStandardPort(request)) { + writer.print(String.format(":%d", request.getPort())); + } + } + + private boolean isNonStandardPort(DocumentableHttpServletRequest request) { + return (SCHEME_HTTP.equals(request.getScheme()) && request.getPort() != STANDARD_PORT_HTTP) + || (SCHEME_HTTPS.equals(request.getScheme()) && request.getPort() != STANDARD_PORT_HTTPS); + } + + private void writePathAndQueryString(DocumentableHttpServletRequest request, + PrintWriter writer) { + if (StringUtils.hasText(request.getContextPath())) { + writer.print(String.format(request.getContextPath().startsWith("/") ? "%s" + : "/%s", request.getContextPath())); + } + + writer.print(request.getRequestUriWithQueryString()); + } + + private void writeOptionToIncludeHeadersInOutput(PrintWriter writer) { + writer.print(" -i"); + } + + private void writeHttpMethodIfNecessary(DocumentableHttpServletRequest request, + PrintWriter writer) { + if (!request.isGetRequest()) { + writer.print(String.format(" -X %s", request.getMethod())); + } + } + + private void writeHeaders(DocumentableHttpServletRequest request, PrintWriter writer) { + for (Entry> entry : request.getHeaders().entrySet()) { + for (String header : entry.getValue()) { + writer.print(String.format(" -H '%s: %s'", entry.getKey(), header)); + } + } + } + + private void writeParts(DocumentableHttpServletRequest request, PrintWriter writer) + throws IOException { + for (Entry> entry : request.getMultipartFiles() + .entrySet()) { + for (MultipartFile file : entry.getValue()) { + writer.printf(" -F '%s=", file.getName()); + if (!StringUtils.hasText(file.getOriginalFilename())) { + writer.append(new String(file.getBytes())); + } + else { + writer.printf("@%s", file.getOriginalFilename()); + } + + if (StringUtils.hasText(file.getContentType())) { + writer.append(";type=").append(file.getContentType()); + } + writer.append("'"); + } + } + + } + + private void writeContent(DocumentableHttpServletRequest request, PrintWriter writer) + throws IOException { + if (request.getContentLength() > 0) { + writer.print(String.format(" -d '%s'", request.getContentAsString())); + } + else if (request.isMultipartRequest()) { + for (Entry entry : request.getParameterMap().entrySet()) { + for (String value : entry.getValue()) { + writer.print(String.format(" -F '%s=%s'", entry.getKey(), value)); + } + } + } + else if (request.isPostRequest() || request.isPutRequest()) { + String queryString = request.getParameterMapAsQueryString(); + if (StringUtils.hasText(queryString)) { + writer.print(String.format(" -d '%s'", queryString)); + } + } + } +} \ No newline at end of file diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/http/HttpDocumentation.java b/spring-restdocs/src/main/java/org/springframework/restdocs/http/HttpDocumentation.java index b24618bb..c097e40e 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/http/HttpDocumentation.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/http/HttpDocumentation.java @@ -16,23 +16,9 @@ package org.springframework.restdocs.http; -import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; import java.util.Map; -import java.util.Map.Entry; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.restdocs.snippet.DocumentableHttpServletRequest; -import org.springframework.restdocs.snippet.SnippetWritingResultHandler; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.util.StringUtils; -import org.springframework.web.multipart.MultipartFile; +import org.springframework.restdocs.snippet.Snippet; /** * Static factory methods for documenting a RESTful API's HTTP requests. @@ -42,213 +28,49 @@ import org.springframework.web.multipart.MultipartFile; */ public abstract class HttpDocumentation { - private static final String MULTIPART_BOUNDARY = "6o2knFse3p53ty9dmcQvWAIx1zInP11uCfbm"; - private HttpDocumentation() { } /** - * Produces a documentation snippet containing the request formatted as an HTTP - * request + * Returns a handler that will produce a snippet containing the HTTP request for the + * API call. * - * @param identifier An identifier for the API call that is being documented - * @param attributes Attributes made available during rendering of the HTTP requst - * snippet * @return the handler that will produce the snippet */ - public static SnippetWritingResultHandler documentHttpRequest(String identifier, - Map attributes) { - return new HttpRequestWritingResultHandler(identifier, attributes); + public static Snippet httpRequest() { + return new HttpRequestSnippet(); } /** - * Produces a documentation snippet containing the response formatted as the HTTP - * response sent by the server - * - * @param identifier An identifier for the API call that is being documented - * @param attributes Attributes made available during rendering of the HTTP response - * snippet + * Returns a handler that will produce a snippet containing the HTTP request for the + * API call. The given {@code attributes} will be available during snippet generation. + * + * @param attributes the attributes * @return the handler that will produce the snippet */ - public static SnippetWritingResultHandler documentHttpResponse(String identifier, - Map attributes) { - return new HttpResponseWritingResultHandler(identifier, attributes); - + public static Snippet httpRequest(Map attributes) { + return new HttpRequestSnippet(attributes); } - private static final class HttpRequestWritingResultHandler extends - SnippetWritingResultHandler { - - private HttpRequestWritingResultHandler(String identifier, - Map attributes) { - super(identifier, "http-request", attributes); - } - - @Override - public Map doHandle(MvcResult result) throws IOException { - DocumentableHttpServletRequest request = new DocumentableHttpServletRequest( - result.getRequest()); - Map model = new HashMap(); - model.put("method", result.getRequest().getMethod()); - model.put("path", request.getRequestUriWithQueryString()); - model.put("headers", getHeaders(request)); - model.put("requestBody", getRequestBody(request)); - return model; - } - - private List> getHeaders( - DocumentableHttpServletRequest request) { - List> headers = new ArrayList<>(); - if (requiresHostHeader(request)) { - headers.add(header(HttpHeaders.HOST, request.getHost())); - } - - for (Entry> header : request.getHeaders().entrySet()) { - for (String value : header.getValue()) { - if (header.getKey() == HttpHeaders.CONTENT_TYPE - && request.isMultipartRequest()) { - headers.add(header(header.getKey(), String.format( - "%s; boundary=%s", value, MULTIPART_BOUNDARY))); - } - else { - headers.add(header(header.getKey(), value)); - } - - } - } - if (requiresFormEncodingContentType(request)) { - headers.add(header(HttpHeaders.CONTENT_TYPE, - MediaType.APPLICATION_FORM_URLENCODED_VALUE)); - } - return headers; - } - - private String getRequestBody(DocumentableHttpServletRequest request) - throws IOException { - StringWriter httpRequest = new StringWriter(); - PrintWriter writer = new PrintWriter(httpRequest); - if (request.getContentLength() > 0) { - writer.println(); - writer.print(request.getContentAsString()); - } - else if (request.isPostRequest() || request.isPutRequest()) { - if (request.isMultipartRequest()) { - writeParts(request, writer); - } - else { - String queryString = request.getParameterMapAsQueryString(); - if (StringUtils.hasText(queryString)) { - writer.println(); - writer.print(queryString); - } - } - } - return httpRequest.toString(); - } - - private void writeParts(DocumentableHttpServletRequest request, PrintWriter writer) - throws IOException { - writer.println(); - for (Entry parameter : request.getParameterMap().entrySet()) { - for (String value : parameter.getValue()) { - writePartBoundary(writer); - writePart(parameter.getKey(), value, null, writer); - writer.println(); - } - } - for (Entry> entry : request.getMultipartFiles() - .entrySet()) { - for (MultipartFile file : entry.getValue()) { - writePartBoundary(writer); - writePart(file, writer); - writer.println(); - } - } - writeMultipartEnd(writer); - } - - private void writePartBoundary(PrintWriter writer) { - writer.printf("--%s%n", MULTIPART_BOUNDARY); - } - - private void writePart(String name, String value, String contentType, - PrintWriter writer) { - writer.printf("Content-Disposition: form-data; name=%s%n", name); - if (StringUtils.hasText(contentType)) { - writer.printf("Content-Type: %s%n", contentType); - } - writer.println(); - writer.print(value); - } - - private void writePart(MultipartFile part, PrintWriter writer) throws IOException { - writePart(part.getName(), new String(part.getBytes()), part.getContentType(), - writer); - } - - private void writeMultipartEnd(PrintWriter writer) { - writer.printf("--%s--", MULTIPART_BOUNDARY); - } - - private boolean requiresHostHeader(DocumentableHttpServletRequest request) { - return request.getHeaders().get(HttpHeaders.HOST) == null; - } - - private boolean requiresFormEncodingContentType( - DocumentableHttpServletRequest request) { - return request.getHeaders().getContentType() == null - && (request.isPostRequest() || request.isPutRequest()) - && StringUtils.hasText(request.getParameterMapAsQueryString()); - } - - private Map header(String name, String value) { - Map header = new HashMap<>(); - header.put("name", name); - header.put("value", value); - return header; - } + /** + * Returns a handler that will produce a snippet containing the HTTP response for the + * API call. + * @return the handler that will produce the snippet + */ + public static Snippet httpResponse() { + return new HttpResponseSnippet(); } - private static final class HttpResponseWritingResultHandler extends - SnippetWritingResultHandler { - - private HttpResponseWritingResultHandler(String identifier, - Map attributes) { - super(identifier, "http-response", attributes); - } - - @Override - public Map doHandle(MvcResult result) throws IOException { - HttpStatus status = HttpStatus.valueOf(result.getResponse().getStatus()); - Map model = new HashMap(); - model.put( - "responseBody", - StringUtils.hasLength(result.getResponse().getContentAsString()) ? String - .format("%n%s", result.getResponse().getContentAsString()) - : ""); - model.put("statusCode", status.value()); - model.put("statusReason", status.getReasonPhrase()); - model.put("headers", headers(result)); - return model; - } - - private List> headers(MvcResult result) { - List> headers = new ArrayList<>(); - for (String headerName : result.getResponse().getHeaderNames()) { - for (String header : result.getResponse().getHeaders(headerName)) { - headers.add(header(headerName, header)); - } - } - return headers; - } - - private Map header(String name, String value) { - Map header = new HashMap<>(); - header.put("name", name); - header.put("value", value); - return header; - } + /** + * Returns a handler that will produce a snippet containing the HTTP response for the + * API call. The given {@code attributes} will be available during snippet generation. + * + * @param attributes the attributes + * @return the handler that will produce the snippet + */ + public static Snippet httpResponse(Map attributes) { + return new HttpResponseSnippet(attributes); } } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/http/HttpRequestSnippet.java b/spring-restdocs/src/main/java/org/springframework/restdocs/http/HttpRequestSnippet.java new file mode 100644 index 00000000..7ff39756 --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/http/HttpRequestSnippet.java @@ -0,0 +1,175 @@ +/* + * 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.http; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.restdocs.snippet.DocumentableHttpServletRequest; +import org.springframework.restdocs.snippet.Snippet; +import org.springframework.restdocs.snippet.TemplatedSnippet; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.util.StringUtils; +import org.springframework.web.multipart.MultipartFile; + +/** + * A {@link Snippet} that documents an HTTP request. + * + * @author Andy Wilkinson + */ +class HttpRequestSnippet extends TemplatedSnippet { + + private static final String MULTIPART_BOUNDARY = "6o2knFse3p53ty9dmcQvWAIx1zInP11uCfbm"; + + HttpRequestSnippet() { + this(null); + } + + HttpRequestSnippet(Map attributes) { + super("http-request", attributes); + } + + @Override + public Map document(MvcResult result) throws IOException { + DocumentableHttpServletRequest request = new DocumentableHttpServletRequest( + result.getRequest()); + Map model = new HashMap(); + model.put("method", result.getRequest().getMethod()); + model.put("path", request.getRequestUriWithQueryString()); + model.put("headers", getHeaders(request)); + model.put("requestBody", getRequestBody(request)); + return model; + } + + private List> getHeaders(DocumentableHttpServletRequest request) { + List> headers = new ArrayList<>(); + if (requiresHostHeader(request)) { + headers.add(header(HttpHeaders.HOST, request.getHost())); + } + + for (Entry> header : request.getHeaders().entrySet()) { + for (String value : header.getValue()) { + if (header.getKey() == HttpHeaders.CONTENT_TYPE + && request.isMultipartRequest()) { + headers.add(header(header.getKey(), + String.format("%s; boundary=%s", value, MULTIPART_BOUNDARY))); + } + else { + headers.add(header(header.getKey(), value)); + } + + } + } + if (requiresFormEncodingContentType(request)) { + headers.add(header(HttpHeaders.CONTENT_TYPE, + MediaType.APPLICATION_FORM_URLENCODED_VALUE)); + } + return headers; + } + + private String getRequestBody(DocumentableHttpServletRequest request) + throws IOException { + StringWriter httpRequest = new StringWriter(); + PrintWriter writer = new PrintWriter(httpRequest); + if (request.getContentLength() > 0) { + writer.println(); + writer.print(request.getContentAsString()); + } + else if (request.isPostRequest() || request.isPutRequest()) { + if (request.isMultipartRequest()) { + writeParts(request, writer); + } + else { + String queryString = request.getParameterMapAsQueryString(); + if (StringUtils.hasText(queryString)) { + writer.println(); + writer.print(queryString); + } + } + } + return httpRequest.toString(); + } + + private void writeParts(DocumentableHttpServletRequest request, PrintWriter writer) + throws IOException { + writer.println(); + for (Entry parameter : request.getParameterMap().entrySet()) { + for (String value : parameter.getValue()) { + writePartBoundary(writer); + writePart(parameter.getKey(), value, null, writer); + writer.println(); + } + } + for (Entry> entry : request.getMultipartFiles() + .entrySet()) { + for (MultipartFile file : entry.getValue()) { + writePartBoundary(writer); + writePart(file, writer); + writer.println(); + } + } + writeMultipartEnd(writer); + } + + private void writePartBoundary(PrintWriter writer) { + writer.printf("--%s%n", MULTIPART_BOUNDARY); + } + + private void writePart(String name, String value, String contentType, + PrintWriter writer) { + writer.printf("Content-Disposition: form-data; name=%s%n", name); + if (StringUtils.hasText(contentType)) { + writer.printf("Content-Type: %s%n", contentType); + } + writer.println(); + writer.print(value); + } + + private void writePart(MultipartFile part, PrintWriter writer) throws IOException { + writePart(part.getName(), new String(part.getBytes()), part.getContentType(), + writer); + } + + private void writeMultipartEnd(PrintWriter writer) { + writer.printf("--%s--", MULTIPART_BOUNDARY); + } + + private boolean requiresHostHeader(DocumentableHttpServletRequest request) { + return request.getHeaders().get(HttpHeaders.HOST) == null; + } + + private boolean requiresFormEncodingContentType(DocumentableHttpServletRequest request) { + return request.getHeaders().getContentType() == null + && (request.isPostRequest() || request.isPutRequest()) + && StringUtils.hasText(request.getParameterMapAsQueryString()); + } + + private Map header(String name, String value) { + Map header = new HashMap<>(); + header.put("name", name); + header.put("value", value); + return header; + } +} \ No newline at end of file diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/http/HttpResponseSnippet.java b/spring-restdocs/src/main/java/org/springframework/restdocs/http/HttpResponseSnippet.java new file mode 100644 index 00000000..b3dfb3c2 --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/http/HttpResponseSnippet.java @@ -0,0 +1,76 @@ +/* + * 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.http; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.http.HttpStatus; +import org.springframework.restdocs.snippet.Snippet; +import org.springframework.restdocs.snippet.TemplatedSnippet; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.util.StringUtils; + +/** + * A {@link Snippet} that documents an HTTP response. + * + * @author Andy Wilkinson + */ +class HttpResponseSnippet extends TemplatedSnippet { + + HttpResponseSnippet() { + this(null); + } + + HttpResponseSnippet(Map attributes) { + super("http-response", attributes); + } + + @Override + public Map document(MvcResult result) throws IOException { + HttpStatus status = HttpStatus.valueOf(result.getResponse().getStatus()); + Map model = new HashMap(); + model.put( + "responseBody", + StringUtils.hasLength(result.getResponse().getContentAsString()) ? String + .format("%n%s", result.getResponse().getContentAsString()) : ""); + model.put("statusCode", status.value()); + model.put("statusReason", status.getReasonPhrase()); + model.put("headers", headers(result)); + return model; + } + + private List> headers(MvcResult result) { + List> headers = new ArrayList<>(); + for (String headerName : result.getResponse().getHeaderNames()) { + for (String header : result.getResponse().getHeaders(headerName)) { + headers.add(header(headerName, header)); + } + } + return headers; + } + + private Map header(String name, String value) { + Map header = new HashMap<>(); + header.put("name", name); + header.put("value", value); + return header; + } +} \ No newline at end of file diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/AbstractJsonLinkExtractor.java b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/AbstractJsonLinkExtractor.java new file mode 100644 index 00000000..45c73c23 --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/AbstractJsonLinkExtractor.java @@ -0,0 +1,46 @@ +/* + * 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.hypermedia; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import org.springframework.mock.web.MockHttpServletResponse; + +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Abstract base class for a {@link LinkExtractor} that extracts links from JSON + * + * @author Andy Wilkinson + */ +abstract class AbstractJsonLinkExtractor implements LinkExtractor { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Override + @SuppressWarnings("unchecked") + public Map> extractLinks(MockHttpServletResponse response) + throws IOException { + Map jsonContent = this.objectMapper.readValue( + response.getContentAsString(), Map.class); + return extractLinks(jsonContent); + } + + protected abstract Map> extractLinks(Map json); +} \ No newline at end of file diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/AtomLinkExtractor.java b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/AtomLinkExtractor.java new file mode 100644 index 00000000..e7bc955f --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/AtomLinkExtractor.java @@ -0,0 +1,65 @@ +/* + * 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.hypermedia; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +/** + * {@link LinkExtractor} that extracts links in Atom format. + * + * @author Andy Wilkinson + */ +@SuppressWarnings("unchecked") +class AtomLinkExtractor extends AbstractJsonLinkExtractor { + + @Override + public Map> extractLinks(Map json) { + MultiValueMap extractedLinks = new LinkedMultiValueMap<>(); + Object possibleLinks = json.get("links"); + if (possibleLinks instanceof Collection) { + Collection linksCollection = (Collection) possibleLinks; + for (Object linkObject : linksCollection) { + if (linkObject instanceof Map) { + Link link = maybeCreateLink((Map) linkObject); + maybeStoreLink(link, extractedLinks); + } + } + } + return extractedLinks; + } + + private static Link maybeCreateLink(Map 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, + MultiValueMap extractedLinks) { + if (link != null) { + extractedLinks.add(link.getRel(), link); + } + } +} \ No newline at end of file diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractor.java b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractor.java new file mode 100644 index 00000000..c114d138 --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractor.java @@ -0,0 +1,58 @@ +package org.springframework.restdocs.hypermedia; + +import java.io.IOException; +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 org.springframework.util.StringUtils; + +/** + * {@link LinkExtractor} that delegates to other link extractors based on the response's + * content type. + * + * @author Andy Wilkinson + * + */ +class ContentTypeLinkExtractor implements LinkExtractor { + + private Map linkExtractors = new HashMap<>(); + + ContentTypeLinkExtractor() { + this.linkExtractors.put(MediaType.APPLICATION_JSON, new AtomLinkExtractor()); + this.linkExtractors.put(HalLinkExtractor.HAL_MEDIA_TYPE, new HalLinkExtractor()); + } + + ContentTypeLinkExtractor(Map linkExtractors) { + this.linkExtractors.putAll(linkExtractors); + } + + @Override + public Map> extractLinks(MockHttpServletResponse response) + throws IOException { + String contentType = response.getContentType(); + LinkExtractor extractorForContentType = getExtractorForContentType(contentType); + if (extractorForContentType != null) { + return extractorForContentType.extractLinks(response); + } + throw new IllegalStateException( + "No LinkExtractor has been provided and one is not available for the " + + "content type " + contentType); + } + + private LinkExtractor getExtractorForContentType(String contentType) { + if (StringUtils.hasText(contentType)) { + MediaType mediaType = MediaType.parseMediaType(contentType); + for (Entry entry : this.linkExtractors.entrySet()) { + if (mediaType.isCompatibleWith(entry.getKey())) { + return entry.getValue(); + } + } + } + return null; + } + +} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/HalLinkExtractor.java b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/HalLinkExtractor.java new file mode 100644 index 00000000..7bc57fff --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/HalLinkExtractor.java @@ -0,0 +1,80 @@ +/* + * 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.hypermedia; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.springframework.http.MediaType; + +/** + * {@link LinkExtractor} that extracts links in Hypermedia Application Language (HAL) + * format. + * + * @author Andy Wilkinson + */ +class HalLinkExtractor extends AbstractJsonLinkExtractor { + + static final MediaType HAL_MEDIA_TYPE = new MediaType("application", "hal+json"); + + @Override + public Map> extractLinks(Map json) { + Map> extractedLinks = new LinkedHashMap<>(); + Object possibleLinks = json.get("_links"); + if (possibleLinks instanceof Map) { + @SuppressWarnings("unchecked") + Map links = (Map) possibleLinks; + for (Entry entry : links.entrySet()) { + String rel = entry.getKey(); + extractedLinks.put(rel, convertToLinks(entry.getValue(), rel)); + } + } + return extractedLinks; + } + + private static List convertToLinks(Object object, String rel) { + List links = new ArrayList<>(); + if (object instanceof Collection) { + @SuppressWarnings("unchecked") + Collection hrefObjects = (Collection) 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 links) { + if (possibleLink != null) { + links.add(possibleLink); + } + } +} \ No newline at end of file diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/HypermediaDocumentation.java b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/HypermediaDocumentation.java index 4ea7df09..52c0cccb 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/HypermediaDocumentation.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/HypermediaDocumentation.java @@ -19,10 +19,10 @@ package org.springframework.restdocs.hypermedia; import java.util.Arrays; import java.util.Map; -import org.springframework.restdocs.RestDocumentationResultHandler; +import org.springframework.restdocs.snippet.Snippet; /** - * Static factory methods for documenting a RESTful API that utilises Hypermedia. + * Static factory methods for documenting a RESTful API that utilizes Hypermedia. * * @author Andy Wilkinson */ @@ -37,29 +37,89 @@ public abstract class HypermediaDocumentation { * * @param rel The rel of the link * @return a {@code LinkDescriptor} ready for further configuration - * @see RestDocumentationResultHandler#withLinks(LinkDescriptor...) - * @see RestDocumentationResultHandler#withLinks(LinkExtractor, LinkDescriptor...) */ public static LinkDescriptor linkWithRel(String rel) { return new LinkDescriptor(rel); } /** - * Creates a {@code LinkSnippetResultHandler} that will produce a documentation - * snippet for a response's links. + * Returns a handler that will produce a snippet documenting the links in the API + * call's response. Links will be extracted from the response automatically based on + * its content type. + * + * @param descriptors The descriptions of the response's links + * @return the handler + */ + public static Snippet links(LinkDescriptor... descriptors) { + return new LinksSnippet(new ContentTypeLinkExtractor(), + Arrays.asList(descriptors)); + } + + /** + * Returns a handler that will produce a snippet documenting the links in the API + * call's response. The given {@code attributes} will be available during snippet + * generation. Links will be extracted from the response automatically based on its + * content type. + * + * @param attributes Attributes made available during rendering of the links snippet + * @param descriptors The descriptions of the response's links + * @return the handler + */ + public static Snippet links(Map attributes, + LinkDescriptor... descriptors) { + return new LinksSnippet(new ContentTypeLinkExtractor(), attributes, + Arrays.asList(descriptors)); + } + + /** + * Returns a handler that will produce a snippet documenting the links in the API + * call's response. Links will be extracted from the response using the given + * {@code linkExtractor}. + * + * @param linkExtractor Used to extract the links from the response + * @param descriptors The descriptions of the response's links + * @return the handler + */ + public static Snippet links(LinkExtractor linkExtractor, + LinkDescriptor... descriptors) { + return new LinksSnippet(linkExtractor, Arrays.asList(descriptors)); + } + + /** + * Returns a handler that will produce a snippet documenting the links in the API + * call's response. The given {@code attributes} will be available during snippet + * generation. Links will be extracted from the response using the given + * {@code linkExtractor}. * - * @param identifier An identifier for the API call that is being documented * @param attributes Attributes made available during rendering of the links snippet * @param linkExtractor Used to extract the links from the response * @param descriptors The descriptions of the response's links * @return the handler - * @see RestDocumentationResultHandler#withLinks(LinkDescriptor...) - * @see RestDocumentationResultHandler#withLinks(LinkExtractor, LinkDescriptor...) */ - public static LinkSnippetResultHandler documentLinks(String identifier, - Map attributes, LinkExtractor linkExtractor, - LinkDescriptor... descriptors) { - return new LinkSnippetResultHandler(identifier, attributes, linkExtractor, + public static Snippet links(LinkExtractor linkExtractor, + Map attributes, LinkDescriptor... descriptors) { + return new LinksSnippet(linkExtractor, attributes, Arrays.asList(descriptors)); } + + /** + * 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(); + } } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/LinkExtractors.java b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/LinkExtractors.java deleted file mode 100644 index 35c0f778..00000000 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/LinkExtractors.java +++ /dev/null @@ -1,187 +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.hypermedia; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.LinkedHashMap; -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 org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; - -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 abstract class LinkExtractors { - - private 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, may include parameters - * @return The extractor for the content type, or {@code null} - */ - public static LinkExtractor extractorForContentType(String contentType) { - if (StringUtils.hasText(contentType)) { - MediaType mediaType = MediaType.parseMediaType(contentType); - if (mediaType.isCompatibleWith(MediaType.APPLICATION_JSON)) { - return atomLinks(); - } - if (mediaType.isCompatibleWith(HalLinkExtractor.HAL_MEDIA_TYPE)) { - return halLinks(); - } - } - return null; - } - - private abstract static class JsonContentLinkExtractor implements LinkExtractor { - - private final ObjectMapper objectMapper = new ObjectMapper(); - - @Override - @SuppressWarnings("unchecked") - public Map> extractLinks(MockHttpServletResponse response) - throws IOException { - Map jsonContent = this.objectMapper.readValue( - response.getContentAsString(), Map.class); - return extractLinks(jsonContent); - } - - protected abstract Map> extractLinks(Map json); - } - - @SuppressWarnings("unchecked") - static class HalLinkExtractor extends JsonContentLinkExtractor { - - private static final MediaType HAL_MEDIA_TYPE = new MediaType("application", - "hal+json"); - - @Override - public Map> extractLinks(Map json) { - Map> extractedLinks = new LinkedHashMap<>(); - Object possibleLinks = json.get("_links"); - if (possibleLinks instanceof Map) { - Map links = (Map) possibleLinks; - for (Entry entry : links.entrySet()) { - String rel = entry.getKey(); - extractedLinks.put(rel, convertToLinks(entry.getValue(), rel)); - } - } - return extractedLinks; - } - - private static List convertToLinks(Object object, String rel) { - List links = new ArrayList<>(); - if (object instanceof Collection) { - Collection hrefObjects = (Collection) 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 links) { - if (possibleLink != null) { - links.add(possibleLink); - } - } - } - - @SuppressWarnings("unchecked") - static class AtomLinkExtractor extends JsonContentLinkExtractor { - - @Override - public Map> extractLinks(Map json) { - MultiValueMap extractedLinks = new LinkedMultiValueMap<>(); - Object possibleLinks = json.get("links"); - if (possibleLinks instanceof Collection) { - Collection linksCollection = (Collection) possibleLinks; - for (Object linkObject : linksCollection) { - if (linkObject instanceof Map) { - Link link = maybeCreateLink((Map) linkObject); - maybeStoreLink(link, extractedLinks); - } - } - } - return extractedLinks; - } - - private static Link maybeCreateLink(Map 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, - MultiValueMap extractedLinks) { - if (link != null) { - extractedLinks.add(link.getRel(), link); - } - } - } -} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/LinkSnippetResultHandler.java b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/LinksSnippet.java similarity index 65% rename from spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/LinkSnippetResultHandler.java rename to spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/LinksSnippet.java index 68155007..3557581b 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/LinkSnippetResultHandler.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/LinksSnippet.java @@ -26,29 +26,33 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import org.springframework.restdocs.snippet.SnippetGenerationException; -import org.springframework.restdocs.snippet.SnippetWritingResultHandler; +import org.springframework.restdocs.snippet.Snippet; +import org.springframework.restdocs.snippet.SnippetException; +import org.springframework.restdocs.snippet.TemplatedSnippet; import org.springframework.test.web.servlet.MvcResult; import org.springframework.util.Assert; /** - * A {@link SnippetWritingResultHandler} that produces a snippet documenting a RESTful - * resource's links. + * A {@link Snippet} that documents a RESTful resource's links. * * @author Andy Wilkinson */ -public class LinkSnippetResultHandler extends SnippetWritingResultHandler { +class LinksSnippet extends TemplatedSnippet { private final Map descriptorsByRel = new LinkedHashMap<>(); private final Set requiredRels = new HashSet<>(); - private final LinkExtractor extractor; + private final LinkExtractor linkExtractor; - LinkSnippetResultHandler(String identifier, Map attributes, - LinkExtractor linkExtractor, List descriptors) { - super(identifier, "links", attributes); - this.extractor = linkExtractor; + LinksSnippet(LinkExtractor linkExtractor, List descriptors) { + this(linkExtractor, null, descriptors); + } + + LinksSnippet(LinkExtractor linkExtractor, Map attributes, + List descriptors) { + super("links", attributes); + this.linkExtractor = linkExtractor; for (LinkDescriptor descriptor : descriptors) { Assert.hasText(descriptor.getRel()); Assert.hasText(descriptor.getDescription()); @@ -60,31 +64,13 @@ public class LinkSnippetResultHandler extends SnippetWritingResultHandler { } @Override - protected Map doHandle(MvcResult result) throws IOException { - validate(extractLinks(result)); + protected Map document(MvcResult result) throws IOException { + validate(this.linkExtractor.extractLinks(result.getResponse())); Map model = new HashMap<>(); model.put("links", createLinksModel()); return model; } - private Map> extractLinks(MvcResult result) throws IOException { - if (this.extractor != null) { - return this.extractor.extractLinks(result.getResponse()); - } - else { - String contentType = result.getResponse().getContentType(); - LinkExtractor extractorForContentType = LinkExtractors - .extractorForContentType(contentType); - if (extractorForContentType != null) { - return extractorForContentType.extractLinks(result.getResponse()); - } - throw new IllegalStateException( - "No LinkExtractor has been provided and one is not available for the content type " - + contentType); - - } - } - private void validate(Map> links) { Set actualRels = links.keySet(); @@ -104,10 +90,10 @@ public class LinkSnippetResultHandler extends SnippetWritingResultHandler { if (message.length() > 0) { message += ". "; } - message += "Links with the following relations were not found in the response: " - + missingRels; + message += "Links with the following relations were not found in the " + + "response: " + missingRels; } - throw new SnippetGenerationException(message); + throw new SnippetException(message); } } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/payload/FieldSnippetResultHandler.java b/spring-restdocs/src/main/java/org/springframework/restdocs/payload/AbstractFieldsSnippet.java similarity index 81% rename from spring-restdocs/src/main/java/org/springframework/restdocs/payload/FieldSnippetResultHandler.java rename to spring-restdocs/src/main/java/org/springframework/restdocs/payload/AbstractFieldsSnippet.java index 0eb0b1f3..d9dd6df1 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/payload/FieldSnippetResultHandler.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/payload/AbstractFieldsSnippet.java @@ -25,20 +25,21 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; -import org.springframework.restdocs.snippet.SnippetWritingResultHandler; +import org.springframework.restdocs.snippet.TemplatedSnippet; import org.springframework.test.web.servlet.MvcResult; import org.springframework.util.Assert; import com.fasterxml.jackson.databind.ObjectMapper; /** - * A {@link SnippetWritingResultHandler} that produces a snippet documenting a RESTful - * resource's request or response fields. + * A {@link TemplatedSnippet} that produces a snippet documenting a + * RESTful resource's request or response fields. * * @author Andreas Evers * @author Andy Wilkinson */ -public abstract class FieldSnippetResultHandler extends SnippetWritingResultHandler { +public abstract class AbstractFieldsSnippet extends + TemplatedSnippet { private final Map descriptorsByPath = new LinkedHashMap(); @@ -50,9 +51,9 @@ public abstract class FieldSnippetResultHandler extends SnippetWritingResultHand private List fieldDescriptors; - FieldSnippetResultHandler(String identifier, String type, - Map attributes, List descriptors) { - super(identifier, type + "-fields", attributes); + AbstractFieldsSnippet(String type, Map attributes, + List descriptors) { + super(type + "-fields", attributes); for (FieldDescriptor descriptor : descriptors) { Assert.notNull(descriptor.getPath()); Assert.hasText(descriptor.getDescription()); @@ -62,7 +63,7 @@ public abstract class FieldSnippetResultHandler extends SnippetWritingResultHand } @Override - protected Map doHandle(MvcResult result) throws IOException { + protected Map document(MvcResult result) throws IOException { this.fieldValidator.validate(getPayloadReader(result), this.fieldDescriptors); Object payload = extractPayload(result); Map model = new HashMap<>(); @@ -80,8 +81,8 @@ public abstract class FieldSnippetResultHandler extends SnippetWritingResultHand private FieldType getFieldType(FieldDescriptor descriptor, Object payload) { try { - return FieldSnippetResultHandler.this.fieldTypeResolver.resolveFieldType( - descriptor.getPath(), payload); + return AbstractFieldsSnippet.this.fieldTypeResolver + .resolveFieldType(descriptor.getPath(), payload); } catch (FieldDoesNotExistException ex) { String message = "Cannot determine the type of the field '" diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/payload/FieldValidator.java b/spring-restdocs/src/main/java/org/springframework/restdocs/payload/FieldValidator.java index 9055cc87..82e43717 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/payload/FieldValidator.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/payload/FieldValidator.java @@ -22,7 +22,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import org.springframework.restdocs.snippet.SnippetGenerationException; +import org.springframework.restdocs.snippet.SnippetException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; @@ -60,7 +60,7 @@ class FieldValidator { message += "Fields with the following paths were not found in the payload: " + missingFields; } - throw new SnippetGenerationException(message); + throw new SnippetException(message); } } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/payload/PayloadDocumentation.java b/spring-restdocs/src/main/java/org/springframework/restdocs/payload/PayloadDocumentation.java index 4e9d34c6..40e04704 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/payload/PayloadDocumentation.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/payload/PayloadDocumentation.java @@ -19,7 +19,7 @@ package org.springframework.restdocs.payload; import java.util.Arrays; import java.util.Map; -import org.springframework.restdocs.RestDocumentationResultHandler; +import org.springframework.restdocs.snippet.Snippet; /** * Static factory methods for documenting a RESTful API's request and response payloads. @@ -89,57 +89,92 @@ public abstract class PayloadDocumentation { * * @param path The path of the field * @return a {@code FieldDescriptor} ready for further configuration - * @see RestDocumentationResultHandler#withRequestFields(FieldDescriptor...) - * @see RestDocumentationResultHandler#withResponseFields(FieldDescriptor...) */ public static FieldDescriptor fieldWithPath(String path) { return new FieldDescriptor(path); } /** - * Creates a {@code RequestFieldsSnippetResultHandler} that will produce a - * documentation snippet for a request's fields. + * Returns a handler that will produce a snippet documenting the fields of the API + * call's request. *

- * If a field is present in the request but is not documented by one of the - * descriptors a failure will occur when the handler is invoked. Similarly, if a field - * is documented, is not marked as optional, and is not present in the request a - * failure will also occur. For payloads with a hierarchical structure, documenting a - * field is sufficient for all of its descendants to also be treated as having been + * If a field is present in the request, but is not documented by one of the + * descriptors, a failure will occur when the handler is invoked. Similarly, if a + * field is documented, is not marked as optional, and is not present in the request, + * a failure will also occur. For payloads with a hierarchical structure, documenting + * a field is sufficient for all of its descendants to also be treated as having been * documented. * - * @param identifier An identifier for the API call that is being documented - * @param attributes Attributes made available during rendering of the links snippet * @param descriptors The descriptions of the request's fields * @return the handler - * @see RestDocumentationResultHandler#withRequestFields(FieldDescriptor...) * @see #fieldWithPath(String) */ - public static FieldSnippetResultHandler documentRequestFields(String identifier, - Map attributes, FieldDescriptor... descriptors) { - return new RequestFieldSnippetResultHandler(identifier, attributes, + public static Snippet requestFields(FieldDescriptor... descriptors) { + return new RequestFieldsSnippet(Arrays.asList(descriptors)); + } + + /** + * Returns a handler that will produce a snippet documenting the fields of the API + * call's request. The given {@code attributes} will be available during snippet + * generation. + *

+ * If a field is present in the request, but is not documented by one of the + * descriptors, a failure will occur when the handler is invoked. Similarly, if a + * field is documented, is not marked as optional, and is not present in the request, + * a failure will also occur. For payloads with a hierarchical structure, documenting + * a field is sufficient for all of its descendants to also be treated as having been + * documented. + * + * @param attributes Attributes made available during rendering of the snippet + * @param descriptors The descriptions of the request's fields + * @return the handler + * @see #fieldWithPath(String) + */ + public static Snippet requestFields(Map attributes, + FieldDescriptor... descriptors) { + return new RequestFieldsSnippet(attributes, Arrays.asList(descriptors)); } /** - * Creates a {@code ResponseFieldsSnippetResultHandler} that will produce a - * documentation snippet for a response's fields. + * Returns a handler that will produce a snippet documenting the fields of the API + * call's response. *

- * If a field is present in the response but is not documented by one of the - * descriptors a failure will occur when the handler is invoked. Similarly, if a field - * is documented, is not marked as optional, and is not present in the response a - * failure will also occur. For payloads with a hierarchical structure, documenting a - * field is sufficient for all of its descendants to also be treated as having been + * If a field is present in the response, but is not documented by one of the + * descriptors, a failure will occur when the handler is invoked. Similarly, if a + * field is documented, is not marked as optional, and is not present in the response, + * a failure will also occur. For payloads with a hierarchical structure, documenting + * a field is sufficient for all of its descendants to also be treated as having been * documented. * - * @param identifier An identifier for the API call that is being documented - * @param attributes Attributes made available during rendering of the links snippet * @param descriptors The descriptions of the response's fields * @return the handler - * @see RestDocumentationResultHandler#withResponseFields(FieldDescriptor...) + * @see #fieldWithPath(String) */ - public static FieldSnippetResultHandler documentResponseFields(String identifier, - Map attributes, FieldDescriptor... descriptors) { - return new ResponseFieldSnippetResultHandler(identifier, attributes, + public static Snippet responseFields(FieldDescriptor... descriptors) { + return new ResponseFieldsSnippet(Arrays.asList(descriptors)); + } + + /** + * Returns a handler that will produce a snippet documenting the fields of the API + * call's response. The given {@code attributes} will be available during snippet + * generation. + *

+ * If a field is present in the response, but is not documented by one of the + * descriptors, a failure will occur when the handler is invoked. Similarly, if a + * field is documented, is not marked as optional, and is not present in the response, + * a failure will also occur. For payloads with a hierarchical structure, documenting + * a field is sufficient for all of its descendants to also be treated as having been + * documented. + * + * @param attributes Attributes made available during rendering of the snippet + * @param descriptors The descriptions of the response's fields + * @return the handler + * @see #fieldWithPath(String) + */ + public static Snippet responseFields(Map attributes, + FieldDescriptor... descriptors) { + return new ResponseFieldsSnippet(attributes, Arrays.asList(descriptors)); } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/payload/RequestFieldSnippetResultHandler.java b/spring-restdocs/src/main/java/org/springframework/restdocs/payload/RequestFieldsSnippet.java similarity index 71% rename from spring-restdocs/src/main/java/org/springframework/restdocs/payload/RequestFieldSnippetResultHandler.java rename to spring-restdocs/src/main/java/org/springframework/restdocs/payload/RequestFieldsSnippet.java index 851204d2..bff0832f 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/payload/RequestFieldSnippetResultHandler.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/payload/RequestFieldsSnippet.java @@ -20,18 +20,22 @@ import java.io.Reader; import java.util.List; import java.util.Map; +import org.springframework.restdocs.snippet.Snippet; import org.springframework.test.web.servlet.MvcResult; /** - * A {@link FieldSnippetResultHandler} for documenting a request's fields + * A {@link Snippet} that documents the fields in a request. * * @author Andy Wilkinson */ -public class RequestFieldSnippetResultHandler extends FieldSnippetResultHandler { +class RequestFieldsSnippet extends AbstractFieldsSnippet { - RequestFieldSnippetResultHandler(String identifier, Map attributes, - List descriptors) { - super(identifier, "request", attributes, descriptors); + RequestFieldsSnippet(List descriptors) { + this(null, descriptors); + } + + RequestFieldsSnippet(Map attributes, List descriptors) { + super("request", attributes, descriptors); } @Override diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/payload/ResponseFieldSnippetResultHandler.java b/spring-restdocs/src/main/java/org/springframework/restdocs/payload/ResponseFieldsSnippet.java similarity index 74% rename from spring-restdocs/src/main/java/org/springframework/restdocs/payload/ResponseFieldSnippetResultHandler.java rename to spring-restdocs/src/main/java/org/springframework/restdocs/payload/ResponseFieldsSnippet.java index e5af663c..2972d80d 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/payload/ResponseFieldSnippetResultHandler.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/payload/ResponseFieldsSnippet.java @@ -21,18 +21,23 @@ import java.io.StringReader; import java.util.List; import java.util.Map; +import org.springframework.restdocs.snippet.Snippet; import org.springframework.test.web.servlet.MvcResult; /** - * A {@link FieldSnippetResultHandler} for documenting a response's fields + * A {@link Snippet} the documents the fields in a response. * * @author Andy Wilkinson */ -public class ResponseFieldSnippetResultHandler extends FieldSnippetResultHandler { +class ResponseFieldsSnippet extends AbstractFieldsSnippet { - ResponseFieldSnippetResultHandler(String identifier, Map attributes, + ResponseFieldsSnippet(List descriptors) { + this(null, descriptors); + } + + ResponseFieldsSnippet(Map attributes, List descriptors) { - super(identifier, "response", attributes, descriptors); + super("response", attributes, descriptors); } @Override diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/request/AbstractParametersSnippetResultHandler.java b/spring-restdocs/src/main/java/org/springframework/restdocs/request/AbstractParametersSnippet.java similarity index 81% rename from spring-restdocs/src/main/java/org/springframework/restdocs/request/AbstractParametersSnippetResultHandler.java rename to spring-restdocs/src/main/java/org/springframework/restdocs/request/AbstractParametersSnippet.java index cc4e630b..35a3ba2a 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/request/AbstractParametersSnippetResultHandler.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/request/AbstractParametersSnippet.java @@ -10,19 +10,18 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import org.springframework.restdocs.snippet.SnippetWritingResultHandler; +import org.springframework.restdocs.snippet.TemplatedSnippet; import org.springframework.test.web.servlet.MvcResult; import org.springframework.util.Assert; -public abstract class AbstractParametersSnippetResultHandler extends - SnippetWritingResultHandler { +abstract class AbstractParametersSnippet extends + TemplatedSnippet { private final Map descriptorsByName = new LinkedHashMap<>(); - protected AbstractParametersSnippetResultHandler(String identifier, - String snippetName, Map attributes, - ParameterDescriptor... descriptors) { - super(identifier, snippetName, attributes); + protected AbstractParametersSnippet(String snippetName, + Map attributes, List descriptors) { + super(snippetName, attributes); for (ParameterDescriptor descriptor : descriptors) { Assert.hasText(descriptor.getName()); Assert.hasText(descriptor.getDescription()); @@ -31,7 +30,7 @@ public abstract class AbstractParametersSnippetResultHandler extends } @Override - protected Map doHandle(MvcResult result) throws IOException { + protected Map document(MvcResult result) throws IOException { verifyParameterDescriptors(result); Map model = new HashMap<>(); diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/request/PathParametersSnippetResultHandler.java b/spring-restdocs/src/main/java/org/springframework/restdocs/request/PathParametersSnippet.java similarity index 79% rename from spring-restdocs/src/main/java/org/springframework/restdocs/request/PathParametersSnippetResultHandler.java rename to spring-restdocs/src/main/java/org/springframework/restdocs/request/PathParametersSnippet.java index 70465a1a..91bf624d 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/request/PathParametersSnippetResultHandler.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/request/PathParametersSnippet.java @@ -17,30 +17,33 @@ package org.springframework.restdocs.request; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.springframework.restdocs.snippet.SnippetGenerationException; -import org.springframework.restdocs.snippet.SnippetWritingResultHandler; +import org.springframework.restdocs.snippet.Snippet; +import org.springframework.restdocs.snippet.SnippetException; import org.springframework.test.web.servlet.MvcResult; import org.springframework.util.Assert; /** - * A {@link SnippetWritingResultHandler} that produces a snippet documenting the path - * parameters supported by a RESTful resource. + * A {@link Snippet} that documents the path parameters supported by a RESTful resource. * * @author Andy Wilkinson */ -public class PathParametersSnippetResultHandler extends - AbstractParametersSnippetResultHandler { +class PathParametersSnippet extends AbstractParametersSnippet { private static final Pattern NAMES_PATTERN = Pattern.compile("\\{([^/]+?)\\}"); - protected PathParametersSnippetResultHandler(String identifier, - Map attributes, ParameterDescriptor... descriptors) { - super(identifier, "path-parameters", attributes, descriptors); + PathParametersSnippet(List descriptors) { + this(null, descriptors); + } + + PathParametersSnippet(Map attributes, + List descriptors) { + super("path-parameters", attributes, descriptors); } @Override @@ -84,7 +87,7 @@ public class PathParametersSnippetResultHandler extends message += "Path parameters with the following names were not found in " + "the request: " + missingParameters; } - throw new SnippetGenerationException(message); + throw new SnippetException(message); } } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/request/QueryParametersSnippetResultHandler.java b/spring-restdocs/src/main/java/org/springframework/restdocs/request/QueryParametersSnippet.java similarity index 69% rename from spring-restdocs/src/main/java/org/springframework/restdocs/request/QueryParametersSnippetResultHandler.java rename to spring-restdocs/src/main/java/org/springframework/restdocs/request/QueryParametersSnippet.java index 45a3f924..0bd11b88 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/request/QueryParametersSnippetResultHandler.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/request/QueryParametersSnippet.java @@ -16,25 +16,28 @@ package org.springframework.restdocs.request; +import java.util.List; import java.util.Map; import java.util.Set; -import org.springframework.restdocs.snippet.SnippetGenerationException; -import org.springframework.restdocs.snippet.SnippetWritingResultHandler; +import org.springframework.restdocs.snippet.Snippet; +import org.springframework.restdocs.snippet.SnippetException; import org.springframework.test.web.servlet.MvcResult; /** - * A {@link SnippetWritingResultHandler} that produces a snippet documenting the query - * parameters supported by a RESTful resource. + * A {@link Snippet} that documents the query parameters supported by a RESTful resource. * * @author Andy Wilkinson */ -public class QueryParametersSnippetResultHandler extends - AbstractParametersSnippetResultHandler { +class QueryParametersSnippet extends AbstractParametersSnippet { - protected QueryParametersSnippetResultHandler(String identifier, - Map attributes, ParameterDescriptor... descriptors) { - super(identifier, "query-parameters", attributes, descriptors); + QueryParametersSnippet(List descriptors) { + this(null, descriptors); + } + + QueryParametersSnippet(Map attributes, + List descriptors) { + super("query-parameters", attributes, descriptors); } @Override @@ -52,7 +55,7 @@ public class QueryParametersSnippetResultHandler extends message += "Query parameters with the following names were not found in the request: " + missingParameters; } - throw new SnippetGenerationException(message); + throw new SnippetException(message); } @Override diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/request/RequestDocumentation.java b/spring-restdocs/src/main/java/org/springframework/restdocs/request/RequestDocumentation.java index 3b45c3ec..fea071e3 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/request/RequestDocumentation.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/request/RequestDocumentation.java @@ -16,10 +16,10 @@ package org.springframework.restdocs.request; +import java.util.Arrays; import java.util.Map; -import org.springframework.restdocs.RestDocumentationResultHandler; -import org.springframework.restdocs.snippet.SnippetWritingResultHandler; +import org.springframework.restdocs.snippet.Snippet; /** * Static factory methods for documenting aspects of a request sent to a RESTful API. @@ -32,49 +32,69 @@ public abstract class RequestDocumentation { } - /** - * Creates a {@link SnippetWritingResultHandler} that will produce a snippet - * documenting a request's path parameters. - * - * @param identifier An identifier for the API call that is being documented - * @param attributes Attributes made available during rendering of the path parameters - * snippet - * @param descriptors The descriptions of the parameters in the request's path - * @return the result handler - * @see RestDocumentationResultHandler#withPathParameters(ParameterDescriptor...) - */ - public static SnippetWritingResultHandler documentPathParameters(String identifier, - Map attributes, ParameterDescriptor... descriptors) { - return new PathParametersSnippetResultHandler(identifier, attributes, descriptors); - } - - /** - * Creates a {@link SnippetWritingResultHandler} that will produce a snippet - * documenting a request's query parameters - * - * @param identifier An identifier for the API call that is being documented - * @param attributes Attributes made available during rendering of the query - * parameters snippet - * @param descriptors The descriptions of the parameters in the request's query string - * @return the result handler - * @see RestDocumentationResultHandler#withQueryParameters(ParameterDescriptor...) - */ - public static SnippetWritingResultHandler documentQueryParameters(String identifier, - Map attributes, ParameterDescriptor... descriptors) { - return new QueryParametersSnippetResultHandler(identifier, attributes, - descriptors); - } - /** * Creates a {@link ParameterDescriptor} that describes a query string parameter with * the given {@code name}. * * @param name The name of the parameter * @return a {@link ParameterDescriptor} ready for further configuration - * @see RestDocumentationResultHandler#withQueryParameters(ParameterDescriptor...) */ public static ParameterDescriptor parameterWithName(String name) { return new ParameterDescriptor(name); } + /** + * Returns a handler that will produce a snippet documenting the path parameters from + * the API call's request. + * + * @param descriptors The descriptions of the parameters in the request's path + * @return the handler + */ + public static Snippet pathParameters(ParameterDescriptor... descriptors) { + return new PathParametersSnippet(Arrays.asList(descriptors)); + } + + /** + * Returns a handler that will produce a snippet documenting the path parameters from + * the API call's request. The given {@code attributes} will be available during + * snippet generation. + * + * @param attributes Attributes made available during rendering of the path parameters + * snippet + * @param descriptors The descriptions of the parameters in the request's path + * @return the handler + */ + public static Snippet pathParameters(Map attributes, + ParameterDescriptor... descriptors) { + return new PathParametersSnippet(attributes, + Arrays.asList(descriptors)); + } + + /** + * Returns a handler that will produce a snippet documenting the query parameters from + * the API call's request. + * + * @param descriptors The descriptions of the request's query parameters + * @return the handler + */ + public static Snippet queryParameters(ParameterDescriptor... descriptors) { + return new QueryParametersSnippet(Arrays.asList(descriptors)); + } + + /** + * Returns a handler that will produce a snippet documenting the query parameters from + * the API call's request. The given {@code attributes} will be available during + * snippet generation. + * + * @param attributes Attributes made available during rendering of the query + * parameters snippet + * @param descriptors The descriptions of the request's query parameters + * @return the handler + */ + public static Snippet queryParameters(Map attributes, + ParameterDescriptor... descriptors) { + return new QueryParametersSnippet(attributes, + Arrays.asList(descriptors)); + } + } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/response/ContentModifyingReponsePostProcessor.java b/spring-restdocs/src/main/java/org/springframework/restdocs/response/ContentModifyingReponsePostProcessor.java index 977eaf5c..44d78999 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/response/ContentModifyingReponsePostProcessor.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/response/ContentModifyingReponsePostProcessor.java @@ -67,7 +67,7 @@ public abstract class ContentModifyingReponsePostProcessor implements private final MockHttpServletResponse delegate; - public ContentModifyingMethodInterceptor(String modifiedContent, + private ContentModifyingMethodInterceptor(String modifiedContent, MockHttpServletResponse delegate) { this.modifiedContent = modifiedContent; this.delegate = delegate; diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/response/HeaderRemovingResponsePostProcessor.java b/spring-restdocs/src/main/java/org/springframework/restdocs/response/HeaderRemovingResponsePostProcessor.java index c7cc69a1..53da363e 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/response/HeaderRemovingResponsePostProcessor.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/response/HeaderRemovingResponsePostProcessor.java @@ -42,7 +42,7 @@ class HeaderRemovingResponsePostProcessor implements ResponsePostProcessor { private final Set headersToRemove; - public HeaderRemovingResponsePostProcessor(String... headersToRemove) { + HeaderRemovingResponsePostProcessor(String... headersToRemove) { this.headersToRemove = new HashSet<>(Arrays.asList(headersToRemove)); } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/Attributes.java b/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/Attributes.java index 9f552e1e..db8462f3 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/Attributes.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/Attributes.java @@ -75,12 +75,11 @@ public abstract class Attributes { public Attribute value(Object value) { return new Attribute(this.key, value); } + } /** * An attribute (key-value pair). - * - * @author Andy Wilkinson */ public static class Attribute { @@ -88,18 +87,32 @@ public abstract class Attributes { private final Object value; + /** + * Creates a new attribute with the given {@code key} and {@code value}. + * @param key the key + * @param value the value + */ public Attribute(String key, Object value) { this.key = key; this.value = value; } + /** + * Returns the attribute's key + * @return the key + */ public String getKey() { return this.key; } + /** + * Returns the attribute's value + * @return the value + */ public Object getValue() { return this.value; } } + } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/Snippet.java b/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/Snippet.java new file mode 100644 index 00000000..323af9e2 --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/Snippet.java @@ -0,0 +1,40 @@ +/* + * 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.snippet; + +import java.io.IOException; + +import org.springframework.test.web.servlet.MvcResult; + +/** + * A {@link Snippet} is used to document aspects of a call to a RESTful API. + * + * @author Andy Wilkinson + */ +public interface Snippet { + + /** + * Documents the call to the RESTful API described by the given {@code result}. The + * call is identified by the given {@code operation}. + * + * @param operation the API operation + * @param result the result of the operation + * @throws IOException if a failure occurs will documenting the result + */ + void document(String operation, MvcResult result) throws IOException; + +} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/SnippetGenerationException.java b/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/SnippetException.java similarity index 81% rename from spring-restdocs/src/main/java/org/springframework/restdocs/snippet/SnippetGenerationException.java rename to spring-restdocs/src/main/java/org/springframework/restdocs/snippet/SnippetException.java index 027d8282..f846b8f3 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/SnippetGenerationException.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/SnippetException.java @@ -23,14 +23,13 @@ package org.springframework.restdocs.snippet; * @author Andy Wilkinson */ @SuppressWarnings("serial") -public class SnippetGenerationException extends RuntimeException { +public class SnippetException extends RuntimeException { /** - * Creates a new {@code SnippetGenerationException} described by the given - * {@code message} + * Creates a new {@code SnippetException} described by the given {@code message} * @param message the message that describes the problem */ - public SnippetGenerationException(String message) { + public SnippetException(String message) { super(message); } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/SnippetWritingResultHandler.java b/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/TemplatedSnippet.java similarity index 70% rename from spring-restdocs/src/main/java/org/springframework/restdocs/snippet/SnippetWritingResultHandler.java rename to spring-restdocs/src/main/java/org/springframework/restdocs/snippet/TemplatedSnippet.java index 12b364eb..de9f966a 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/SnippetWritingResultHandler.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/TemplatedSnippet.java @@ -21,26 +21,23 @@ import java.io.Writer; import java.util.HashMap; import java.util.Map; +import org.springframework.restdocs.templates.Template; import org.springframework.restdocs.templates.TemplateEngine; import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.ResultHandler; /** - * Base class for a {@link ResultHandler} that writes a documentation snippet + * Base class for a {@link Snippet} that is produced using a {@link Template} and + * {@link TemplateEngine}. * * @author Andy Wilkinson */ -public abstract class SnippetWritingResultHandler implements ResultHandler { +public abstract class TemplatedSnippet implements Snippet { private final Map attributes = new HashMap<>(); - private final String identifier; - private final String snippetName; - protected SnippetWritingResultHandler(String identifier, String snippetName, - Map attributes) { - this.identifier = identifier; + protected TemplatedSnippet(String snippetName, Map attributes) { this.snippetName = snippetName; if (attributes != null) { this.attributes.putAll(attributes); @@ -48,11 +45,11 @@ public abstract class SnippetWritingResultHandler implements ResultHandler { } @Override - public void handle(MvcResult result) throws IOException { + public void document(String operation, MvcResult result) throws IOException { WriterResolver writerResolver = (WriterResolver) result.getRequest() .getAttribute(WriterResolver.class.getName()); - try (Writer writer = writerResolver.resolve(this.identifier, this.snippetName)) { - Map model = doHandle(result); + try (Writer writer = writerResolver.resolve(operation, this.snippetName)) { + Map model = document(result); model.putAll(this.attributes); TemplateEngine templateEngine = (TemplateEngine) result.getRequest() .getAttribute(TemplateEngine.class.getName()); @@ -60,6 +57,6 @@ public abstract class SnippetWritingResultHandler implements ResultHandler { } } - protected abstract Map doHandle(MvcResult result) throws IOException; + protected abstract Map document(MvcResult result) throws IOException; } \ No newline at end of file diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/RestDocumentationIntegrationTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/RestDocumentationIntegrationTests.java index bc84de53..f3642d76 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/RestDocumentationIntegrationTests.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/RestDocumentationIntegrationTests.java @@ -23,9 +23,15 @@ import static org.junit.Assert.assertTrue; import static org.springframework.restdocs.RestDocumentation.document; import static org.springframework.restdocs.RestDocumentation.modifyResponseTo; import static org.springframework.restdocs.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.curl.CurlDocumentation.curlRequest; import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; +import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; import static org.springframework.restdocs.response.ResponsePostProcessors.maskLinks; import static org.springframework.restdocs.response.ResponsePostProcessors.prettyPrintContent; import static org.springframework.restdocs.response.ResponsePostProcessors.removeHeaders; @@ -112,14 +118,14 @@ public class RestDocumentationIntegrationTests { } @Test - public void links() throws Exception { + public void linksSnippet() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(new RestDocumentationConfigurer()).build(); mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) - .andDo(document("links").withLinks( - linkWithRel("rel").description("The description"))); + .andDo(document("links", + links(linkWithRel("rel").description("The description")))); assertExpectedSnippetFilesExist(new File("build/generated-snippets/links"), "http-request.adoc", "http-response.adoc", "curl-request.adoc", @@ -127,14 +133,14 @@ public class RestDocumentationIntegrationTests { } @Test - public void pathParameters() throws Exception { + public void pathParametersSnippet() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(new RestDocumentationConfigurer()).build(); mockMvc.perform(get("{foo}", "/").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) - .andDo(document("links").withPathParameters( - parameterWithName("foo").description("The description"))); + .andDo(document("links", pathParameters(parameterWithName("foo") + .description("The description")))); assertExpectedSnippetFilesExist(new File("build/generated-snippets/links"), "http-request.adoc", "http-response.adoc", "curl-request.adoc", @@ -142,14 +148,14 @@ public class RestDocumentationIntegrationTests { } @Test - public void queryParameters() throws Exception { + public void queryParametersSnippet() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(new RestDocumentationConfigurer()).build(); mockMvc.perform(get("/").param("foo", "bar").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) - .andDo(document("links").withQueryParameters( - parameterWithName("foo").description("The description"))); + .andDo(document("links", queryParameters(parameterWithName("foo") + .description("The description")))); assertExpectedSnippetFilesExist(new File("build/generated-snippets/links"), "http-request.adoc", "http-response.adoc", "curl-request.adoc", @@ -157,7 +163,7 @@ public class RestDocumentationIntegrationTests { } @Test - public void requestFields() throws Exception { + public void requestFieldsSnippet() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(new RestDocumentationConfigurer()).build(); @@ -165,8 +171,8 @@ public class RestDocumentationIntegrationTests { get("/").param("foo", "bar").content("{\"a\":\"alpha\"}") .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) - .andDo(document("links").withRequestFields( - fieldWithPath("a").description("The description"))); + .andDo(document("links", + requestFields(fieldWithPath("a").description("The description")))); assertExpectedSnippetFilesExist(new File("build/generated-snippets/links"), "http-request.adoc", "http-response.adoc", "curl-request.adoc", @@ -174,15 +180,18 @@ public class RestDocumentationIntegrationTests { } @Test - public void responseFields() throws Exception { + public void responseFieldsSnippet() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(new RestDocumentationConfigurer()).build(); mockMvc.perform(get("/").param("foo", "bar").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) - .andDo(document("links").withResponseFields( - fieldWithPath("a").description("The description"), - fieldWithPath("links").description("Links to other resources"))); + .andDo(document( + "links", + responseFields( + fieldWithPath("a").description("The description"), + fieldWithPath("links").description( + "Links to other resources")))); assertExpectedSnippetFilesExist(new File("build/generated-snippets/links"), "http-request.adoc", "http-response.adoc", "curl-request.adoc", @@ -284,8 +293,10 @@ public class RestDocumentationIntegrationTests { is(snippet().withContents(equalTo("Custom curl request")))); mockMvc.perform(get("/")).andDo( - document("index").withCurlRequest( - attributes(key("title").value("Access the index using curl")))); + document( + "index", + curlRequest(attributes(key("title").value( + "Access the index using curl"))))); } private void assertExpectedSnippetFilesExist(File directory, String... snippets) { diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/config/RestDocumentationConfigurerTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/config/RestDocumentationConfigurerTests.java index 1f684b17..b8344b2f 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/config/RestDocumentationConfigurerTests.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/config/RestDocumentationConfigurerTests.java @@ -43,7 +43,7 @@ public class RestDocumentationConfigurerTests { private MockHttpServletRequest request = new MockHttpServletRequest(); - private RestDocumentationContext context = new RestDocumentationContext(); + private RestDocumentationContext context = new RestDocumentationContext(null); @Before public void establishContext() { diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/config/RestDocumentationContexts.java b/spring-restdocs/src/test/java/org/springframework/restdocs/config/RestDocumentationContexts.java new file mode 100644 index 00000000..629d6908 --- /dev/null +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/config/RestDocumentationContexts.java @@ -0,0 +1,6 @@ +package org.springframework.restdocs.config; + + +public class RestDocumentationContexts { + +} diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/curl/CurlDocumentationTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/curl/CurlRequestDocumentationHandlerTests.java similarity index 84% rename from spring-restdocs/src/test/java/org/springframework/restdocs/curl/CurlDocumentationTests.java rename to spring-restdocs/src/test/java/org/springframework/restdocs/curl/CurlRequestDocumentationHandlerTests.java index c0b8f808..300cf6fa 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/curl/CurlDocumentationTests.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/curl/CurlRequestDocumentationHandlerTests.java @@ -23,7 +23,6 @@ import static org.springframework.restdocs.RestDocumentationRequestBuilders.file import static org.springframework.restdocs.RestDocumentationRequestBuilders.get; import static org.springframework.restdocs.RestDocumentationRequestBuilders.post; import static org.springframework.restdocs.RestDocumentationRequestBuilders.put; -import static org.springframework.restdocs.curl.CurlDocumentation.documentCurlRequest; import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; import static org.springframework.restdocs.test.SnippetMatchers.codeBlock; @@ -44,14 +43,14 @@ import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; import org.springframework.restdocs.test.ExpectedSnippet; /** - * Tests for {@link CurlDocumentation} + * Tests for {@link CurlRequestSnippet} * * @author Andy Wilkinson * @author Yann Le Guern * @author Dmitriy Mayboroda * @author Jonathan Pearlin */ -public class CurlDocumentationTests { +public class CurlRequestDocumentationHandlerTests { @Rule public ExpectedSnippet snippet = new ExpectedSnippet(); @@ -63,14 +62,16 @@ public class CurlDocumentationTests { public void getRequest() throws IOException { this.snippet.expectCurlRequest("get-request").withContents( codeBlock("bash").content("$ curl 'http://localhost/foo' -i")); - documentCurlRequest("get-request", null).handle(result(get("/foo"))); + new CurlRequestSnippet() + .document("get-request", result(get("/foo"))); } @Test public void nonGetRequest() throws IOException { this.snippet.expectCurlRequest("non-get-request").withContents( codeBlock("bash").content("$ curl 'http://localhost/foo' -i -X POST")); - documentCurlRequest("non-get-request", null).handle(result(post("/foo"))); + new CurlRequestSnippet().document("non-get-request", + result(post("/foo"))); } @Test @@ -78,7 +79,7 @@ public class CurlDocumentationTests { this.snippet.expectCurlRequest("request-with-content").withContents( codeBlock("bash") .content("$ curl 'http://localhost/foo' -i -d 'content'")); - documentCurlRequest("request-with-content", null).handle( + new CurlRequestSnippet().document("request-with-content", result(get("/foo").content("content"))); } @@ -88,7 +89,7 @@ public class CurlDocumentationTests { .withContents( codeBlock("bash").content( "$ curl 'http://localhost/foo?param=value' -i")); - documentCurlRequest("request-with-query-string", null).handle( + new CurlRequestSnippet().document("request-with-query-string", result(get("/foo?param=value"))); } @@ -96,7 +97,7 @@ public class CurlDocumentationTests { public void requestWithOneParameter() throws IOException { this.snippet.expectCurlRequest("request-with-one-parameter").withContents( codeBlock("bash").content("$ curl 'http://localhost/foo?k1=v1' -i")); - documentCurlRequest("request-with-one-parameter", null).handle( + new CurlRequestSnippet().document("request-with-one-parameter", result(get("/foo").param("k1", "v1"))); } @@ -105,9 +106,9 @@ public class CurlDocumentationTests { this.snippet.expectCurlRequest("request-with-multiple-parameters").withContents( codeBlock("bash").content( "$ curl 'http://localhost/foo?k1=v1&k1=v1-bis&k2=v2' -i")); - documentCurlRequest("request-with-multiple-parameters", null).handle( - result(get("/foo").param("k1", "v1").param("k2", "v2") - .param("k1", "v1-bis"))); + new CurlRequestSnippet().document( + "request-with-multiple-parameters", result(get("/foo").param("k1", "v1") + .param("k2", "v2").param("k1", "v1-bis"))); } @Test @@ -116,7 +117,8 @@ public class CurlDocumentationTests { .withContents( codeBlock("bash").content( "$ curl 'http://localhost/foo?k1=foo+bar%26' -i")); - documentCurlRequest("request-with-url-encoded-parameter", null).handle( + new CurlRequestSnippet().document( + "request-with-url-encoded-parameter", result(get("/foo").param("k1", "foo bar&"))); } @@ -125,7 +127,7 @@ public class CurlDocumentationTests { this.snippet.expectCurlRequest("post-request-with-one-parameter").withContents( codeBlock("bash").content( "$ curl 'http://localhost/foo' -i -X POST -d 'k1=v1'")); - documentCurlRequest("post-request-with-one-parameter", null).handle( + new CurlRequestSnippet().document("post-request-with-one-parameter", result(post("/foo").param("k1", "v1"))); } @@ -136,7 +138,8 @@ public class CurlDocumentationTests { codeBlock("bash").content( "$ curl 'http://localhost/foo' -i -X POST" + " -d 'k1=v1&k1=v1-bis&k2=v2'")); - documentCurlRequest("post-request-with-multiple-parameters", null).handle( + new CurlRequestSnippet().document( + "post-request-with-multiple-parameters", result(post("/foo").param("k1", "v1", "v1-bis").param("k2", "v2"))); } @@ -147,7 +150,8 @@ public class CurlDocumentationTests { .withContents( codeBlock("bash").content( "$ curl 'http://localhost/foo' -i -X POST -d 'k1=a%26b'")); - documentCurlRequest("post-request-with-url-encoded-parameter", null).handle( + new CurlRequestSnippet().document( + "post-request-with-url-encoded-parameter", result(post("/foo").param("k1", "a&b"))); } @@ -156,7 +160,7 @@ public class CurlDocumentationTests { this.snippet.expectCurlRequest("put-request-with-one-parameter").withContents( codeBlock("bash").content( "$ curl 'http://localhost/foo' -i -X PUT -d 'k1=v1'")); - documentCurlRequest("put-request-with-one-parameter", null).handle( + new CurlRequestSnippet().document("put-request-with-one-parameter", result(put("/foo").param("k1", "v1"))); } @@ -167,7 +171,8 @@ public class CurlDocumentationTests { codeBlock("bash").content( "$ curl 'http://localhost/foo' -i -X PUT" + " -d 'k1=v1&k1=v1-bis&k2=v2'")); - documentCurlRequest("put-request-with-multiple-parameters", null).handle( + new CurlRequestSnippet().document( + "put-request-with-multiple-parameters", result(put("/foo").param("k1", "v1", "v1-bis").param("k2", "v2"))); } @@ -177,7 +182,8 @@ public class CurlDocumentationTests { .withContents( codeBlock("bash").content( "$ curl 'http://localhost/foo' -i -X PUT -d 'k1=a%26b'")); - documentCurlRequest("put-request-with-url-encoded-parameter", null).handle( + new CurlRequestSnippet().document( + "put-request-with-url-encoded-parameter", result(put("/foo").param("k1", "a&b"))); } @@ -187,7 +193,8 @@ public class CurlDocumentationTests { codeBlock("bash").content( "$ curl 'http://localhost/foo' -i" + " -H 'Content-Type: application/json' -H 'a: alpha'")); - documentCurlRequest("request-with-headers", null).handle( + new CurlRequestSnippet().document( + "request-with-headers", result(get("/foo").contentType(MediaType.APPLICATION_JSON).header("a", "alpha"))); } @@ -198,7 +205,8 @@ public class CurlDocumentationTests { codeBlock("bash").content("$ curl 'http://localhost:8080/foo' -i")); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); request.setServerPort(8080); - documentCurlRequest("http-with-non-standard-port", null).handle(result(request)); + new CurlRequestSnippet().document("http-with-non-standard-port", + result(request)); } @Test @@ -208,7 +216,8 @@ public class CurlDocumentationTests { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); request.setServerPort(443); request.setScheme("https"); - documentCurlRequest("https-with-standard-port", null).handle(result(request)); + new CurlRequestSnippet().document("https-with-standard-port", + result(request)); } @Test @@ -218,7 +227,8 @@ public class CurlDocumentationTests { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); request.setServerPort(8443); request.setScheme("https"); - documentCurlRequest("https-with-non-standard-port", null).handle(result(request)); + new CurlRequestSnippet().document("https-with-non-standard-port", + result(request)); } @Test @@ -227,7 +237,8 @@ public class CurlDocumentationTests { codeBlock("bash").content("$ curl 'http://api.example.com/foo' -i")); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); request.setServerName("api.example.com"); - documentCurlRequest("request-with-custom-host", null).handle(result(request)); + new CurlRequestSnippet().document("request-with-custom-host", + result(request)); } @Test @@ -239,8 +250,8 @@ public class CurlDocumentationTests { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); request.setServerName("api.example.com"); request.setContextPath("/v3"); - documentCurlRequest("request-with-custom-context-with-slash", null).handle( - result(request)); + new CurlRequestSnippet().document( + "request-with-custom-context-with-slash", result(request)); } @Test @@ -252,8 +263,8 @@ public class CurlDocumentationTests { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); request.setServerName("api.example.com"); request.setContextPath("v3"); - documentCurlRequest("request-with-custom-context-without-slash", null).handle( - result(request)); + new CurlRequestSnippet().document( + "request-with-custom-context-without-slash", result(request)); } @Test @@ -265,7 +276,8 @@ public class CurlDocumentationTests { .withContents(codeBlock("bash").content(expectedContent)); MockMultipartFile multipartFile = new MockMultipartFile("metadata", "{\"description\": \"foo\"}".getBytes()); - documentCurlRequest("multipart-post-no-original-filename", null).handle( + new CurlRequestSnippet().document( + "multipart-post-no-original-filename", result(fileUpload("/upload").file(multipartFile))); } @@ -279,7 +291,8 @@ public class CurlDocumentationTests { MockMultipartFile multipartFile = new MockMultipartFile("image", "documents/images/example.png", MediaType.IMAGE_PNG_VALUE, "bytes".getBytes()); - documentCurlRequest("multipart-post-with-content-type", null).handle( + new CurlRequestSnippet().document( + "multipart-post-with-content-type", result(fileUpload("/upload").file(multipartFile))); } @@ -292,7 +305,7 @@ public class CurlDocumentationTests { codeBlock("bash").content(expectedContent)); MockMultipartFile multipartFile = new MockMultipartFile("image", "documents/images/example.png", null, "bytes".getBytes()); - documentCurlRequest("multipart-post", null).handle( + new CurlRequestSnippet().document("multipart-post", result(fileUpload("/upload").file(multipartFile))); } @@ -306,7 +319,8 @@ public class CurlDocumentationTests { codeBlock("bash").content(expectedContent)); MockMultipartFile multipartFile = new MockMultipartFile("image", "documents/images/example.png", null, "bytes".getBytes()); - documentCurlRequest("multipart-post", null).handle( + new CurlRequestSnippet().document( + "multipart-post", result(fileUpload("/upload").file(multipartFile) .param("a", "apple", "avocado").param("b", "banana"))); } @@ -323,9 +337,8 @@ public class CurlDocumentationTests { "src/test/resources/custom-snippet-templates/curl-request-with-title.snippet")); request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( resolver)); - documentCurlRequest("custom-attributes", - attributes(key("title").value("curl request title"))).handle( - result(request)); + new CurlRequestSnippet(attributes(key("title").value( + "curl request title"))).document("custom-attributes", result(request)); } } diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/http/HttpDocumentationTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/http/HttpRequestDocumentationHandlerTests.java similarity index 69% rename from spring-restdocs/src/test/java/org/springframework/restdocs/http/HttpDocumentationTests.java rename to spring-restdocs/src/test/java/org/springframework/restdocs/http/HttpRequestDocumentationHandlerTests.java index 59b1f547..beb79997 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/http/HttpDocumentationTests.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/http/HttpRequestDocumentationHandlerTests.java @@ -19,18 +19,13 @@ package org.springframework.restdocs.http; import static org.hamcrest.CoreMatchers.containsString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static org.springframework.http.HttpStatus.BAD_REQUEST; -import static org.springframework.http.HttpStatus.OK; import static org.springframework.restdocs.RestDocumentationRequestBuilders.fileUpload; import static org.springframework.restdocs.RestDocumentationRequestBuilders.get; import static org.springframework.restdocs.RestDocumentationRequestBuilders.post; import static org.springframework.restdocs.RestDocumentationRequestBuilders.put; -import static org.springframework.restdocs.http.HttpDocumentation.documentHttpRequest; -import static org.springframework.restdocs.http.HttpDocumentation.documentHttpResponse; import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; import static org.springframework.restdocs.test.SnippetMatchers.httpRequest; -import static org.springframework.restdocs.test.SnippetMatchers.httpResponse; import static org.springframework.restdocs.test.StubMvcResult.result; import static org.springframework.web.bind.annotation.RequestMethod.GET; import static org.springframework.web.bind.annotation.RequestMethod.POST; @@ -44,7 +39,6 @@ import org.springframework.core.io.FileSystemResource; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockMultipartFile; import org.springframework.restdocs.templates.TemplateEngine; import org.springframework.restdocs.templates.TemplateResourceResolver; @@ -52,12 +46,13 @@ import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; import org.springframework.restdocs.test.ExpectedSnippet; /** - * Tests for {@link HttpDocumentation} - * + * Tests for {@link HttpRequestSnippet} + * * @author Andy Wilkinson * @author Jonathan Pearlin + * */ -public class HttpDocumentationTests { +public class HttpRequestDocumentationHandlerTests { private static final String BOUNDARY = "6o2knFse3p53ty9dmcQvWAIx1zInP11uCfbm"; @@ -70,8 +65,8 @@ public class HttpDocumentationTests { httpRequest(GET, "/foo").header(HttpHeaders.HOST, "localhost").header( "Alpha", "a")); - documentHttpRequest("get-request", null).handle( - result(get("/foo").header("Alpha", "a"))); + new HttpRequestSnippet().document("get-request", result(get("/foo") + .header("Alpha", "a"))); } @Test @@ -79,7 +74,7 @@ public class HttpDocumentationTests { this.snippet.expectHttpRequest("get-request-with-query-string").withContents( httpRequest(GET, "/foo?bar=baz").header(HttpHeaders.HOST, "localhost")); - documentHttpRequest("get-request-with-query-string", null).handle( + new HttpRequestSnippet().document("get-request-with-query-string", result(get("/foo?bar=baz"))); } @@ -88,7 +83,7 @@ public class HttpDocumentationTests { this.snippet.expectHttpRequest("get-request-with-parameter").withContents( httpRequest(GET, "/foo?b%26r=baz").header(HttpHeaders.HOST, "localhost")); - documentHttpRequest("get-request-with-parameter", null).handle( + new HttpRequestSnippet().document("get-request-with-parameter", result(get("/foo").param("b&r", "baz"))); } @@ -98,7 +93,7 @@ public class HttpDocumentationTests { httpRequest(POST, "/foo").header(HttpHeaders.HOST, "localhost").content( "Hello, world")); - documentHttpRequest("post-request-with-content", null).handle( + new HttpRequestSnippet().document("post-request-with-content", result(post("/foo").content("Hello, world"))); } @@ -109,7 +104,7 @@ public class HttpDocumentationTests { .header("Content-Type", "application/x-www-form-urlencoded") .content("b%26r=baz&a=alpha")); - documentHttpRequest("post-request-with-parameter", null).handle( + new HttpRequestSnippet().document("post-request-with-parameter", result(post("/foo").param("b&r", "baz").param("a", "alpha"))); } @@ -119,7 +114,7 @@ public class HttpDocumentationTests { httpRequest(PUT, "/foo").header(HttpHeaders.HOST, "localhost").content( "Hello, world")); - documentHttpRequest("put-request-with-content", null).handle( + new HttpRequestSnippet().document("put-request-with-content", result(put("/foo").content("Hello, world"))); } @@ -130,48 +125,10 @@ public class HttpDocumentationTests { .header("Content-Type", "application/x-www-form-urlencoded") .content("b%26r=baz&a=alpha")); - documentHttpRequest("put-request-with-parameter", null).handle( + new HttpRequestSnippet().document("put-request-with-parameter", result(put("/foo").param("b&r", "baz").param("a", "alpha"))); } - @Test - public void basicResponse() throws IOException { - this.snippet.expectHttpResponse("basic-response").withContents(httpResponse(OK)); - documentHttpResponse("basic-response", null).handle(result()); - } - - @Test - public void nonOkResponse() throws IOException { - this.snippet.expectHttpResponse("non-ok-response").withContents( - httpResponse(BAD_REQUEST)); - - MockHttpServletResponse response = new MockHttpServletResponse(); - response.setStatus(BAD_REQUEST.value()); - documentHttpResponse("non-ok-response", null).handle(result(response)); - } - - @Test - public void responseWithHeaders() throws IOException { - this.snippet.expectHttpResponse("response-with-headers").withContents( - httpResponse(OK) // - .header("Content-Type", "application/json") // - .header("a", "alpha")); - - MockHttpServletResponse response = new MockHttpServletResponse(); - response.setContentType(MediaType.APPLICATION_JSON_VALUE); - response.setHeader("a", "alpha"); - documentHttpResponse("response-with-headers", null).handle(result(response)); - } - - @Test - public void responseWithContent() throws IOException { - this.snippet.expectHttpResponse("response-with-content").withContents( - httpResponse(OK).content("content")); - MockHttpServletResponse response = new MockHttpServletResponse(); - response.getWriter().append("content"); - documentHttpResponse("response-with-content", null).handle(result(response)); - } - @Test public void multipartPost() throws IOException { String expectedContent = createPart(String.format("Content-Disposition: " @@ -184,7 +141,7 @@ public class HttpDocumentationTests { .content(expectedContent)); MockMultipartFile multipartFile = new MockMultipartFile("image", "documents/images/example.png", null, "<< data >>".getBytes()); - documentHttpRequest("multipart-post", null).handle( + new HttpRequestSnippet().document("multipart-post", result(fileUpload("/upload").file(multipartFile))); } @@ -207,7 +164,8 @@ public class HttpDocumentationTests { .content(expectedContent)); MockMultipartFile multipartFile = new MockMultipartFile("image", "documents/images/example.png", null, "<< data >>".getBytes()); - documentHttpRequest("multipart-post", null).handle( + new HttpRequestSnippet().document( + "multipart-post", result(fileUpload("/upload").file(multipartFile) .param("a", "apple", "avocado").param("b", "banana"))); } @@ -226,7 +184,8 @@ public class HttpDocumentationTests { MockMultipartFile multipartFile = new MockMultipartFile("image", "documents/images/example.png", MediaType.IMAGE_PNG_VALUE, "<< data >>".getBytes()); - documentHttpRequest("multipart-post-with-content-type", null).handle( + new HttpRequestSnippet().document( + "multipart-post-with-content-type", result(fileUpload("/upload").file(multipartFile))); } @@ -238,7 +197,7 @@ public class HttpDocumentationTests { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); request.setServerName("api.example.com"); - documentHttpRequest("get-request-custom-server-name", null).handle( + new HttpRequestSnippet().document("get-request-custom-server-name", result(request)); } @@ -247,7 +206,7 @@ public class HttpDocumentationTests { this.snippet.expectHttpRequest("get-request-custom-host").withContents( httpRequest(GET, "/foo").header(HttpHeaders.HOST, "api.example.com")); - documentHttpRequest("get-request-custom-host", null).handle( + new HttpRequestSnippet().document("get-request-custom-host", result(get("/foo").header(HttpHeaders.HOST, "api.example.com"))); } @@ -263,25 +222,8 @@ public class HttpDocumentationTests { "src/test/resources/custom-snippet-templates/http-request-with-title.snippet")); request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( resolver)); - documentHttpRequest("request-with-snippet-attributes", - attributes(key("title").value("Title for the request"))).handle( - result(request)); - } - - @Test - public void responseWithCustomSnippetAttributes() throws IOException { - this.snippet.expectHttpResponse("response-with-snippet-attributes").withContents( - containsString("Title for the response")); - MockHttpServletRequest request = new MockHttpServletRequest(); - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - when(resolver.resolveTemplateResource("http-response")) - .thenReturn( - new FileSystemResource( - "src/test/resources/custom-snippet-templates/http-response-with-title.snippet")); - request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( - resolver)); - documentHttpResponse("response-with-snippet-attributes", - attributes(key("title").value("Title for the response"))).handle( + new HttpRequestSnippet(attributes(key("title").value( + "Title for the request"))).document("request-with-snippet-attributes", result(request)); } @@ -290,12 +232,12 @@ public class HttpDocumentationTests { } private String createPart(String content, boolean last) { - String boundary = "6o2knFse3p53ty9dmcQvWAIx1zInP11uCfbm"; StringBuilder part = new StringBuilder(); - part.append(String.format("--%s%n%s%n", boundary, content)); + part.append(String.format("--%s%n%s%n", BOUNDARY, content)); if (last) { - part.append(String.format("--%s--", boundary)); + part.append(String.format("--%s--", BOUNDARY)); } return part.toString(); } + } diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/http/HttpResponseDocumentationHandlerTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/http/HttpResponseDocumentationHandlerTests.java new file mode 100644 index 00000000..c9983761 --- /dev/null +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/http/HttpResponseDocumentationHandlerTests.java @@ -0,0 +1,111 @@ +/* + * 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.http; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.OK; +import static org.springframework.restdocs.snippet.Attributes.attributes; +import static org.springframework.restdocs.snippet.Attributes.key; +import static org.springframework.restdocs.test.SnippetMatchers.httpResponse; +import static org.springframework.restdocs.test.StubMvcResult.result; + +import java.io.IOException; + +import org.junit.Rule; +import org.junit.Test; +import org.springframework.core.io.FileSystemResource; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.restdocs.templates.TemplateEngine; +import org.springframework.restdocs.templates.TemplateResourceResolver; +import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; +import org.springframework.restdocs.test.ExpectedSnippet; + +/** + * Tests for {@link HttpResponseSnippet} + * + * @author Andy Wilkinson + * @author Jonathan Pearlin + */ +public class HttpResponseDocumentationHandlerTests { + + @Rule + public final ExpectedSnippet snippet = new ExpectedSnippet(); + + @Test + public void basicResponse() throws IOException { + this.snippet.expectHttpResponse("basic-response").withContents(httpResponse(OK)); + new HttpResponseSnippet().document("basic-response", result()); + } + + @Test + public void nonOkResponse() throws IOException { + this.snippet.expectHttpResponse("non-ok-response").withContents( + httpResponse(BAD_REQUEST)); + + MockHttpServletResponse response = new MockHttpServletResponse(); + response.setStatus(BAD_REQUEST.value()); + new HttpResponseSnippet().document("non-ok-response", + result(response)); + } + + @Test + public void responseWithHeaders() throws IOException { + this.snippet.expectHttpResponse("response-with-headers").withContents( + httpResponse(OK) // + .header("Content-Type", "application/json") // + .header("a", "alpha")); + + MockHttpServletResponse response = new MockHttpServletResponse(); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setHeader("a", "alpha"); + new HttpResponseSnippet().document("response-with-headers", + result(response)); + } + + @Test + public void responseWithContent() throws IOException { + this.snippet.expectHttpResponse("response-with-content").withContents( + httpResponse(OK).content("content")); + MockHttpServletResponse response = new MockHttpServletResponse(); + response.getWriter().append("content"); + new HttpResponseSnippet().document("response-with-content", + result(response)); + } + + @Test + public void responseWithCustomSnippetAttributes() throws IOException { + this.snippet.expectHttpResponse("response-with-snippet-attributes").withContents( + containsString("Title for the response")); + MockHttpServletRequest request = new MockHttpServletRequest(); + TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); + when(resolver.resolveTemplateResource("http-response")) + .thenReturn( + new FileSystemResource( + "src/test/resources/custom-snippet-templates/http-response-with-title.snippet")); + request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( + resolver)); + new HttpResponseSnippet(attributes(key("title").value( + "Title for the response"))).document("response-with-snippet-attributes", + result(request)); + } + +} diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractorTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractorTests.java new file mode 100644 index 00000000..1c258a26 --- /dev/null +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractorTests.java @@ -0,0 +1,70 @@ +/* + * 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.hypermedia; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpServletResponse; + +/** + * Tests for {@link ContentTypeLinkExtractor}. + * + * @author Andy Wilkinson + */ +public class ContentTypeLinkExtractorTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private final MockHttpServletResponse response = new MockHttpServletResponse(); + + @Test + public void extractionFailsWithNullContentType() throws IOException { + this.thrown.expect(IllegalStateException.class); + new ContentTypeLinkExtractor().extractLinks(this.response); + } + + @Test + public void extractorCalledWithMatchingContextType() throws IOException { + Map extractors = new HashMap<>(); + LinkExtractor extractor = mock(LinkExtractor.class); + extractors.put(MediaType.APPLICATION_JSON, extractor); + this.response.setContentType("application/json"); + new ContentTypeLinkExtractor(extractors).extractLinks(this.response); + verify(extractor).extractLinks(this.response); + } + + @Test + public void extractorCalledWithCompatibleContextType() throws IOException { + Map extractors = new HashMap<>(); + LinkExtractor extractor = mock(LinkExtractor.class); + extractors.put(MediaType.APPLICATION_JSON, extractor); + this.response.setContentType("application/json;foo=bar"); + new ContentTypeLinkExtractor(extractors).extractLinks(this.response); + verify(extractor).extractLinks(this.response); + } + +} diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/hypermedia/LinkExtractorsPayloadTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/hypermedia/LinkExtractorsPayloadTests.java index 036aa1b5..9e6256d7 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/hypermedia/LinkExtractorsPayloadTests.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/hypermedia/LinkExtractorsPayloadTests.java @@ -37,7 +37,8 @@ import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; /** - * Parameterized tests for {@link LinkExtractors} with various payloads. + * Parameterized tests for {@link HalLinkExtractor} and {@link AtomLinkExtractor} with + * various payloads. * * @author Andy Wilkinson */ @@ -50,8 +51,8 @@ public class LinkExtractorsPayloadTests { @Parameters public static Collection data() { - return Arrays.asList(new Object[] { LinkExtractors.halLinks(), "hal" }, - new Object[] { LinkExtractors.atomLinks(), "atom" }); + return Arrays.asList(new Object[] { new HalLinkExtractor(), "hal" }, + new Object[] { new AtomLinkExtractor(), "atom" }); } public LinkExtractorsPayloadTests(LinkExtractor linkExtractor, String linkType) { diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/hypermedia/LinkExtractorsTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/hypermedia/LinkExtractorsTests.java deleted file mode 100644 index 89cba6f0..00000000 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/hypermedia/LinkExtractorsTests.java +++ /dev/null @@ -1,65 +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.hypermedia; - -import static org.hamcrest.CoreMatchers.instanceOf; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.junit.Assert.assertThat; - -import org.junit.Test; -import org.springframework.restdocs.hypermedia.LinkExtractors.AtomLinkExtractor; -import org.springframework.restdocs.hypermedia.LinkExtractors.HalLinkExtractor; - -/** - * Tests for {@link LinkExtractors}. - * - * @author Andy Wilkinson - */ -public class LinkExtractorsTests { - - @Test - public void nullContentTypeYieldsNullExtractor() { - assertThat(LinkExtractors.extractorForContentType(null), nullValue()); - } - - @Test - public void emptyContentTypeYieldsNullExtractor() { - assertThat(LinkExtractors.extractorForContentType(""), nullValue()); - } - - @Test - public void applicationJsonContentTypeYieldsAtomExtractor() { - LinkExtractor linkExtractor = LinkExtractors - .extractorForContentType("application/json"); - assertThat(linkExtractor, instanceOf(AtomLinkExtractor.class)); - } - - @Test - public void applicationHalJsonContentTypeYieldsHalExtractor() { - LinkExtractor linkExtractor = LinkExtractors - .extractorForContentType("application/hal+json"); - assertThat(linkExtractor, instanceOf(HalLinkExtractor.class)); - } - - @Test - public void contentTypeWithParameterYieldsExtractor() { - LinkExtractor linkExtractor = LinkExtractors - .extractorForContentType("application/json;foo=bar"); - assertThat(linkExtractor, instanceOf(AtomLinkExtractor.class)); - } - -} diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/hypermedia/HypermediaDocumentationTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/hypermedia/LinksDocumentationHandlerTests.java similarity index 69% rename from spring-restdocs/src/test/java/org/springframework/restdocs/hypermedia/HypermediaDocumentationTests.java rename to spring-restdocs/src/test/java/org/springframework/restdocs/hypermedia/LinksDocumentationHandlerTests.java index 6fb0d58d..71943b10 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/hypermedia/HypermediaDocumentationTests.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/hypermedia/LinksDocumentationHandlerTests.java @@ -20,13 +20,14 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.startsWith; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.documentLinks; import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader; import static org.springframework.restdocs.test.StubMvcResult.result; import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; import org.junit.Rule; import org.junit.Test; @@ -34,7 +35,7 @@ import org.junit.rules.ExpectedException; import org.springframework.core.io.FileSystemResource; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.restdocs.snippet.SnippetGenerationException; +import org.springframework.restdocs.snippet.SnippetException; import org.springframework.restdocs.templates.TemplateEngine; import org.springframework.restdocs.templates.TemplateResourceResolver; import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; @@ -43,11 +44,11 @@ import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; /** - * Tests for {@link HypermediaDocumentation} + * Tests for {@link LinksSnippet} * * @author Andy Wilkinson */ -public class HypermediaDocumentationTests { +public class LinksDocumentationHandlerTests { @Rule public ExpectedSnippet snippet = new ExpectedSnippet(); @@ -57,21 +58,22 @@ public class HypermediaDocumentationTests { @Test public void undocumentedLink() throws IOException { - this.thrown.expect(SnippetGenerationException.class); + this.thrown.expect(SnippetException.class); this.thrown.expectMessage(equalTo("Links with the following relations were not" + " documented: [foo]")); - documentLinks("undocumented-link", null, - new StubLinkExtractor().withLinks(new Link("foo", "bar"))).handle( - result()); + new LinksSnippet(new StubLinkExtractor().withLinks(new Link("foo", + "bar")), Collections. emptyList()).document( + "undocumented-link", result()); } @Test public void missingLink() throws IOException { - this.thrown.expect(SnippetGenerationException.class); + this.thrown.expect(SnippetException.class); this.thrown.expectMessage(equalTo("Links with the following relations were not" + " found in the response: [foo]")); - documentLinks("missing-link", null, new StubLinkExtractor(), - new LinkDescriptor("foo").description("bar")).handle(result()); + new LinksSnippet(new StubLinkExtractor(), + Arrays.asList(new LinkDescriptor("foo").description("bar"))).document( + "missing-link", result()); } @Test @@ -79,9 +81,9 @@ public class HypermediaDocumentationTests { this.snippet.expectLinks("documented-optional-link").withContents( // tableWithHeader("Relation", "Description") // .row("foo", "bar")); - documentLinks("documented-optional-link", null, - new StubLinkExtractor().withLinks(new Link("foo", "blah")), - new LinkDescriptor("foo").description("bar").optional()).handle(result()); + new LinksSnippet(new StubLinkExtractor().withLinks(new Link("foo", + "blah")), Arrays.asList(new LinkDescriptor("foo").description("bar") + .optional())).document("documented-optional-link", result()); } @Test @@ -89,19 +91,20 @@ public class HypermediaDocumentationTests { this.snippet.expectLinks("missing-optional-link").withContents( // tableWithHeader("Relation", "Description") // .row("foo", "bar")); - documentLinks("missing-optional-link", null, new StubLinkExtractor(), - new LinkDescriptor("foo").description("bar").optional()).handle(result()); + new LinksSnippet(new StubLinkExtractor(), + Arrays.asList(new LinkDescriptor("foo").description("bar").optional())) + .document("missing-optional-link", result()); } @Test public void undocumentedLinkAndMissingLink() throws IOException { - this.thrown.expect(SnippetGenerationException.class); + this.thrown.expect(SnippetException.class); this.thrown.expectMessage(equalTo("Links with the following relations were not" + " documented: [a]. Links with the following relations were not" + " found in the response: [foo]")); - documentLinks("undocumented-link-and-missing-link", null, - new StubLinkExtractor().withLinks(new Link("a", "alpha")), - new LinkDescriptor("foo").description("bar")).handle(result()); + new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a", + "alpha")), Arrays.asList(new LinkDescriptor("foo").description("bar"))) + .document("undocumented-link-and-missing-link", result()); } @Test @@ -110,12 +113,11 @@ public class HypermediaDocumentationTests { tableWithHeader("Relation", "Description") // .row("a", "one") // .row("b", "two")); - documentLinks( - "documented-links", - null, - new StubLinkExtractor().withLinks(new Link("a", "alpha"), new Link("b", - "bravo")), new LinkDescriptor("a").description("one"), - new LinkDescriptor("b").description("two")).handle(result()); + new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a", + "alpha"), new Link("b", "bravo")), Arrays.asList( + new LinkDescriptor("a").description("one"), + new LinkDescriptor("b").description("two"))).document("documented-links", + result()); } @Test @@ -132,20 +134,18 @@ public class HypermediaDocumentationTests { "src/test/resources/custom-snippet-templates/links-with-extra-column.snippet")); request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( resolver)); - documentLinks( - "links-with-custom-descriptor-attributes", - null, - new StubLinkExtractor().withLinks(new Link("a", "alpha"), new Link("b", - "bravo")), + new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a", + "alpha"), new Link("b", "bravo")), Arrays.asList( new LinkDescriptor("a").description("one").attributes( key("foo").value("alpha")), new LinkDescriptor("b").description("two").attributes( - key("foo").value("bravo"))).handle(result(request)); + key("foo").value("bravo")))).document( + "links-with-custom-descriptor-attributes", result(request)); } @Test - public void linksWithCustomAttribute() throws IOException { - this.snippet.expectLinks("links-with-custom-attribute").withContents( + public void linksWithCustomAttributes() throws IOException { + this.snippet.expectLinks("links-with-custom-attributes").withContents( startsWith(".Title for the links")); MockHttpServletRequest request = new MockHttpServletRequest(); TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); @@ -155,12 +155,12 @@ public class HypermediaDocumentationTests { "src/test/resources/custom-snippet-templates/links-with-title.snippet")); request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( resolver)); - documentLinks( - "links-with-custom-attribute", - attributes(key("title").value("Title for the links")), - new StubLinkExtractor().withLinks(new Link("a", "alpha"), new Link("b", - "bravo")), new LinkDescriptor("a").description("one"), - new LinkDescriptor("b").description("two")).handle(result(request)); + new LinksSnippet(new StubLinkExtractor().withLinks(new Link("a", + "alpha"), new Link("b", "bravo")), attributes(key("title").value( + "Title for the links")), Arrays.asList( + new LinkDescriptor("a").description("one"), + new LinkDescriptor("b").description("two"))).document( + "links-with-custom-attributes", result(request)); } private static class StubLinkExtractor implements LinkExtractor { diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/payload/FieldValidatorTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/payload/FieldValidatorTests.java index e8ec0f29..53f118f4 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/payload/FieldValidatorTests.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/payload/FieldValidatorTests.java @@ -24,7 +24,7 @@ import java.util.Arrays; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.springframework.restdocs.snippet.SnippetGenerationException; +import org.springframework.restdocs.snippet.SnippetException; /** * Tests for {@link FieldValidator} @@ -73,7 +73,7 @@ public class FieldValidatorTests { @Test public void missingField() throws IOException { - this.thrownException.expect(SnippetGenerationException.class); + this.thrownException.expect(SnippetException.class); this.thrownException .expectMessage(equalTo("Fields with the following paths were not found" + " in the payload: [y, z]")); @@ -84,7 +84,7 @@ public class FieldValidatorTests { @Test public void undocumentedField() throws IOException { - this.thrownException.expect(SnippetGenerationException.class); + this.thrownException.expect(SnippetException.class); this.thrownException.expectMessage(equalTo(String .format("The following parts of the payload were not" + " documented:%n{%n \"a\" : {%n \"c\" : true%n }%n}"))); diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/payload/PayloadDocumentationTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/payload/PayloadDocumentationTests.java deleted file mode 100644 index f23a3ba6..00000000 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/payload/PayloadDocumentationTests.java +++ /dev/null @@ -1,282 +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.payload; - -import static org.hamcrest.CoreMatchers.endsWith; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.startsWith; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.springframework.restdocs.RestDocumentationRequestBuilders.get; -import static org.springframework.restdocs.payload.PayloadDocumentation.documentRequestFields; -import static org.springframework.restdocs.payload.PayloadDocumentation.documentResponseFields; -import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; -import static org.springframework.restdocs.snippet.Attributes.attributes; -import static org.springframework.restdocs.snippet.Attributes.key; -import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader; -import static org.springframework.restdocs.test.StubMvcResult.result; - -import java.io.IOException; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.springframework.core.io.FileSystemResource; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.restdocs.snippet.SnippetGenerationException; -import org.springframework.restdocs.templates.TemplateEngine; -import org.springframework.restdocs.templates.TemplateResourceResolver; -import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; -import org.springframework.restdocs.test.ExpectedSnippet; - -/** - * Tests for {@link PayloadDocumentation} - * - * @author Andy Wilkinson - */ -public class PayloadDocumentationTests { - - @Rule - public final ExpectedException thrown = ExpectedException.none(); - - @Rule - public final ExpectedSnippet snippet = new ExpectedSnippet(); - - @Test - public void mapRequestWithFields() throws IOException { - this.snippet.expectRequestFields("map-request-with-fields").withContents( // - tableWithHeader("Path", "Type", "Description") // - .row("a.b", "Number", "one") // - .row("a.c", "String", "two") // - .row("a", "Object", "three")); - - documentRequestFields("map-request-with-fields", null, - fieldWithPath("a.b").description("one"), - fieldWithPath("a.c").description("two"), - fieldWithPath("a").description("three")).handle( - result(get("/foo").content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}"))); - } - - @Test - public void arrayRequestWithFields() throws IOException { - this.snippet.expectRequestFields("array-request-with-fields").withContents( // - tableWithHeader("Path", "Type", "Description") // - .row("[]a.b", "Number", "one") // - .row("[]a.c", "String", "two") // - .row("[]a", "Object", "three")); - - documentRequestFields("array-request-with-fields", null, - fieldWithPath("[]a.b").description("one"), - fieldWithPath("[]a.c").description("two"), - fieldWithPath("[]a").description("three")).handle( - result(get("/foo").content( - "[{\"a\": {\"b\": 5}},{\"a\": {\"c\": \"charlie\"}}]"))); - } - - @Test - public void mapResponseWithFields() throws IOException { - this.snippet.expectResponseFields("map-response-with-fields").withContents(// - tableWithHeader("Path", "Type", "Description") // - .row("id", "Number", "one") // - .row("date", "String", "two") // - .row("assets", "Array", "three") // - .row("assets[]", "Object", "four") // - .row("assets[].id", "Number", "five") // - .row("assets[].name", "String", "six")); - - MockHttpServletResponse response = new MockHttpServletResponse(); - response.getWriter().append( - "{\"id\": 67,\"date\": \"2015-01-20\",\"assets\":" - + " [{\"id\":356,\"name\": \"sample\"}]}"); - documentResponseFields("map-response-with-fields", null, - fieldWithPath("id").description("one"), - fieldWithPath("date").description("two"), - fieldWithPath("assets").description("three"), - fieldWithPath("assets[]").description("four"), - fieldWithPath("assets[].id").description("five"), - fieldWithPath("assets[].name").description("six")).handle( - result(response)); - } - - @Test - public void arrayResponseWithFields() throws IOException { - this.snippet.expectResponseFields("array-response-with-fields").withContents( // - tableWithHeader("Path", "Type", "Description") // - .row("[]a.b", "Number", "one") // - .row("[]a.c", "String", "two") // - .row("[]a", "Object", "three")); - - MockHttpServletResponse response = new MockHttpServletResponse(); - response.getWriter() - .append("[{\"a\": {\"b\": 5}},{\"a\": {\"c\": \"charlie\"}}]"); - documentResponseFields("array-response-with-fields", null, - fieldWithPath("[]a.b").description("one"), - fieldWithPath("[]a.c").description("two"), - fieldWithPath("[]a").description("three")).handle(result(response)); - } - - @Test - public void arrayResponse() throws IOException { - this.snippet.expectResponseFields("array-response").withContents( // - tableWithHeader("Path", "Type", "Description") // - .row("[]", "String", "one")); - - MockHttpServletResponse response = new MockHttpServletResponse(); - response.getWriter().append("[\"a\", \"b\", \"c\"]"); - documentResponseFields("array-response", null, - fieldWithPath("[]").description("one")).handle(result(response)); - } - - @Test - public void undocumentedRequestField() throws IOException { - this.thrown.expect(SnippetGenerationException.class); - this.thrown - .expectMessage(startsWith("The following parts of the payload were not" - + " documented:")); - documentRequestFields("undocumented-request-fields", null).handle( - result(get("/foo").content("{\"a\": 5}"))); - } - - @Test - public void missingRequestField() throws IOException { - this.thrown.expect(SnippetGenerationException.class); - this.thrown - .expectMessage(equalTo("Fields with the following paths were not found" - + " in the payload: [a.b]")); - documentRequestFields("missing-request-fields", null, - fieldWithPath("a.b").description("one")).handle( - result(get("/foo").content("{}"))); - } - - @Test - public void missingOptionalRequestFieldWithNoTypeProvided() throws IOException { - this.thrown.expect(FieldTypeRequiredException.class); - documentRequestFields("missing-optional-request-field-with-no-type", null, - fieldWithPath("a.b").description("one").optional()).handle( - result(get("/foo").content("{ }"))); - } - - @Test - public void undocumentedRequestFieldAndMissingRequestField() throws IOException { - this.thrown.expect(SnippetGenerationException.class); - this.thrown - .expectMessage(startsWith("The following parts of the payload were not" - + " documented:")); - this.thrown - .expectMessage(endsWith("Fields with the following paths were not found" - + " in the payload: [a.b]")); - documentRequestFields("undocumented-request-field-and-missing-request-field", - null, fieldWithPath("a.b").description("one")).handle( - result(get("/foo").content("{ \"a\": { \"c\": 5 }}"))); - } - - @Test - public void requestFieldsWithCustomDescriptorAttributes() throws IOException { - this.snippet.expectRequestFields( - "request-fields-with-custom-descriptor-attributes").withContents( // - tableWithHeader("Path", "Type", "Description", "Foo") // - .row("a.b", "Number", "one", "alpha") // - .row("a.c", "String", "two", "bravo") // - .row("a", "Object", "three", "charlie")); - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - when(resolver.resolveTemplateResource("request-fields")) - .thenReturn( - new FileSystemResource( - "src/test/resources/custom-snippet-templates/request-fields-with-extra-column.snippet")); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( - resolver)); - request.setContent("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}".getBytes()); - documentRequestFields( - "request-fields-with-custom-descriptor-attributes", - null, - fieldWithPath("a.b").description("one").attributes( - key("foo").value("alpha")), - fieldWithPath("a.c").description("two").attributes( - key("foo").value("bravo")), - fieldWithPath("a").description("three").attributes( - key("foo").value("charlie"))).handle(result(request)); - } - - @Test - public void responseFieldsWithCustomDescriptorAttributes() throws IOException { - this.snippet.expectResponseFields("response-fields-with-custom-attributes") - .withContents( // - tableWithHeader("Path", "Type", "Description", "Foo") // - .row("a.b", "Number", "one", "alpha") // - .row("a.c", "String", "two", "bravo") // - .row("a", "Object", "three", "charlie")); - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - when(resolver.resolveTemplateResource("response-fields")) - .thenReturn( - new FileSystemResource( - "src/test/resources/custom-snippet-templates/response-fields-with-extra-column.snippet")); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( - resolver)); - MockHttpServletResponse response = new MockHttpServletResponse(); - response.getOutputStream().print("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}"); - documentResponseFields( - "response-fields-with-custom-attributes", - null, - fieldWithPath("a.b").description("one").attributes( - key("foo").value("alpha")), - fieldWithPath("a.c").description("two").attributes( - key("foo").value("bravo")), - fieldWithPath("a").description("three").attributes( - key("foo").value("charlie"))).handle(result(request, response)); - } - - @Test - public void requestFieldsWithCustomAttributes() throws IOException { - this.snippet.expectRequestFields("request-fields-with-custom-attributes") - .withContents(startsWith(".Custom title")); - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - when(resolver.resolveTemplateResource("request-fields")) - .thenReturn( - new FileSystemResource( - "src/test/resources/custom-snippet-templates/request-fields-with-title.snippet")); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( - resolver)); - request.setContent("{\"a\": \"foo\"}".getBytes()); - MockHttpServletResponse response = new MockHttpServletResponse(); - documentRequestFields("request-fields-with-custom-attributes", - attributes(key("title").value("Custom title")), - fieldWithPath("a").description("one")).handle(result(request, response)); - } - - @Test - public void responseFieldsWithCustomAttributes() throws IOException { - this.snippet.expectResponseFields("response-fields-with-custom-attributes") - .withContents(startsWith(".Custom title")); - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - when(resolver.resolveTemplateResource("response-fields")) - .thenReturn( - new FileSystemResource( - "src/test/resources/custom-snippet-templates/response-fields-with-title.snippet")); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( - resolver)); - MockHttpServletResponse response = new MockHttpServletResponse(); - response.getOutputStream().print("{\"a\": \"foo\"}"); - documentResponseFields("response-fields-with-custom-attributes", - attributes(key("title").value("Custom title")), - fieldWithPath("a").description("one")).handle(result(request, response)); - } - -} diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/payload/RequestFieldsDocumentationHandlerTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/payload/RequestFieldsDocumentationHandlerTests.java new file mode 100644 index 00000000..4e772260 --- /dev/null +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/payload/RequestFieldsDocumentationHandlerTests.java @@ -0,0 +1,185 @@ +/* + * 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.payload; + +import static org.hamcrest.CoreMatchers.endsWith; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.startsWith; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.springframework.restdocs.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.snippet.Attributes.attributes; +import static org.springframework.restdocs.snippet.Attributes.key; +import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader; +import static org.springframework.restdocs.test.StubMvcResult.result; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.springframework.core.io.FileSystemResource; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.restdocs.snippet.SnippetException; +import org.springframework.restdocs.templates.TemplateEngine; +import org.springframework.restdocs.templates.TemplateResourceResolver; +import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; +import org.springframework.restdocs.test.ExpectedSnippet; + +/** + * Tests for {@link RequestFieldsSnippet} + * + * @author Andy Wilkinson + */ +public class RequestFieldsDocumentationHandlerTests { + + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + @Rule + public final ExpectedSnippet snippet = new ExpectedSnippet(); + + @Test + public void mapRequestWithFields() throws IOException { + this.snippet.expectRequestFields("map-request-with-fields").withContents( // + tableWithHeader("Path", "Type", "Description") // + .row("a.b", "Number", "one") // + .row("a.c", "String", "two") // + .row("a", "Object", "three")); + + new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b") + .description("one"), fieldWithPath("a.c").description("two"), + fieldWithPath("a").description("three"))).document( + "map-request-with-fields", + result(get("/foo").content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}"))); + } + + @Test + public void arrayRequestWithFields() throws IOException { + this.snippet.expectRequestFields("array-request-with-fields").withContents( // + tableWithHeader("Path", "Type", "Description") // + .row("[]a.b", "Number", "one") // + .row("[]a.c", "String", "two") // + .row("[]a", "Object", "three")); + + new RequestFieldsSnippet(Arrays.asList(fieldWithPath("[]a.b") + .description("one"), fieldWithPath("[]a.c").description("two"), + fieldWithPath("[]a").description("three"))).document( + "array-request-with-fields", + result(get("/foo").content( + "[{\"a\": {\"b\": 5}},{\"a\": {\"c\": \"charlie\"}}]"))); + } + + @Test + public void undocumentedRequestField() throws IOException { + this.thrown.expect(SnippetException.class); + this.thrown + .expectMessage(startsWith("The following parts of the payload were not" + + " documented:")); + new RequestFieldsSnippet(Collections. emptyList()) + .document("undocumented-request-field", + result(get("/foo").content("{\"a\": 5}"))); + } + + @Test + public void missingRequestField() throws IOException { + this.thrown.expect(SnippetException.class); + this.thrown + .expectMessage(equalTo("Fields with the following paths were not found" + + " in the payload: [a.b]")); + new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b") + .description("one"))).document("missing-request-fields", + result(get("/foo").content("{}"))); + } + + @Test + public void missingOptionalRequestFieldWithNoTypeProvided() throws IOException { + this.thrown.expect(FieldTypeRequiredException.class); + new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b") + .description("one").optional())).document( + "missing-optional-request-field-with-no-type", result(get("/foo") + .content("{ }"))); + } + + @Test + public void undocumentedRequestFieldAndMissingRequestField() throws IOException { + this.thrown.expect(SnippetException.class); + this.thrown + .expectMessage(startsWith("The following parts of the payload were not" + + " documented:")); + this.thrown + .expectMessage(endsWith("Fields with the following paths were not found" + + " in the payload: [a.b]")); + new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b") + .description("one"))).document( + "undocumented-request-field-and-missing-request-field", + result(get("/foo").content("{ \"a\": { \"c\": 5 }}"))); + } + + @Test + public void requestFieldsWithCustomDescriptorAttributes() throws IOException { + this.snippet.expectRequestFields( + "request-fields-with-custom-descriptor-attributes").withContents( // + tableWithHeader("Path", "Type", "Description", "Foo") // + .row("a.b", "Number", "one", "alpha") // + .row("a.c", "String", "two", "bravo") // + .row("a", "Object", "three", "charlie")); + TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); + when(resolver.resolveTemplateResource("request-fields")).thenReturn( + snippetResource("request-fields-with-extra-column")); + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( + resolver)); + request.setContent("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}".getBytes()); + new RequestFieldsSnippet(Arrays.asList( + fieldWithPath("a.b").description("one").attributes( + key("foo").value("alpha")), + fieldWithPath("a.c").description("two").attributes( + key("foo").value("bravo")), + fieldWithPath("a").description("three").attributes( + key("foo").value("charlie")))).document( + "request-fields-with-custom-descriptor-attributes", result(request)); + } + + @Test + public void requestFieldsWithCustomAttributes() throws IOException { + this.snippet.expectRequestFields("request-fields-with-custom-attributes") + .withContents(startsWith(".Custom title")); + TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); + when(resolver.resolveTemplateResource("request-fields")).thenReturn( + snippetResource("request-fields-with-title")); + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( + resolver)); + request.setContent("{\"a\": \"foo\"}".getBytes()); + MockHttpServletResponse response = new MockHttpServletResponse(); + new RequestFieldsSnippet(attributes(key("title").value( + "Custom title")), Arrays.asList(fieldWithPath("a").description("one"))) + .document("request-fields-with-custom-attributes", + result(request, response)); + } + + private FileSystemResource snippetResource(String name) { + return new FileSystemResource("src/test/resources/custom-snippet-templates/" + + name + ".snippet"); + } + +} diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/payload/ResponseFieldsDocumentationHandlerTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/payload/ResponseFieldsDocumentationHandlerTests.java new file mode 100644 index 00000000..9b165a4e --- /dev/null +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/payload/ResponseFieldsDocumentationHandlerTests.java @@ -0,0 +1,156 @@ +/* + * 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.payload; + +import static org.hamcrest.CoreMatchers.startsWith; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.snippet.Attributes.attributes; +import static org.springframework.restdocs.snippet.Attributes.key; +import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader; +import static org.springframework.restdocs.test.StubMvcResult.result; + +import java.io.IOException; +import java.util.Arrays; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.springframework.core.io.FileSystemResource; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.restdocs.templates.TemplateEngine; +import org.springframework.restdocs.templates.TemplateResourceResolver; +import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; +import org.springframework.restdocs.test.ExpectedSnippet; + +/** + * Tests for {@link PayloadDocumentation} + * + * @author Andy Wilkinson + */ +public class ResponseFieldsDocumentationHandlerTests { + + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + @Rule + public final ExpectedSnippet snippet = new ExpectedSnippet(); + + @Test + public void mapResponseWithFields() throws IOException { + this.snippet.expectResponseFields("map-response-with-fields").withContents(// + tableWithHeader("Path", "Type", "Description") // + .row("id", "Number", "one") // + .row("date", "String", "two") // + .row("assets", "Array", "three") // + .row("assets[]", "Object", "four") // + .row("assets[].id", "Number", "five") // + .row("assets[].name", "String", "six")); + + MockHttpServletResponse response = new MockHttpServletResponse(); + response.getWriter().append( + "{\"id\": 67,\"date\": \"2015-01-20\",\"assets\":" + + " [{\"id\":356,\"name\": \"sample\"}]}"); + new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("id") + .description("one"), fieldWithPath("date").description("two"), + fieldWithPath("assets").description("three"), fieldWithPath("assets[]") + .description("four"), + fieldWithPath("assets[].id").description("five"), + fieldWithPath("assets[].name").description("six"))).document( + "map-response-with-fields", result(response)); + } + + @Test + public void arrayResponseWithFields() throws IOException { + this.snippet.expectResponseFields("array-response-with-fields").withContents( // + tableWithHeader("Path", "Type", "Description") // + .row("[]a.b", "Number", "one") // + .row("[]a.c", "String", "two") // + .row("[]a", "Object", "three")); + + MockHttpServletResponse response = new MockHttpServletResponse(); + response.getWriter() + .append("[{\"a\": {\"b\": 5}},{\"a\": {\"c\": \"charlie\"}}]"); + new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("[]a.b") + .description("one"), fieldWithPath("[]a.c").description("two"), + fieldWithPath("[]a").description("three"))).document( + "array-response-with-fields", result(response)); + } + + @Test + public void arrayResponse() throws IOException { + this.snippet.expectResponseFields("array-response").withContents( // + tableWithHeader("Path", "Type", "Description") // + .row("[]", "String", "one")); + + MockHttpServletResponse response = new MockHttpServletResponse(); + response.getWriter().append("[\"a\", \"b\", \"c\"]"); + new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("[]") + .description("one"))).document("array-response", result(response)); + } + + @Test + public void responseFieldsWithCustomDescriptorAttributes() throws IOException { + this.snippet.expectResponseFields("response-fields-with-custom-attributes") + .withContents( // + tableWithHeader("Path", "Type", "Description", "Foo") // + .row("a.b", "Number", "one", "alpha") // + .row("a.c", "String", "two", "bravo") // + .row("a", "Object", "three", "charlie")); + TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); + when(resolver.resolveTemplateResource("response-fields")).thenReturn( + snippetResource("response-fields-with-extra-column")); + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( + resolver)); + MockHttpServletResponse response = new MockHttpServletResponse(); + response.getOutputStream().print("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}"); + new ResponseFieldsSnippet(Arrays.asList( + fieldWithPath("a.b").description("one").attributes( + key("foo").value("alpha")), + fieldWithPath("a.c").description("two").attributes( + key("foo").value("bravo")), + fieldWithPath("a").description("three").attributes( + key("foo").value("charlie")))).document( + "response-fields-with-custom-attributes", result(request, response)); + } + + @Test + public void responseFieldsWithCustomAttributes() throws IOException { + this.snippet.expectResponseFields("response-fields-with-custom-attributes") + .withContents(startsWith(".Custom title")); + TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); + when(resolver.resolveTemplateResource("response-fields")).thenReturn( + snippetResource("response-fields-with-title")); + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( + resolver)); + MockHttpServletResponse response = new MockHttpServletResponse(); + response.getOutputStream().print("{\"a\": \"foo\"}"); + new ResponseFieldsSnippet(attributes(key("title").value( + "Custom title")), Arrays.asList(fieldWithPath("a").description("one"))) + .document("response-fields-with-custom-attributes", + result(request, response)); + } + + private FileSystemResource snippetResource(String name) { + return new FileSystemResource("src/test/resources/custom-snippet-templates/" + + name + ".snippet"); + } + +} diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/request/PathParametersDocumentationHandlerTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/request/PathParametersDocumentationHandlerTests.java new file mode 100644 index 00000000..b980ead2 --- /dev/null +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/request/PathParametersDocumentationHandlerTests.java @@ -0,0 +1,147 @@ +/* + * 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.request; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.startsWith; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.snippet.Attributes.attributes; +import static org.springframework.restdocs.snippet.Attributes.key; +import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader; +import static org.springframework.restdocs.test.StubMvcResult.result; +import static org.springframework.restdocs.test.TestRequestBuilders.get; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.springframework.core.io.FileSystemResource; +import org.springframework.restdocs.config.RestDocumentationContext; +import org.springframework.restdocs.snippet.SnippetException; +import org.springframework.restdocs.templates.TemplateEngine; +import org.springframework.restdocs.templates.TemplateResourceResolver; +import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; +import org.springframework.restdocs.test.ExpectedSnippet; + +/** + * Tests for {@link PathParametersSnippet} + * + * @author awilkinson + * + */ +public class PathParametersDocumentationHandlerTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Rule + public ExpectedSnippet snippet = new ExpectedSnippet(); + + @Test + public void undocumentedPathParameter() throws IOException { + this.thrown.expect(SnippetException.class); + this.thrown.expectMessage(equalTo("Path parameters with the following names were" + + " not documented: [a]")); + new PathParametersSnippet(Collections. emptyList()) + .document("undocumented-path-parameter", result(get("/{a}/", "alpha"))); + } + + @Test + public void missingPathParameter() throws IOException { + this.thrown.expect(SnippetException.class); + this.thrown.expectMessage(equalTo("Path parameters with the following names were" + + " not found in the request: [a]")); + new PathParametersSnippet( + Arrays.asList(parameterWithName("a").description("one"))).document( + "missing-path-parameter", result(get("/"))); + } + + @Test + public void undocumentedAndMissingPathParameters() throws IOException { + this.thrown.expect(SnippetException.class); + this.thrown.expectMessage(equalTo("Path parameters with the following names were" + + " not documented: [b]. Path parameters with the following" + + " names were not found in the request: [a]")); + new PathParametersSnippet( + Arrays.asList(parameterWithName("a").description("one"))).document( + "undocumented-and-missing-path-parameters", result(get("/{b}", "bravo"))); + } + + @Test + public void pathParameters() throws IOException { + this.snippet.expectPathParameters("path-parameters").withContents( + tableWithHeader("Parameter", "Description").row("a", "one").row("b", + "two")); + new PathParametersSnippet(Arrays.asList( + parameterWithName("a").description("one"), parameterWithName("b") + .description("two"))).document( + "path-parameters", + result(get("/{a}/{b}", "alpha", "banana").requestAttr( + RestDocumentationContext.class.getName(), + new RestDocumentationContext(null)))); + } + + @Test + public void pathParametersWithCustomDescriptorAttributes() throws IOException { + this.snippet.expectPathParameters( + "path-parameters-with-custom-descriptor-attributes").withContents( + tableWithHeader("Parameter", "Description", "Foo").row("a", "one", + "alpha").row("b", "two", "bravo")); + TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); + when(resolver.resolveTemplateResource("path-parameters")).thenReturn( + snippetResource("path-parameters-with-extra-column")); + new PathParametersSnippet(Arrays.asList(parameterWithName("a").description("one") + .attributes(key("foo").value("alpha")), parameterWithName("b") + .description("two").attributes(key("foo").value("bravo")))).document( + "path-parameters-with-custom-descriptor-attributes", + result(get("{a}/{b}", "alpha", "bravo").requestAttr( + TemplateEngine.class.getName(), + new MustacheTemplateEngine(resolver)))); + } + + @Test + public void pathParametersWithCustomAttributes() throws IOException { + this.snippet.expectPathParameters("path-parameters-with-custom-attributes") + .withContents(startsWith(".The title")); + TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); + when(resolver.resolveTemplateResource("path-parameters")).thenReturn( + snippetResource("path-parameters-with-title")); + new PathParametersSnippet( + attributes(key("title").value("The title")), + Arrays.asList( + parameterWithName("a").description("one").attributes( + key("foo").value("alpha")), parameterWithName("b") + .description("two").attributes(key("foo").value("bravo")))) + .document( + "path-parameters-with-custom-attributes", + result(get("{a}/{b}", "alpha", "bravo").requestAttr( + TemplateEngine.class.getName(), + new MustacheTemplateEngine(resolver)))); + + } + + private FileSystemResource snippetResource(String name) { + return new FileSystemResource("src/test/resources/custom-snippet-templates/" + + name + ".snippet"); + } + +} diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/request/QueryParametersDocumentationHandlerTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/request/QueryParametersDocumentationHandlerTests.java new file mode 100644 index 00000000..00fb20a7 --- /dev/null +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/request/QueryParametersDocumentationHandlerTests.java @@ -0,0 +1,169 @@ +/* + * 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.request; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.startsWith; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.snippet.Attributes.attributes; +import static org.springframework.restdocs.snippet.Attributes.key; +import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader; +import static org.springframework.restdocs.test.StubMvcResult.result; +import static org.springframework.restdocs.test.TestRequestBuilders.get; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.springframework.core.io.FileSystemResource; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.restdocs.config.RestDocumentationContext; +import org.springframework.restdocs.snippet.SnippetException; +import org.springframework.restdocs.templates.TemplateEngine; +import org.springframework.restdocs.templates.TemplateResourceResolver; +import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; +import org.springframework.restdocs.test.ExpectedSnippet; + +/** + * Tests for {@link QueryParametersSnippet} + * + * @author Andy Wilkinson + */ +public class QueryParametersDocumentationHandlerTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Rule + public ExpectedSnippet snippet = new ExpectedSnippet(); + + @Test + public void undocumentedQueryParameter() throws IOException { + this.thrown.expect(SnippetException.class); + this.thrown + .expectMessage(equalTo("Query parameters with the following names were" + + " not documented: [a]")); + new QueryParametersSnippet(Collections. emptyList()) + .document("undocumented-query-parameter", + result(get("/").param("a", "alpha"))); + } + + @Test + public void missingQueryParameter() throws IOException { + this.thrown.expect(SnippetException.class); + this.thrown + .expectMessage(equalTo("Query parameters with the following names were" + + " not found in the request: [a]")); + new QueryParametersSnippet(Arrays.asList(parameterWithName("a") + .description("one"))).document("missing-query-parameter", + result(get("/"))); + } + + @Test + public void undocumentedAndMissingQueryParameters() throws IOException { + this.thrown.expect(SnippetException.class); + this.thrown + .expectMessage(equalTo("Query parameters with the following names were" + + " not documented: [b]. Query parameters with the following" + + " names were not found in the request: [a]")); + new QueryParametersSnippet(Arrays.asList(parameterWithName("a") + .description("one"))).document( + "undocumented-and-missing-query-parameters", + result(get("/").param("b", "bravo"))); + } + + @Test + public void queryParameterSnippetFromRequestParameters() throws IOException { + this.snippet.expectQueryParameters("query-parameter-snippet-request-parameters") + .withContents( + tableWithHeader("Parameter", "Description").row("a", "one").row( + "b", "two")); + new QueryParametersSnippet(Arrays.asList(parameterWithName("a") + .description("one"), parameterWithName("b").description("two"))) + .document("query-parameter-snippet-request-parameters", result(get("/") + .param("a", "bravo").param("b", "bravo"))); + } + + @Test + public void queryParameterSnippetFromRequestUriQueryString() throws IOException { + this.snippet.expectQueryParameters( + "query-parameter-snippet-request-uri-query-string").withContents( + tableWithHeader("Parameter", "Description").row("a", "one").row("b", + "two")); + new QueryParametersSnippet(Arrays.asList(parameterWithName("a") + .description("one"), parameterWithName("b").description("two"))) + .document( + "query-parameter-snippet-request-uri-query-string", + result(get("/?a=alpha&b=bravo").requestAttr( + RestDocumentationContext.class.getName(), + new RestDocumentationContext(null)))); + } + + @Test + public void queryParametersWithCustomDescriptorAttributes() throws IOException { + this.snippet.expectQueryParameters( + "query-parameters-with-custom-descriptor-attributes").withContents( + tableWithHeader("Parameter", "Description", "Foo").row("a", "one", + "alpha").row("b", "two", "bravo")); + TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); + when(resolver.resolveTemplateResource("query-parameters")).thenReturn( + snippetResource("query-parameters-with-extra-column")); + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( + resolver)); + request.addParameter("a", "bravo"); + request.addParameter("b", "bravo"); + new QueryParametersSnippet(Arrays.asList( + parameterWithName("a").description("one").attributes( + key("foo").value("alpha")), + parameterWithName("b").description("two").attributes( + key("foo").value("bravo")))).document( + "query-parameters-with-custom-descriptor-attributes", result(request)); + } + + @Test + public void queryParametersWithCustomAttributes() throws IOException { + this.snippet.expectQueryParameters("query-parameters-with-custom-attributes") + .withContents(startsWith(".The title")); + TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); + when(resolver.resolveTemplateResource("query-parameters")).thenReturn( + snippetResource("query-parameters-with-title")); + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( + resolver)); + request.addParameter("a", "bravo"); + request.addParameter("b", "bravo"); + new QueryParametersSnippet( + attributes(key("title").value("The title")), + Arrays.asList( + parameterWithName("a").description("one").attributes( + key("foo").value("alpha")), parameterWithName("b") + .description("two").attributes(key("foo").value("bravo")))) + .document("query-parameters-with-custom-attributes", result(request)); + } + + private FileSystemResource snippetResource(String name) { + return new FileSystemResource("src/test/resources/custom-snippet-templates/" + + name + ".snippet"); + } + +} diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/request/RequestDocumentationTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/request/RequestDocumentationTests.java deleted file mode 100644 index b193ad5c..00000000 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/request/RequestDocumentationTests.java +++ /dev/null @@ -1,252 +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.request; - -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.startsWith; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.springframework.restdocs.request.RequestDocumentation.documentPathParameters; -import static org.springframework.restdocs.request.RequestDocumentation.documentQueryParameters; -import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; -import static org.springframework.restdocs.snippet.Attributes.attributes; -import static org.springframework.restdocs.snippet.Attributes.key; -import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader; -import static org.springframework.restdocs.test.StubMvcResult.result; -import static org.springframework.restdocs.test.TestRequestBuilders.get; - -import java.io.IOException; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.springframework.core.io.FileSystemResource; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.restdocs.config.RestDocumentationContext; -import org.springframework.restdocs.snippet.SnippetGenerationException; -import org.springframework.restdocs.templates.TemplateEngine; -import org.springframework.restdocs.templates.TemplateResourceResolver; -import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; -import org.springframework.restdocs.test.ExpectedSnippet; - -/** - * Requests for {@link RequestDocumentation} - * - * @author Andy Wilkinson - */ -public class RequestDocumentationTests { - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - @Rule - public ExpectedSnippet snippet = new ExpectedSnippet(); - - @Test - public void undocumentedQueryParameter() throws IOException { - this.thrown.expect(SnippetGenerationException.class); - this.thrown - .expectMessage(equalTo("Query parameters with the following names were" - + " not documented: [a]")); - documentQueryParameters("undocumented-query-parameter", null).handle( - result(get("/").param("a", "alpha"))); - } - - @Test - public void missingQueryParameter() throws IOException { - this.thrown.expect(SnippetGenerationException.class); - this.thrown - .expectMessage(equalTo("Query parameters with the following names were" - + " not found in the request: [a]")); - documentQueryParameters("missing-query-parameter", null, - parameterWithName("a").description("one")).handle(result(get("/"))); - } - - @Test - public void undocumentedAndMissingQueryParameters() throws IOException { - this.thrown.expect(SnippetGenerationException.class); - this.thrown - .expectMessage(equalTo("Query parameters with the following names were" - + " not documented: [b]. Query parameters with the following" - + " names were not found in the request: [a]")); - documentQueryParameters("undocumented-and-missing-query-parameters", null, - parameterWithName("a").description("one")).handle( - result(get("/").param("b", "bravo"))); - } - - @Test - public void queryParameterSnippetFromRequestParameters() throws IOException { - this.snippet.expectQueryParameters("query-parameter-snippet-request-parameters") - .withContents( - tableWithHeader("Parameter", "Description").row("a", "one").row( - "b", "two")); - documentQueryParameters("query-parameter-snippet-request-parameters", null, - parameterWithName("a").description("one"), - parameterWithName("b").description("two")).handle( - result(get("/").param("a", "bravo").param("b", "bravo"))); - } - - @Test - public void queryParameterSnippetFromRequestUriQueryString() throws IOException { - this.snippet.expectQueryParameters( - "query-parameter-snippet-request-uri-query-string").withContents( - tableWithHeader("Parameter", "Description").row("a", "one").row("b", - "two")); - documentQueryParameters("query-parameter-snippet-request-uri-query-string", null, - parameterWithName("a").description("one"), - parameterWithName("b").description("two")).handle( - result(get("/?a=alpha&b=bravo").requestAttr( - RestDocumentationContext.class.getName(), - new RestDocumentationContext()))); - } - - @Test - public void queryParametersWithCustomDescriptorAttributes() throws IOException { - this.snippet.expectQueryParameters( - "query-parameters-with-custom-descriptor-attributes").withContents( - tableWithHeader("Parameter", "Description", "Foo").row("a", "one", - "alpha").row("b", "two", "bravo")); - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - when(resolver.resolveTemplateResource("query-parameters")) - .thenReturn( - new FileSystemResource( - "src/test/resources/custom-snippet-templates/query-parameters-with-extra-column.snippet")); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( - resolver)); - request.addParameter("a", "bravo"); - request.addParameter("b", "bravo"); - documentQueryParameters( - "query-parameters-with-custom-descriptor-attributes", - null, - parameterWithName("a").description("one").attributes( - key("foo").value("alpha")), - parameterWithName("b").description("two").attributes( - key("foo").value("bravo"))).handle(result(request)); - } - - @Test - public void queryParametersWithCustomAttributes() throws IOException { - this.snippet.expectQueryParameters("query-parameters-with-custom-attributes") - .withContents(startsWith(".The title")); - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - when(resolver.resolveTemplateResource("query-parameters")) - .thenReturn( - new FileSystemResource( - "src/test/resources/custom-snippet-templates/query-parameters-with-title.snippet")); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( - resolver)); - request.addParameter("a", "bravo"); - request.addParameter("b", "bravo"); - documentQueryParameters( - "query-parameters-with-custom-attributes", - attributes(key("title").value("The title")), - parameterWithName("a").description("one").attributes( - key("foo").value("alpha")), - parameterWithName("b").description("two").attributes( - key("foo").value("bravo"))).handle(result(request)); - - } - - @Test - public void undocumentedPathParameter() throws IOException { - this.thrown.expect(SnippetGenerationException.class); - this.thrown.expectMessage(equalTo("Path parameters with the following names were" - + " not documented: [a]")); - documentPathParameters("undocumented-path-parameter", null).handle( - result(get("/{a}/", "alpha"))); - } - - @Test - public void missingPathParameter() throws IOException { - this.thrown.expect(SnippetGenerationException.class); - this.thrown.expectMessage(equalTo("Path parameters with the following names were" - + " not found in the request: [a]")); - documentPathParameters("missing-path-parameter", null, - parameterWithName("a").description("one")).handle(result(get("/"))); - } - - @Test - public void undocumentedAndMissingPathParameters() throws IOException { - this.thrown.expect(SnippetGenerationException.class); - this.thrown.expectMessage(equalTo("Path parameters with the following names were" - + " not documented: [b]. Path parameters with the following" - + " names were not found in the request: [a]")); - documentPathParameters("undocumented-and-missing-path-parameters", null, - parameterWithName("a").description("one")).handle( - result(get("/{b}", "bravo"))); - } - - @Test - public void pathParameters() throws IOException { - this.snippet.expectPathParameters("path-parameters").withContents( - tableWithHeader("Parameter", "Description").row("a", "one").row("b", - "two")); - documentPathParameters("path-parameters", null, - parameterWithName("a").description("one"), - parameterWithName("b").description("two")).handle( - result(get("/{a}/{b}", "alpha", "banana").requestAttr( - RestDocumentationContext.class.getName(), - new RestDocumentationContext()))); - } - - @Test - public void pathParametersWithCustomDescriptorAttributes() throws IOException { - this.snippet.expectPathParameters( - "path-parameters-with-custom-descriptor-attributes").withContents( - tableWithHeader("Parameter", "Description", "Foo").row("a", "one", - "alpha").row("b", "two", "bravo")); - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - when(resolver.resolveTemplateResource("path-parameters")) - .thenReturn( - new FileSystemResource( - "src/test/resources/custom-snippet-templates/path-parameters-with-extra-column.snippet")); - documentPathParameters( - "path-parameters-with-custom-descriptor-attributes", - null, - parameterWithName("a").description("one").attributes( - key("foo").value("alpha")), - parameterWithName("b").description("two").attributes( - key("foo").value("bravo"))).handle( - result(get("{a}/{b}", "alpha", "bravo").requestAttr( - TemplateEngine.class.getName(), - new MustacheTemplateEngine(resolver)))); - } - - @Test - public void pathParametersWithCustomAttributes() throws IOException { - this.snippet.expectPathParameters("path-parameters-with-custom-attributes") - .withContents(startsWith(".The title")); - TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - when(resolver.resolveTemplateResource("path-parameters")) - .thenReturn( - new FileSystemResource( - "src/test/resources/custom-snippet-templates/path-parameters-with-title.snippet")); - documentPathParameters( - "path-parameters-with-custom-attributes", - attributes(key("title").value("The title")), - parameterWithName("a").description("one").attributes( - key("foo").value("alpha")), - parameterWithName("b").description("two").attributes( - key("foo").value("bravo"))).handle( - result(get("{a}/{b}", "alpha", "bravo").requestAttr( - TemplateEngine.class.getName(), - new MustacheTemplateEngine(resolver)))); - - } -} diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/snippet/StandardWriterResolverTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/snippet/StandardWriterResolverTests.java index 37d8fa44..c1882d27 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/snippet/StandardWriterResolverTests.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/snippet/StandardWriterResolverTests.java @@ -24,6 +24,7 @@ import static org.mockito.Mockito.mock; import java.io.File; import org.junit.Test; +import org.springframework.restdocs.snippet.StandardWriterResolver; import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver; /** diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/test/ExpectedSnippet.java b/spring-restdocs/src/test/java/org/springframework/restdocs/test/ExpectedSnippet.java index 5847424c..c1e95814 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/test/ExpectedSnippet.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/test/ExpectedSnippet.java @@ -26,12 +26,12 @@ import org.hamcrest.Matcher; import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; -import org.springframework.restdocs.snippet.SnippetWritingResultHandler; +import org.springframework.restdocs.snippet.TemplatedSnippet; import org.springframework.restdocs.test.SnippetMatchers.SnippetMatcher; /** * The {@code ExpectedSnippet} rule is used to verify that a - * {@link SnippetWritingResultHandler} has generated the expected snippet. + * {@link TemplatedSnippet} has generated the expected snippet. * * @author Andy Wilkinson */ diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/test/StubMvcResult.java b/spring-restdocs/src/test/java/org/springframework/restdocs/test/StubMvcResult.java index d772f7f0..284f24d8 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/test/StubMvcResult.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/test/StubMvcResult.java @@ -92,7 +92,7 @@ public class StubMvcResult implements MvcResult { this.request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(new StandardTemplateResourceResolver())); } - RestDocumentationContext context = new RestDocumentationContext(); + RestDocumentationContext context = new RestDocumentationContext(null); this.request.setAttribute(RestDocumentationContext.class.getName(), context); if (this.request.getAttribute(WriterResolver.class.getName()) == null) { this.request.setAttribute(WriterResolver.class.getName(), diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/test/TestRequestBuilders.java b/spring-restdocs/src/test/java/org/springframework/restdocs/test/TestRequestBuilders.java index de6d5df0..fa877f5b 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/test/TestRequestBuilders.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/test/TestRequestBuilders.java @@ -29,7 +29,8 @@ public class TestRequestBuilders { Object... urlVariables) { return org.springframework.restdocs.RestDocumentationRequestBuilders.get( urlTemplate, urlVariables).requestAttr( - RestDocumentationContext.class.getName(), new RestDocumentationContext()); + RestDocumentationContext.class.getName(), + new RestDocumentationContext(null)); } }