Add support for documenting body of a request, response or request part

Closes gh-318
Closes gh-319
This commit is contained in:
Andy Wilkinson
2016-10-31 12:46:36 +00:00
parent 1393182182
commit dec3727da1
31 changed files with 1160 additions and 95 deletions

View File

@@ -105,12 +105,28 @@ descriptors to a snippet that's preconfigured to ignore certain links. For examp
include::{examples-dir}/com/example/Hypermedia.java[tags=ignore-links]
----
[[documenting-your-api-request-response-payloads]]
=== Request and response payloads
In addition to the hypermedia-specific support <<documenting-your-api-hypermedia,described
above>>, support for general documentation of request and response payloads is also
provided. Consider the following payload:
provided.
By default, Spring REST Docs will automatically generate snippets for the body of the
request and the body of the response. These snippets are named `request-body.adoc` and
`response-body.adoc` respectively.
[[documenting-your-api-request-response-payloads-fields]]
==== Request and response fields
To provide more detailed documentation of a request or response payload, support for
documenting the payload's fields is provided.
Consider the following payload:
[source,json,indent=0]
----
@@ -122,7 +138,7 @@ provided. Consider the following payload:
}
----
It can be documented like this:
Its fields can be documented like this:
[source,java,indent=0,role="primary"]
.MockMvc
@@ -195,11 +211,11 @@ TIP: By default, Spring REST Docs will assume that the payload you are documenti
JSON. If you want to document an XML payload the content type of the request or response
must be compatible with `application/xml`.
[[documenting-your-api-request-response-payloads-json]]
==== JSON payloads
[[documenting-your-api-request-response-payloads-fields-json]]
===== Fields in JSON payloads
[[documenting-your-api-request-response-payloads-json-field-paths]]
===== JSON field paths
[[documenting-your-api-request-response-payloads-fields-json-field-paths]]
====== JSON field paths
JSON field paths use either dot notation or bracket notation. Dot notation uses '.' to
separate each key in the path; `a.b`, for example. Bracket notation wraps each key in
@@ -288,8 +304,8 @@ found in the following array:
[[documenting-your-api-request-response-payloads-json-field-types]]
===== JSON field types
[[documenting-your-api-request-response-payloads-fields-json-field-types]]
====== JSON field types
When a field is documented, Spring REST Docs will attempt to determine its type by
examining the payload. Seven different types are supported:
@@ -340,18 +356,18 @@ include::{examples-dir}/com/example/restassured/Payload.java[tags=explicit-type]
<1> Set the field's type to `string`.
[[documenting-your-api-request-response-payloads-xml]]
==== XML payloads
[[documenting-your-api-request-response-payloads-fields-xml]]
===== XML payloads
[[documenting-your-api-request-response-payloads-xml-field-paths]]
===== XML field paths
[[documenting-your-api-request-response-payloads-fields-xml-field-paths]]
====== XML field paths
XML field paths are described using XPath. `/` is used to descend into a child node.
[[documenting-your-api-request-response-payloads-xml-field-types]]
===== XML field types
[[documenting-your-api-request-response-payloads-fields-xml-field-types]]
====== XML field types
When documenting an XML payload, you must provide a type for the field using the
`type(Object)` method on `FieldDescriptor`. The result of the supplied type's `toString`
@@ -359,8 +375,8 @@ method will be used in the documentation.
[[documenting-your-api-request-response-payloads-reusing-field-descriptors]]
==== Reusing field descriptors
[[documenting-your-api-request-response-payloads-fields-reusing-field-descriptors]]
===== Reusing field descriptors
In addition to the general support for <<documenting-your-api-reusing-snippets,reusing
snippets>>, the request and response snippets allow additional descriptors to be
@@ -445,7 +461,10 @@ If a payload is large or structurally complex, it can be useful to document
individual sections of the payload. REST Docs allows you to do so by extracting a
subsection of the payload and then documenting it.
Consider the following JSON response payload:
[[documenting-your-api-request-response-payloads-subsections-body]]
===== Documenting a subsection of a request or response body
Consider the following JSON response body:
[source,json,indent=0]
----
@@ -463,13 +482,64 @@ Consider the following JSON response payload:
}
----
A snippet that documents the fields of the `temperature` object (`high` and `low`) can
be produced as follows:
A snippet that documents the `temperature` object can be produces as follows:
[source,java,indent=0,role="primary"]
.MockMvc
----
include::{examples-dir}/com/example/mockmvc/Payload.java[tags=beneath-path]
include::{examples-dir}/com/example/mockmvc/Payload.java[tags=body-subsection]
----
<1> Produce a snippet containing a subsection of the response body. Uses the static
`responseBody` and `beneathPath` methods on
`org.springframework.restdocs.payload.PayloadDocumentation`. To produce a snippet
for the request body, `requestBody` can be used in place of `responseBody`.
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/Payload.java[tags=body-subsection]
----
<1> Produce a snippet containing a subsection of the response body. Uses the static
`responseBody` and `beneathPath` methods on
`org.springframework.restdocs.payload.PayloadDocumentation`. To produce a snippet
for the request body, `requestBody` can be used in place of `responseBody`.
The result is a snippet with the following contents:
[source,json,indent=0]
----
{
"temperature": {
"high": 21.2,
"low": 14.8
}
}
----
To make the snippet's name distinct, an identifier for the subsection is included. By
default, this identifier is `beneath-${path}`. For example, the code above will result in
a snippet named `response-body-beneath-weather.temperature.adoc`. The identifier can
be customized using the `withSubsectionId(String)` method:
[source,java,indent=0]
----
include::{examples-dir}/com/example/Payload.java[tags=custom-subsection-id]
----
This example will result in a snippet named `request-body-temp.adoc`.
[[documenting-your-api-request-response-payloads-subsections-fields]]
===== Documenting the fields of a subsection of a request or response
As well as documenting a subsection of a request or response body, it's also possible to
document the fields in a particular subsection. A snippet that documents the fields of the `temperature` object (`high` and `low`) can be produced as follows:
[source,java,indent=0,role="primary"]
.MockMvc
----
include::{examples-dir}/com/example/mockmvc/Payload.java[tags=fields-subsection]
----
<1> Produce a snippet describing the fields in the subsection of the response payload
beneath the path `weather.temperature`. Uses the static `beneathPath` method on
@@ -479,7 +549,7 @@ include::{examples-dir}/com/example/mockmvc/Payload.java[tags=beneath-path]
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/Payload.java[tags=beneath-path]
include::{examples-dir}/com/example/restassured/Payload.java[tags=fields-subsection]
----
<1> Produce a snippet describing the fields in the subsection of the response payload
beneath the path `weather.temperature`. Uses the static `beneathPath` method on
@@ -490,15 +560,7 @@ The result is a snippet that contains a table describing the `high` and `low` fi
of `weather.temperature`. To make the snippet's name distinct, an identifier for the
subsection is included. By default, this identifier is `beneath-${path}`. For
example, the code above will result in a snippet named
`response-fields-beneath-weather.temperature.adoc`. The identifier can be customized using
the `withSubsectionId(String)` method:
[source,java,indent=0]
----
include::{examples-dir}/com/example/Payload.java[tags=custom-subsection-id]
----
This example will result in a snippet named `response-fields-temp.adoc`.
`response-fields-beneath-weather.temperature.adoc`.
@@ -679,12 +741,51 @@ above.
=== Request part payloads
The payload of a request part can be documented in much the same way as the
<<documenting-your-api-request-response-payloads,payload of a request>>:
<<documenting-your-api-request-response-payloads,payload of a request>> with support
for documenting a request part's body and its fields.
[[documenting-your-api-request-parts-payloads-fields]]
==== Documenting a request part's body
A snippet containing the body of a request part can be generated:
[source,java,indent=0,role="primary"]
.MockMvc
----
include::{examples-dir}/com/example/mockmvc/RequestPartPayload.java[tags=payload]
include::{examples-dir}/com/example/mockmvc/RequestPartPayload.java[tags=body]
----
<1> Configure Spring REST docs to produce a snippet containing the body of the
of the request part named `metadata`. Uses the static `requestPartBody` method on
`PayloadDocumentation`.
payload.
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/RequestPartPayload.java[tags=body]
----
<1> Configure Spring REST docs to produce a snippet containing the body of the request
part named `metadata`. Uses the static `requestPartBody` method on
`PayloadDocumentation`.
The result is a snippet `request-part-${part-name}-body.adoc` that contains the part's
body. For example, documenting a part named `metadata` will produce a snippet named
`request-part-metadata-body.adoc`.
[[documenting-your-api-request-parts-payloads-fields]]
==== Documenting a request part's fields
A request part's fields can be documented in much the same way as the fields of a request
or response:
[source,java,indent=0,role="primary"]
.MockMvc
----
include::{examples-dir}/com/example/mockmvc/RequestPartPayload.java[tags=fields]
----
<1> Configure Spring REST docs to produce a snippet describing the fields in the payload
of the request part named `metadata`. Uses the static `requestPartFields` method on
@@ -696,7 +797,7 @@ include::{examples-dir}/com/example/mockmvc/RequestPartPayload.java[tags=payload
[source,java,indent=0,role="secondary"]
.REST Assured
----
include::{examples-dir}/com/example/restassured/RequestPartPayload.java[tags=payload]
include::{examples-dir}/com/example/restassured/RequestPartPayload.java[tags=fields]
----
<1> Configure Spring REST docs to produce a snippet describing the fields in the payload
of the request part named `metadata`. Uses the static `requestPartFields` method on
@@ -930,6 +1031,13 @@ documented
| `http-response.adoc`
| Contains the HTTP response that was returned
| `request-body.adoc`
| Contains the body of the request that was sent
| `response-body.adoc`
| Contains the body of the response that was returned
|===
You can configure which snippets are produced by default. Please refer to the
@@ -1076,6 +1184,4 @@ and adds a title:
----
<1> Add a title to the table
<2> Add a new column named "Constraints"
<3> Include the descriptors' `constraints` attribute in each row of the table
<3> Include the descriptors' `constraints` attribute in each row of the table

View File

@@ -406,12 +406,14 @@ static `document` method on
<4> Invoke the root (`/`) of the service.
<5> Assert that the service produce the expected response.
By default, four snippets are written:
By default, six snippets are written:
* `<output-directory>/index/curl-request.adoc`
* `<output-directory>/index/http-request.adoc`
* `<output-directory>/index/http-response.adoc`
* `<output-directory>/index/httpie-request.adoc`
* `<output-directory>/index/request-body.adoc`
* `<output-directory>/index/response-body.adoc`
Refer to <<documenting-your-api>> for more information about these and other snippets
that can be produced by Spring REST Docs.

View File

@@ -20,7 +20,7 @@ import org.springframework.restdocs.payload.FieldDescriptor;
import static org.springframework.restdocs.payload.PayloadDocumentation.beneathPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseBody;
public class Payload {
@@ -35,9 +35,7 @@ public class Payload {
public void customSubsectionId() {
// tag::custom-subsection-id[]
responseFields(beneathPath("weather.temperature").withSubsectionId("temp"),
fieldWithPath("high").description(""),
fieldWithPath("low").description(""));
responseBody(beneathPath("weather.temperature").withSubsectionId("temp"));
// end::custom-subsection-id[]
}

View File

@@ -26,9 +26,10 @@ import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuild
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post;
import static org.springframework.restdocs.payload.PayloadDocumentation.beneathPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.subsectionWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseBody;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.subsectionWithPath;
import static org.springframework.restdocs.snippet.Attributes.attributes;
import static org.springframework.restdocs.snippet.Attributes.key;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -40,46 +41,50 @@ public class Payload {
public void response() throws Exception {
// tag::response[]
this.mockMvc.perform(get("/user/5").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(document("index", responseFields( // <1>
fieldWithPath("contact.email").description("The user's email address"), // <2>
fieldWithPath("contact.name").description("The user's name")))); // <3>
.andExpect(status().isOk())
.andDo(document("index",
responseFields( // <1>
fieldWithPath("contact.email")
.description("The user's email address"), // <2>
fieldWithPath("contact.name").description("The user's name")))); // <3>
// end::response[]
}
public void subsection() throws Exception {
// tag::subsection[]
this.mockMvc.perform(get("/user/5").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(document("index", responseFields( // <1>
subsectionWithPath("contact").description("The user's contact details")))); // <1>
.andExpect(status().isOk())
.andDo(document("index",
responseFields( // <1>
subsectionWithPath("contact")
.description("The user's contact details")))); // <1>
// end::subsection[]
}
public void explicitType() throws Exception {
this.mockMvc.perform(get("/user/5").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
// tag::explicit-type[]
.andDo(document("index", responseFields(
fieldWithPath("contact.email")
.type(JsonFieldType.STRING) // <1>
.description("The user's email address"))));
// end::explicit-type[]
.andExpect(status().isOk())
// tag::explicit-type[]
.andDo(document("index",
responseFields(
fieldWithPath("contact.email").type(JsonFieldType.STRING) // <1>
.description("The user's email address"))));
// end::explicit-type[]
}
public void constraints() throws Exception {
this.mockMvc.perform(post("/users/").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
// tag::constraints[]
.andDo(document("create-user", requestFields(
attributes(key("title").value("Fields for user creation")), // <1>
fieldWithPath("name").description("The user's name")
.attributes(key("constraints")
.value("Must not be null. Must not be empty")), // <2>
fieldWithPath("email").description("The user's email address")
.attributes(key("constraints")
.value("Must be a valid email address"))))); // <3>
// end::constraints[]
.andExpect(status().isOk())
// tag::constraints[]
.andDo(document("create-user", requestFields(
attributes(key("title").value("Fields for user creation")), // <1>
fieldWithPath("name").description("The user's name")
.attributes(key("constraints")
.value("Must not be null. Must not be empty")), // <2>
fieldWithPath("email").description("The user's email address")
.attributes(key("constraints")
.value("Must be a valid email address"))))); // <3>
// end::constraints[]
}
public void descriptorReuse() throws Exception {
@@ -89,27 +94,39 @@ public class Payload {
// tag::single-book[]
this.mockMvc.perform(get("/books/1").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(document("book", responseFields(book))); // <1>
.andExpect(status().isOk()).andDo(document("book", responseFields(book))); // <1>
// end::single-book[]
// tag::book-array[]
this.mockMvc.perform(get("/books").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(document("book", responseFields(
fieldWithPath("[]").description("An array of books")) // <1>
.andWithPrefix("[].", book))); // <2>
.andDo(document("book",
responseFields(
fieldWithPath("[]").description("An array of books")) // <1>
.andWithPrefix("[].", book))); // <2>
// end::book-array[]
}
public void subsectionBeneathPath() throws Exception {
// tag::beneath-path[]
public void fieldsSubsection() throws Exception {
// tag::fields-subsection[]
this.mockMvc.perform(get("/locations/1").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(document("location", responseFields(beneathPath("weather.temperature"), // <1>
fieldWithPath("high").description("The forecast high in degrees celcius"), // <2>
fieldWithPath("low").description("The forecast low in degrees celcius"))));
// end::beneath-path[]
.andDo(document("location",
responseFields(beneathPath("weather.temperature"), // <1>
fieldWithPath("high").description(
"The forecast high in degrees celcius"), // <2>
fieldWithPath("low")
.description("The forecast low in degrees celcius"))));
// end::fields-subsection[]
}
public void bodySubsection() throws Exception {
// tag::body-subsection[]
this.mockMvc.perform(get("/locations/1").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()).andDo(document("location",
responseBody(beneathPath("weather.temperature")))); // <1>
// end::body-subsection[]
}
}

View File

@@ -23,6 +23,7 @@ import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.fileUpload;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestPartBody;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestPartFields;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -30,8 +31,8 @@ public class RequestPartPayload {
private MockMvc mockMvc;
public void response() throws Exception {
// tag::payload[]
public void fields() throws Exception {
// tag::fields[]
MockMultipartFile image = new MockMultipartFile("image", "image.png", "image/png",
"<<png data>>".getBytes());
MockMultipartFile metadata = new MockMultipartFile("metadata", "",
@@ -42,7 +43,21 @@ public class RequestPartPayload {
.andExpect(status().isOk())
.andDo(document("image-upload", requestPartFields("metadata", // <1>
fieldWithPath("version").description("The version of the image")))); // <2>
// end::payload[]
// end::fields[]
}
public void body() throws Exception {
// tag::body[]
MockMultipartFile image = new MockMultipartFile("image", "image.png", "image/png",
"<<png data>>".getBytes());
MockMultipartFile metadata = new MockMultipartFile("metadata", "",
"application/json", "{ \"version\": \"1.0\"}".getBytes());
this.mockMvc.perform(fileUpload("/images").file(image).file(metadata)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(document("image-upload", requestPartBody("metadata"))); // <1>
// end::body[]
}
}

View File

@@ -26,6 +26,7 @@ import static org.hamcrest.CoreMatchers.is;
import static org.springframework.restdocs.payload.PayloadDocumentation.beneathPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseBody;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.subsectionWithPath;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document;
@@ -46,7 +47,7 @@ public class Payload {
.then().assertThat().statusCode(is(200));
// end::response[]
}
public void subsection() throws Exception {
// tag::subsection[]
RestAssured.given(this.spec).accept("application/json")
@@ -107,15 +108,24 @@ public class Payload {
// end::book-array[]
}
public void subsectionBeneathPath() throws Exception {
// tag::beneath-path[]
public void fieldsSubsection() throws Exception {
// tag::fields-subsection[]
RestAssured.given(this.spec).accept("application/json")
.filter(document("location", responseFields(beneathPath("weather.temperature"), // <1>
fieldWithPath("high").description("The forecast high in degrees celcius"), // <2>
fieldWithPath("low").description("The forecast low in degrees celcius"))))
.when().get("/locations/1")
.then().assertThat().statusCode(is(200));
// end::beneath-path[]
// end::fields-subsection[]
}
public void bodySubsection() throws Exception {
// tag::body-subsection[]
RestAssured.given(this.spec).accept("application/json")
.filter(document("location", responseBody(beneathPath("weather.temperature")))) // <1>
.when().get("/locations/1")
.then().assertThat().statusCode(is(200));
// end::body-subsection[]
}
}

View File

@@ -25,6 +25,7 @@ import com.jayway.restassured.specification.RequestSpecification;
import static org.hamcrest.CoreMatchers.is;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestPartBody;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestPartFields;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document;
@@ -32,8 +33,8 @@ public class RequestPartPayload {
private RequestSpecification spec;
public void response() throws Exception {
// tag::payload[]
public void fields() throws Exception {
// tag::fields[]
Map<String, String> metadata = new HashMap<>();
metadata.put("version", "1.0");
RestAssured.given(this.spec).accept("application/json")
@@ -42,7 +43,19 @@ public class RequestPartPayload {
.when().multiPart("image", new File("image.png"), "image/png")
.multiPart("metadata", metadata).post("images")
.then().assertThat().statusCode(is(200));
// end::payload[]
// end::fields[]
}
public void body() throws Exception {
// tag::body[]
Map<String, String> metadata = new HashMap<>();
metadata.put("version", "1.0");
RestAssured.given(this.spec).accept("application/json")
.filter(document("image-upload", requestPartBody("metadata"))) // <1>
.when().multiPart("image", new File("image.png"), "image/png")
.multiPart("metadata", metadata).post("images")
.then().assertThat().statusCode(is(200));
// end::body[]
}
}

View File

@@ -25,6 +25,7 @@ import org.springframework.restdocs.RestDocumentationContext;
import org.springframework.restdocs.cli.CliDocumentation;
import org.springframework.restdocs.generate.RestDocumentationGenerator;
import org.springframework.restdocs.http.HttpDocumentation;
import org.springframework.restdocs.payload.PayloadDocumentation;
import org.springframework.restdocs.snippet.Snippet;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateFormats;
@@ -42,7 +43,8 @@ public abstract class SnippetConfigurer<PARENT, TYPE>
private List<Snippet> defaultSnippets = new ArrayList<>(Arrays.asList(
CliDocumentation.curlRequest(), CliDocumentation.httpieRequest(),
HttpDocumentation.httpRequest(), HttpDocumentation.httpResponse()));
HttpDocumentation.httpRequest(), HttpDocumentation.httpResponse(),
PayloadDocumentation.requestBody(), PayloadDocumentation.responseBody()));
/**
* The default encoding for documentation snippets.

View File

@@ -0,0 +1,125 @@
/*
* Copyright 2014-2016 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 java.io.IOException;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import org.springframework.http.MediaType;
import org.springframework.restdocs.operation.Operation;
import org.springframework.restdocs.snippet.ModelCreationException;
import org.springframework.restdocs.snippet.TemplatedSnippet;
/**
* Abstract {@link TemplatedSnippet} subclass that provides a base for snippets that
* document a RESTful resource's request or response body.
*
* @author Andy Wilkinson
*/
public abstract class AbstractBodySnippet extends TemplatedSnippet {
private final PayloadSubsectionExtractor<?> subsectionExtractor;
/**
* Creates a new {@code AbstractBodySnippet} that will produce a snippet named
* {@code <type>-body} using a template named {@code <type>-body}. The snippet will
* contain the subsection of the body extracted by the given
* {@code subsectionExtractor}. The given {@code attributes} will be included in the
* model during template rendering
*
* @param type the type of the body
* @param subsectionExtractor the subsection extractor
* @param attributes the attributes
*/
protected AbstractBodySnippet(String type,
PayloadSubsectionExtractor<?> subsectionExtractor,
Map<String, Object> attributes) {
this(type, type, subsectionExtractor, attributes);
}
/**
* Creates a new {@code AbstractBodySnippet} that will produce a snippet named
* {@code <name>-body} using a template named {@code <type>-body}. The snippet will
* contain the subsection of the body extracted by the given
* {@code subsectionExtractor}. The given {@code attributes} will be included in the
* model during template rendering
*
* @param name the name of the snippet
* @param type the type of the body
* @param subsectionExtractor the subsection extractor
* @param attributes the attributes
*/
protected AbstractBodySnippet(String name, String type,
PayloadSubsectionExtractor<?> subsectionExtractor,
Map<String, Object> attributes) {
super(name + "-body"
+ (subsectionExtractor != null
? "-" + subsectionExtractor.getSubsectionId() : ""),
type + "-body", attributes);
this.subsectionExtractor = subsectionExtractor;
}
@Override
protected Map<String, Object> createModel(Operation operation) {
try {
MediaType contentType = getContentType(operation);
byte[] content = getContent(operation);
if (this.subsectionExtractor != null) {
content = this.subsectionExtractor.extractSubsection(content,
contentType);
}
Charset charset = extractCharset(contentType);
String body = charset != null ? new String(content, charset)
: new String(content);
Map<String, Object> model = new HashMap<>();
model.put("body", body);
return model;
}
catch (IOException ex) {
throw new ModelCreationException(ex);
}
}
private Charset extractCharset(MediaType contentType) {
if (contentType == null) {
return null;
}
return contentType.getCharset();
}
/**
* Returns the content of the request or response extracted from the given
* {@code operation}.
*
* @param operation The operation
* @return The content
* @throws IOException if the content cannot be extracted
*/
protected abstract byte[] getContent(Operation operation) throws IOException;
/**
* Returns the content type of the request or response extracted from the given
* {@code operation}.
*
* @param operation The operation
* @return The content type
*/
protected abstract MediaType getContentType(Operation operation);
}

View File

@@ -1443,6 +1443,164 @@ public abstract class PayloadDocumentation {
true);
}
/**
* Returns a {@code Snippet} that will document the body of the API operation's
* request payload.
*
* @return the snippet that will document the request body
*/
public static RequestBodySnippet requestBody() {
return new RequestBodySnippet();
}
/**
* Returns a {@code Snippet} that will document the body of the API operation's
* request payload. The given attributes will be made available during snippet
* generation.
*
* @param attributes the attributes
* @return the snippet that will document the request body
*/
public static RequestBodySnippet requestBody(Map<String, Object> attributes) {
return new RequestBodySnippet(attributes);
}
/**
* Returns a {@code Snippet} that will document a subsection of the body of the API
* operation's request payload. The subsection will be extracted using the given
* {@code subsectionExtractor}.
*
* @param subsectionExtractor the subsection extractor
* @return the snippet that will document the request body subsection
*/
public static RequestBodySnippet requestBody(
PayloadSubsectionExtractor<?> subsectionExtractor) {
return new RequestBodySnippet(subsectionExtractor);
}
/**
* Returns a {@code Snippet} that will document a subsection of the body of the API
* operation's request payload. The subsection will be extracted using the given
* {@code subsectionExtractor}. The given attributes will be made available during
* snippet generation.
*
* @param subsectionExtractor the subsection extractor
* @param attributes the attributes
* @return the snippet that will document the request body subsection
*/
public static RequestBodySnippet requestBody(
PayloadSubsectionExtractor<?> subsectionExtractor,
Map<String, Object> attributes) {
return new RequestBodySnippet(subsectionExtractor, attributes);
}
/**
* Returns a {@code Snippet} that will document the body of the API operation's
* response payload.
*
* @return the snippet that will document the response body
*/
public static ResponseBodySnippet responseBody() {
return new ResponseBodySnippet();
}
/**
* Returns a {@code Snippet} that will document the body of the API operation's
* response payload. The given attributes will be made available during snippet
* generation.
*
* @param attributes the attributes
* @return the snippet that will document the response body
*/
public static ResponseBodySnippet responseBody(Map<String, Object> attributes) {
return new ResponseBodySnippet(attributes);
}
/**
* Returns a {@code Snippet} that will document a subsection of the body of the API
* operation's response payload. The subsection will be extracted using the given
* {@code subsectionExtractor}.
*
* @param subsectionExtractor the subsection extractor
* @return the snippet that will document the response body subsection
*/
public static ResponseBodySnippet responseBody(
PayloadSubsectionExtractor<?> subsectionExtractor) {
return new ResponseBodySnippet(subsectionExtractor);
}
/**
* Returns a {@code Snippet} that will document a subsection of the body of the API
* operation's response payload. The subsection will be extracted using the given
* {@code subsectionExtractor}. The given attributes will be made available during
* snippet generation.
*
* @param subsectionExtractor the subsection extractor
* @param attributes the attributes
* @return the snippet that will document the response body subsection
*/
public static ResponseBodySnippet responseBody(
PayloadSubsectionExtractor<?> subsectionExtractor,
Map<String, Object> attributes) {
return new ResponseBodySnippet(subsectionExtractor, attributes);
}
/**
* Returns a {@code Snippet} that will document the body of specified part of the API
* operation's request payload.
*
* @param partName the name of the request part
* @return the snippet that will document the response body
*/
public static RequestPartBodySnippet requestPartBody(String partName) {
return new RequestPartBodySnippet(partName);
}
/**
* Returns a {@code Snippet} that will document the body of specified part of the API
* operation's request payload. The given attributes will be made available during
* snippet generation.
*
* @param partName the name of the request part
* @param attributes the attributes
* @return the snippet that will document the response body
*/
public static RequestPartBodySnippet requestPartBody(String partName,
Map<String, Object> attributes) {
return new RequestPartBodySnippet(partName, attributes);
}
/**
* Returns a {@code Snippet} that will document a subsection of the body of specified
* part of the API operation's request payload. The subsection will be extracted using
* the given {@code subsectionExtractor}.
*
* @param partName the name of the request part
* @param subsectionExtractor the subsection extractor
* @return the snippet that will document the response body
*/
public static RequestPartBodySnippet requestPartBody(String partName,
PayloadSubsectionExtractor<?> subsectionExtractor) {
return new RequestPartBodySnippet(partName, subsectionExtractor);
}
/**
* Returns a {@code Snippet} that will document a subsection of the body of specified
* part of the API operation's request payload. The subsection will be extracted using
* the given {@code subsectionExtractor}. The given attributes will be made available
* during snippet generation.
*
* @param partName the name of the request part
* @param subsectionExtractor the subsection extractor
* @param attributes the attributes
* @return the snippet that will document the response body
*/
public static RequestPartBodySnippet requestPartBody(String partName,
PayloadSubsectionExtractor<?> subsectionExtractor,
Map<String, Object> attributes) {
return new RequestPartBodySnippet(partName, subsectionExtractor, attributes);
}
/**
* Creates a copy of the given {@code descriptors} with the given {@code pathPrefix}
* applied to their paths.

View File

@@ -0,0 +1,84 @@
/*
* Copyright 2014-2016 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 java.io.IOException;
import java.util.Map;
import org.springframework.http.MediaType;
import org.springframework.restdocs.operation.Operation;
import org.springframework.restdocs.snippet.Snippet;
/**
* A {@link Snippet} that documents the body of a request.
*
* @author Andy Wilkinson
*/
public class RequestBodySnippet extends AbstractBodySnippet {
/**
* Creates a new {@code RequestBodySnippet}.
*/
public RequestBodySnippet() {
this(null, null);
}
/**
* Creates a new {@code RequestBodySnippet} that will document the subsection of the
* request body extracted by the given {@code subsectionExtractor}.
*
* @param subsectionExtractor the subsection extractor
*/
public RequestBodySnippet(PayloadSubsectionExtractor<?> subsectionExtractor) {
this(subsectionExtractor, null);
}
/**
* Creates a new {@code RequestBodySnippet} with the given additional
* {@code attributes} that will be included in the model during template rendering.
*
* @param attributes The additional attributes
*/
public RequestBodySnippet(Map<String, Object> attributes) {
this(null, attributes);
}
/**
* Creates a new {@code RequestBodySnippet} that will document the subsection of the
* request body extracted by the given {@code subsectionExtractor}. The given
* additional {@code attributes} that will be included in the model during template
* rendering.
*
* @param subsectionExtractor the subsection extractor
* @param attributes The additional attributes
*/
public RequestBodySnippet(PayloadSubsectionExtractor<?> subsectionExtractor,
Map<String, Object> attributes) {
super("request", subsectionExtractor, attributes);
}
@Override
protected byte[] getContent(Operation operation) throws IOException {
return operation.getRequest().getContent();
}
@Override
protected MediaType getContentType(Operation operation) {
return operation.getRequest().getHeaders().getContentType();
}
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright 2014-2016 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 java.io.IOException;
import java.util.Map;
import org.springframework.http.MediaType;
import org.springframework.restdocs.operation.Operation;
import org.springframework.restdocs.operation.OperationRequestPart;
import org.springframework.restdocs.snippet.Snippet;
import org.springframework.restdocs.snippet.SnippetException;
/**
* A {@link Snippet} that documents the body of a request part.
*
* @author Andy Wilkinson
*/
public class RequestPartBodySnippet extends AbstractBodySnippet {
private final String partName;
/**
* Creates a new {@code RequestPartBodySnippet} that will document the body of the
* request part with the given {@code partName}.
*
* @param partName the name of the request part
*/
public RequestPartBodySnippet(String partName) {
this(partName, null, null);
}
/**
* Creates a new {@code RequestPartBodySnippet} that will document the subsection of
* the body of the request part with the given {@code partName} extracted by the given
* {@code subsectionExtractor}.
*
* @param partName the name of the request part
* @param subsectionExtractor the subsection extractor
*/
public RequestPartBodySnippet(String partName,
PayloadSubsectionExtractor<?> subsectionExtractor) {
this(partName, subsectionExtractor, null);
}
/**
* Creates a new {@code RequestPartBodySnippet} that will document the body of the
* request part with the given {@code partName}. The given additional
* {@code attributes} will be included in the model during template rendering.
*
* @param partName the name of the request part
* @param attributes the additional attributes
*/
public RequestPartBodySnippet(String partName, Map<String, Object> attributes) {
this(partName, null, attributes);
}
/**
* Creates a new {@code RequestPartBodySnippet} that will document the body of the
* request part with the given {@code partName}. The subsection of the body extracted
* by the given {@code subsectionExtractor} will be documented and the given
* additional {@code attributes} that will be included in the model during template
* rendering.
*
* @param partName the name of the request part
* @param subsectionExtractor the subsection extractor
* @param attributes The additional attributes
*/
public RequestPartBodySnippet(String partName,
PayloadSubsectionExtractor<?> subsectionExtractor,
Map<String, Object> attributes) {
super("request-part-" + partName, "request-part", subsectionExtractor,
attributes);
this.partName = partName;
}
@Override
protected byte[] getContent(Operation operation) throws IOException {
return findPart(operation).getContent();
}
@Override
protected MediaType getContentType(Operation operation) {
return findPart(operation).getHeaders().getContentType();
}
private OperationRequestPart findPart(Operation operation) {
for (OperationRequestPart candidate : operation.getRequest().getParts()) {
if (candidate.getName().equals(this.partName)) {
return candidate;
}
}
throw new SnippetException("A request part named '" + this.partName
+ "' was not found in the request");
}
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright 2014-2016 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 java.io.IOException;
import java.util.Map;
import org.springframework.http.MediaType;
import org.springframework.restdocs.operation.Operation;
import org.springframework.restdocs.snippet.Snippet;
/**
* A {@link Snippet} that documents the body of a response.
*
* @author Andy Wilkinson
*/
public class ResponseBodySnippet extends AbstractBodySnippet {
/**
* Creates a new {@code ResponseBodySnippet}.
*/
public ResponseBodySnippet() {
this(null, null);
}
/**
* Creates a new {@code ResponseBodySnippet} that will document the subsection of the
* response body extracted by the given {@code subsectionExtractor}.
*
* @param subsectionExtractor the subsection extractor
*/
public ResponseBodySnippet(PayloadSubsectionExtractor<?> subsectionExtractor) {
this(subsectionExtractor, null);
}
/**
* Creates a new {@code ResponseBodySnippet} with the given additional
* {@code attributes} that will be included in the model during template rendering.
*
* @param attributes The additional attributes
*/
public ResponseBodySnippet(Map<String, Object> attributes) {
this(null, attributes);
}
/**
* Creates a new {@code ResponseBodySnippet} that will document the subsection of the
* response body extracted by the given {@code subsectionExtractor}. The given
* additional {@code attributes} that will be included in the model during template
* rendering.
*
* @param subsectionExtractor the subsection extractor
* @param attributes The additional attributes
*/
public ResponseBodySnippet(PayloadSubsectionExtractor<?> subsectionExtractor,
Map<String, Object> attributes) {
super("response", subsectionExtractor, attributes);
}
@Override
protected byte[] getContent(Operation operation) throws IOException {
return operation.getResponse().getContent();
}
@Override
protected MediaType getContentType(Operation operation) {
return operation.getResponse().getHeaders().getContentType();
}
}

View File

@@ -0,0 +1,4 @@
[source,options="nowrap"]
----
{{body}}
----

View File

@@ -0,0 +1,4 @@
[source,options="nowrap"]
----
{{body}}
----

View File

@@ -0,0 +1,4 @@
[source,options="nowrap"]
----
{{body}}
----

View File

@@ -68,7 +68,11 @@ public abstract class AbstractSnippetTests {
}
public CodeBlockMatcher<?> codeBlock(String language) {
return SnippetMatchers.codeBlock(this.templateFormat, language);
return this.codeBlock(language, null);
}
public CodeBlockMatcher<?> codeBlock(String language, String options) {
return SnippetMatchers.codeBlock(this.templateFormat, language, options);
}
public TableMatcher<?> tableWithHeader(String... headers) {

View File

@@ -31,6 +31,8 @@ import org.springframework.restdocs.cli.HttpieRequestSnippet;
import org.springframework.restdocs.generate.RestDocumentationGenerator;
import org.springframework.restdocs.http.HttpRequestSnippet;
import org.springframework.restdocs.http.HttpResponseSnippet;
import org.springframework.restdocs.payload.RequestBodySnippet;
import org.springframework.restdocs.payload.ResponseBodySnippet;
import org.springframework.restdocs.snippet.Snippet;
import org.springframework.restdocs.snippet.StandardWriterResolver;
import org.springframework.restdocs.snippet.WriterResolver;
@@ -76,7 +78,9 @@ public class RestDocumentationConfigurerTests {
contains(instanceOf(CurlRequestSnippet.class),
instanceOf(HttpieRequestSnippet.class),
instanceOf(HttpRequestSnippet.class),
instanceOf(HttpResponseSnippet.class)));
instanceOf(HttpResponseSnippet.class),
instanceOf(RequestBodySnippet.class),
instanceOf(ResponseBodySnippet.class)));
assertThat(configuration, hasEntry(equalTo(SnippetConfiguration.class.getName()),
instanceOf(SnippetConfiguration.class)));
SnippetConfiguration snippetConfiguration = (SnippetConfiguration) configuration
@@ -138,7 +142,9 @@ public class RestDocumentationConfigurerTests {
contains(instanceOf(CurlRequestSnippet.class),
instanceOf(HttpieRequestSnippet.class),
instanceOf(HttpRequestSnippet.class),
instanceOf(HttpResponseSnippet.class), equalTo(snippet)));
instanceOf(HttpResponseSnippet.class),
instanceOf(RequestBodySnippet.class),
instanceOf(ResponseBodySnippet.class), equalTo(snippet)));
}
@Test

View File

@@ -0,0 +1,95 @@
/*
* Copyright 2014-2016 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 java.io.IOException;
import org.junit.Test;
import org.springframework.restdocs.AbstractSnippetTests;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.springframework.restdocs.payload.PayloadDocumentation.beneathPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestPartBody;
import static org.springframework.restdocs.snippet.Attributes.attributes;
import static org.springframework.restdocs.snippet.Attributes.key;
/**
* Tests for {@link RequestPartBodySnippet}.
*
* @author Andy Wilkinson
*/
public class RequestBodyPartSnippetTests extends AbstractSnippetTests {
public RequestBodyPartSnippetTests(String name, TemplateFormat templateFormat) {
super(name, templateFormat);
}
@Test
public void requestPartWithBody() throws IOException {
this.snippets.expect("request-part-one-body")
.withContents(codeBlock(null, "nowrap").content("some content"));
requestPartBody("one").document(this.operationBuilder.request("http://localhost")
.part("one", "some content".getBytes()).build());
}
@Test
public void requestPartWithNoBody() throws IOException {
this.snippets.expect("request-part-one-body")
.withContents(codeBlock(null, "nowrap").content(""));
requestPartBody("one").document(this.operationBuilder.request("http://localhost")
.part("one", new byte[0]).build());
}
@Test
public void subsectionOfRequestPartBody() throws IOException {
this.snippets.expect("request-part-one-body-beneath-a.b")
.withContents(codeBlock(null, "nowrap").content("{\"c\":5}"));
requestPartBody("one", beneathPath("a.b"))
.document(this.operationBuilder.request("http://localhost")
.part("one", "{\"a\":{\"b\":{\"c\":5}}}".getBytes()).build());
}
@Test
public void customSnippetAttributes() throws IOException {
this.snippets.expect("request-part-one-body")
.withContents(codeBlock("json", "nowrap").content("{\"a\":\"alpha\"}"));
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("request-part-body"))
.willReturn(snippetResource("request-part-body-with-language"));
requestPartBody("one",
attributes(
key("language").value("json")))
.document(
this.operationBuilder
.attribute(TemplateEngine.class.getName(),
new MustacheTemplateEngine(
resolver))
.request("http://localhost")
.part("one",
"{\"a\":\"alpha\"}".getBytes())
.build());
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright 2014-2016 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 java.io.IOException;
import org.junit.Test;
import org.springframework.restdocs.AbstractSnippetTests;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.springframework.restdocs.payload.PayloadDocumentation.beneathPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestBody;
import static org.springframework.restdocs.snippet.Attributes.attributes;
import static org.springframework.restdocs.snippet.Attributes.key;
/**
* Tests for {@link RequestBodySnippet}.
*
* @author Andy Wilkinson
*/
public class RequestBodySnippetTests extends AbstractSnippetTests {
public RequestBodySnippetTests(String name, TemplateFormat templateFormat) {
super(name, templateFormat);
}
@Test
public void requestWithBody() throws IOException {
this.snippets.expect("request-body")
.withContents(codeBlock(null, "nowrap").content("some content"));
requestBody().document(this.operationBuilder.request("http://localhost")
.content("some content").build());
}
@Test
public void requestWithNoBody() throws IOException {
this.snippets.expect("request-body")
.withContents(codeBlock(null, "nowrap").content(""));
requestBody().document(this.operationBuilder.request("http://localhost").build());
}
@Test
public void subsectionOfRequestBody() throws IOException {
this.snippets.expect("request-body-beneath-a.b")
.withContents(codeBlock(null, "nowrap").content("{\"c\":5}"));
requestBody(beneathPath("a.b"))
.document(this.operationBuilder.request("http://localhost")
.content("{\"a\":{\"b\":{\"c\":5}}}").build());
}
@Test
public void customSnippetAttributes() throws IOException {
this.snippets.expect("request-body")
.withContents(codeBlock("json", "nowrap").content("{\"a\":\"alpha\"}"));
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("request-body"))
.willReturn(snippetResource("request-body-with-language"));
requestBody(
attributes(
key("language").value("json")))
.document(
this.operationBuilder
.attribute(TemplateEngine.class.getName(),
new MustacheTemplateEngine(
resolver))
.request("http://localhost")
.content("{\"a\":\"alpha\"}").build());
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright 2014-2016 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 java.io.IOException;
import org.junit.Test;
import org.springframework.restdocs.AbstractSnippetTests;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.springframework.restdocs.payload.PayloadDocumentation.beneathPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseBody;
import static org.springframework.restdocs.snippet.Attributes.attributes;
import static org.springframework.restdocs.snippet.Attributes.key;
/**
* Tests for {@link ResponseBodySnippet}.
*
* @author Andy Wilkinson
*/
public class ResponseBodySnippetTests extends AbstractSnippetTests {
public ResponseBodySnippetTests(String name, TemplateFormat templateFormat) {
super(name, templateFormat);
}
@Test
public void responseWithBody() throws IOException {
this.snippets.expect("response-body")
.withContents(codeBlock(null, "nowrap").content("some content"));
PayloadDocumentation.responseBody().document(
this.operationBuilder.response().content("some content").build());
}
@Test
public void responseWithNoBody() throws IOException {
this.snippets.expect("response-body")
.withContents(codeBlock(null, "nowrap").content(""));
PayloadDocumentation.responseBody()
.document(this.operationBuilder.response().build());
}
@Test
public void subsectionOfResponseBody() throws IOException {
this.snippets.expect("response-body-beneath-a.b")
.withContents(codeBlock(null, "nowrap").content("{\"c\":5}"));
responseBody(beneathPath("a.b")).document(this.operationBuilder.response()
.content("{\"a\":{\"b\":{\"c\":5}}}").build());
}
@Test
public void customSnippetAttributes() throws IOException {
this.snippets.expect("response-body")
.withContents(codeBlock("json", "nowrap").content("{\"a\":\"alpha\"}"));
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
given(resolver.resolveTemplateResource("response-body"))
.willReturn(snippetResource("response-body-with-language"));
responseBody(
attributes(
key("language").value("json")))
.document(
this.operationBuilder
.attribute(TemplateEngine.class.getName(),
new MustacheTemplateEngine(
resolver))
.response().content("{\"a\":\"alpha\"}")
.build());
}
}

View File

@@ -95,6 +95,15 @@ public final class SnippetMatchers {
return new MarkdownCodeBlockMatcher(language);
}
@SuppressWarnings({ "rawtypes" })
public static CodeBlockMatcher<?> codeBlock(TemplateFormat format, String language,
String options) {
if ("adoc".equals(format.getFileExtension())) {
return new AsciidoctorCodeBlockMatcher(language, options);
}
return new MarkdownCodeBlockMatcher(language);
}
private static abstract class AbstractSnippetContentMatcher
extends BaseMatcher<String> {
@@ -186,7 +195,7 @@ public final class SnippetMatchers {
protected AsciidoctorCodeBlockMatcher(String language, String options) {
super(TemplateFormats.asciidoctor());
this.addLine("[source," + language
this.addLine("[source" + (language == null ? "" : "," + language)
+ (options == null ? "" : ",options=\"" + options + "\"") + "]");
this.addLine("----");
this.addLine("----");
@@ -204,7 +213,7 @@ public final class SnippetMatchers {
protected MarkdownCodeBlockMatcher(String language) {
super(TemplateFormats.markdown());
this.addLine("```" + language);
this.addLine("```" + (language == null ? "" : language));
this.addLine("```");
}

View File

@@ -0,0 +1,4 @@
[source,{{language}},options="nowrap"]
----
{{body}}
----

View File

@@ -0,0 +1,4 @@
[source,{{language}},options="nowrap"]
----
{{body}}
----

View File

@@ -0,0 +1,4 @@
[source,{{language}},options="nowrap"]
----
{{body}}
----

View File

@@ -0,0 +1,3 @@
```{{language}}
{{body}}
```