diff --git a/docs/src/docs/asciidoc/documenting-your-api.adoc b/docs/src/docs/asciidoc/documenting-your-api.adoc
index d1a27442..96117ffd 100644
--- a/docs/src/docs/asciidoc/documenting-your-api.adoc
+++ b/docs/src/docs/asciidoc/documenting-your-api.adoc
@@ -575,6 +575,12 @@ prevent it from appearing in the generated snippet while avoiding the failure de
above.
+[[documenting-your-api-request-parts]]
+=== Request parts content
+
+If you need to document the content of request part that holds Json data, you should use`requestPartsFields`.
+It acts exactly as [[documenting-your-api-request-parts]] but in order to use it, you must specify the part name.
+
[[documenting-your-api-http-headers]]
=== HTTP headers
diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/PayloadDocumentation.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/PayloadDocumentation.java
index c4a57c25..fed726f1 100644
--- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/PayloadDocumentation.java
+++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/PayloadDocumentation.java
@@ -148,6 +148,56 @@ public abstract class PayloadDocumentation {
return new RequestFieldsSnippet(descriptors);
}
+ /**
+ * Returns a {@code Snippet} that will document the fields of the specified {@code part}
+ * of the API operations's request payload. The fields will be documented using the
+ * given {@code descriptors}.
+ *
+ * If a field is present in the request payload, but is not documented by one of the
+ * descriptors, a failure will occur when the snippet 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.
+ *
+ * If you do not want to document a field, a field descriptor can be marked as
+ * {@link FieldDescriptor#ignored}. This will prevent it from appearing in the
+ * generated snippet while avoiding the failure described above.
+ *
+ * @param part the part name
+ * @param descriptors the descriptions of the request payload's fields
+ * @return the snippet that will document the fields
+ * @see #fieldWithPath(String)
+ */
+ public static RequestPartFieldsSnippet requestPartFields(String part, FieldDescriptor... descriptors) {
+ return requestPartFields(part, Arrays.asList(descriptors));
+ }
+
+ /**
+ * Returns a {@code Snippet} that will document the fields of the specified {@code part}
+ * of the API operations's request payload. The fields will be documented using the given
+ * {@code descriptors}.
+ *
+ * If a field is present in the request payload, but is not documented by one of the
+ * descriptors, a failure will occur when the snippet 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.
+ *
+ * If you do not want to document a field, a field descriptor can be marked as
+ * {@link FieldDescriptor#ignored}. This will prevent it from appearing in the
+ * generated snippet while avoiding the failure described above.
+ *
+ * @param part the part name
+ * @param descriptors the descriptions of the request payload's fields
+ * @return the snippet that will document the fields
+ * @see #fieldWithPath(String)
+ */
+ public static RequestPartFieldsSnippet requestPartFields(String part, List descriptors) {
+ return new RequestPartFieldsSnippet(part, descriptors);
+ }
+
/**
* Returns a {@code Snippet} that will document the fields of the API operations's
* request payload. The fields will be documented using the given {@code descriptors}.
diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/RequestPartFieldsSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/RequestPartFieldsSnippet.java
new file mode 100644
index 00000000..4de8f848
--- /dev/null
+++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/RequestPartFieldsSnippet.java
@@ -0,0 +1,191 @@
+/*
+ * 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.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+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 fields in a request.
+ *
+ * @author Mathieu Pousse
+ * @see PayloadDocumentation#requestPartFields(String, FieldDescriptor...)
+ */
+public class RequestPartFieldsSnippet extends AbstractFieldsSnippet {
+
+ private final String partName;
+
+ /**
+ * Creates a new {@code RequestPartFieldsSnippet} that will document the fields in the
+ * request part using the given {@code descriptors}. Undocumented fields will trigger a
+ * failure.
+ *
+ * @param partName the part name
+ * @param descriptors the descriptors
+ */
+ protected RequestPartFieldsSnippet(String partName, List descriptors) {
+ this(partName, descriptors, null, false);
+ }
+
+ /**
+ * Creates a new {@code RequestPartFieldsSnippet} that will document the fields in the
+ * request part using the given {@code descriptors}. If {@code ignoreUndocumentedFields} is
+ * {@code true}, undocumented fields will be ignored and will not trigger a failure.
+ *
+ * @param partName the part name
+ * @param descriptors the descriptors
+ * @param ignoreUndocumentedFields whether undocumented fields should be ignored
+ */
+ protected RequestPartFieldsSnippet(String partName, List descriptors,
+ boolean ignoreUndocumentedFields) {
+ this(partName, descriptors, null, ignoreUndocumentedFields);
+ }
+
+ /**
+ * Creates a new {@code RequestFieldsSnippet} that will document the fields in the
+ * request using the given {@code descriptors}. The given {@code attributes} will be
+ * included in the model during template rendering. Undocumented fields will trigger a
+ * failure.
+ *
+ * @param partName the part name
+ * @param descriptors the descriptors
+ * @param attributes the additional attributes
+ */
+ protected RequestPartFieldsSnippet(String partName, List descriptors,
+ Map attributes) {
+ this(partName, descriptors, attributes, false);
+ }
+
+ /**
+ * Creates a new {@code RequestFieldsSnippet} that will document the fields in the
+ * request using the given {@code descriptors}. The given {@code attributes} will be
+ * included in the model during template rendering. If
+ * {@code ignoreUndocumentedFields} is {@code true}, undocumented fields will be
+ * ignored and will not trigger a failure.
+ *
+ * @param partName the part name
+ * @param descriptors the descriptors
+ * @param attributes the additional attributes
+ * @param ignoreUndocumentedFields whether undocumented fields should be ignored
+ */
+ protected RequestPartFieldsSnippet(String partName, List descriptors,
+ Map attributes, boolean ignoreUndocumentedFields) {
+ super("request", descriptors, attributes, ignoreUndocumentedFields);
+ this.partName = partName;
+ }
+
+ @Override
+ protected MediaType getContentType(Operation operation) {
+ for (OperationRequestPart candidate : operation.getRequest().getParts()) {
+ if (candidate.getName().equals(this.partName)) {
+ return candidate.getHeaders().getContentType();
+ }
+ }
+ throw new SnippetException(missingPartErrorMessage());
+ }
+
+ @Override
+ protected byte[] getContent(Operation operation) throws IOException {
+ for (OperationRequestPart candidate : operation.getRequest().getParts()) {
+ if (candidate.getName().equals(this.partName)) {
+ return candidate.getContent();
+ }
+ }
+ throw new SnippetException(missingPartErrorMessage());
+ }
+
+ /**
+ * Prepare the error message because the requested part was not found.
+ *
+ * @return see description
+ */
+ protected String missingPartErrorMessage() {
+ return "Request parts with the following names were not found in the request: " + this.partName;
+ }
+
+ /**
+ * Returns a new {@code RequestPartFieldsSnippet} configured with this snippet's
+ * attributes and its descriptors combined with the given
+ * {@code additionalDescriptors}.
+ *
+ * @param additionalDescriptors the additional descriptors
+ * @return the new snippet
+ */
+ public final RequestPartFieldsSnippet and(FieldDescriptor... additionalDescriptors) {
+ return andWithPrefix("", additionalDescriptors);
+ }
+
+ /**
+ * Returns a new {@code RequestPartFieldsSnippet} configured with this snippet's
+ * attributes and its descriptors combined with the given
+ * {@code additionalDescriptors}.
+ *
+ * @param additionalDescriptors the additional descriptors
+ * @return the new snippet
+ */
+ public final RequestPartFieldsSnippet and(List additionalDescriptors) {
+ return andWithPrefix("", additionalDescriptors);
+ }
+
+ /**
+ * Returns a new {@code RequestFieldsSnippet} configured with this snippet's
+ * attributes and its descriptors combined with the given
+ * {@code additionalDescriptors}. The given {@code pathPrefix} is applied to the path
+ * of each additional descriptor.
+ *
+ * @param pathPrefix the prefix to apply to the additional descriptors
+ * @param additionalDescriptors the additional descriptors
+ * @return the new snippet
+ */
+ public final RequestPartFieldsSnippet andWithPrefix(String pathPrefix,
+ FieldDescriptor... additionalDescriptors) {
+ List combinedDescriptors = new ArrayList<>();
+ combinedDescriptors.addAll(getFieldDescriptors());
+ combinedDescriptors.addAll(
+ PayloadDocumentation.applyPathPrefix(pathPrefix, Arrays.asList(additionalDescriptors)));
+ return new RequestPartFieldsSnippet(this.partName, combinedDescriptors, this.getAttributes());
+ }
+
+ /**
+ * Returns a new {@code RequestFieldsSnippet} configured with this snippet's
+ * attributes and its descriptors combined with the given
+ * {@code additionalDescriptors}. The given {@code pathPrefix} is applied to the path
+ * of each additional descriptor.
+ *
+ * @param pathPrefix the prefix to apply to the additional descriptors
+ * @param additionalDescriptors the additional descriptors
+ * @return the new snippet
+ */
+ public final RequestPartFieldsSnippet andWithPrefix(String pathPrefix,
+ List additionalDescriptors) {
+ List combinedDescriptors = new ArrayList<>(
+ getFieldDescriptors());
+ combinedDescriptors.addAll(
+ PayloadDocumentation.applyPathPrefix(pathPrefix, additionalDescriptors));
+ return new RequestPartFieldsSnippet(this.partName, combinedDescriptors, this.getAttributes());
+ }
+
+}
diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestPartsFieldsSnippetFailureTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestPartsFieldsSnippetFailureTests.java
new file mode 100644
index 00000000..a025514c
--- /dev/null
+++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestPartsFieldsSnippetFailureTests.java
@@ -0,0 +1,82 @@
+/*
+ * 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.Arrays;
+import java.util.Collections;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import org.springframework.restdocs.snippet.SnippetException;
+import org.springframework.restdocs.templates.TemplateFormats;
+import org.springframework.restdocs.test.ExpectedSnippet;
+import org.springframework.restdocs.test.OperationBuilder;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
+
+/**
+ * Tests for failures when rendering {@link RequestPartFieldsSnippet} due to missing or
+ * undocumented fields.
+ *
+ * @author Mathieu Pousse
+ */
+public class RequestPartsFieldsSnippetFailureTests {
+
+ @Rule
+ public ExpectedSnippet snippet = new ExpectedSnippet(TemplateFormats.asciidoctor());
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void undocumentedRequestPartField() throws IOException {
+ this.thrown.expect(SnippetException.class);
+ this.thrown.expectMessage(startsWith(
+ "The following parts of the payload were not" + " documented:"));
+ new RequestPartFieldsSnippet("part", Collections.emptyList())
+ .document(new OperationBuilder("undocumented-request-field",
+ this.snippet.getOutputDirectory()).request("http://localhost")
+ .part("part", "{\"a\": 5}".getBytes()).build());
+ }
+
+ @Test
+ public void missingRequestPartField() throws IOException {
+ this.thrown.expect(SnippetException.class);
+ this.thrown.expectMessage(startsWith(
+ "The following parts of the payload were not" + " documented:"));
+ new RequestPartFieldsSnippet("part", Arrays.asList(fieldWithPath("b").description("one")))
+ .document(new OperationBuilder("undocumented-request-field",
+ this.snippet.getOutputDirectory()).request("http://localhost")
+ .part("part", "{\"a\": 5}".getBytes()).build());
+ }
+
+ @Test
+ public void missingRequestPart() throws IOException {
+ this.thrown.expect(SnippetException.class);
+ this.thrown.expectMessage(equalTo("Request parts with the following names were not found in the request: another"));
+ new RequestPartFieldsSnippet("another", Arrays.asList(fieldWithPath("a.b").description("one")))
+ .document(new OperationBuilder("missing-request-fields",
+ this.snippet.getOutputDirectory()).request("http://localhost")
+ .part("part", "{\"a\": {\"b\": 5}}".getBytes()).build());
+ }
+
+}
diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestPartsFieldsSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestPartsFieldsSnippetTests.java
new file mode 100644
index 00000000..8dbff585
--- /dev/null
+++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestPartsFieldsSnippetTests.java
@@ -0,0 +1,56 @@
+/*
+ * 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.Arrays;
+
+import org.junit.Test;
+
+import org.springframework.restdocs.AbstractSnippetTests;
+import org.springframework.restdocs.templates.TemplateFormat;
+
+import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
+
+/**
+ * Tests for {@link RequestPartFieldsSnippet}.
+ *
+ * @author Mathieu Pousse
+ */
+public class RequestPartsFieldsSnippetTests extends AbstractSnippetTests {
+
+ public RequestPartsFieldsSnippetTests(String name, TemplateFormat templateFormat) {
+ super(name, templateFormat);
+ }
+
+ @Test
+ public void mapRequestWithFields() throws IOException {
+ this.snippet.expectRequestFields("map-request-parts-with-fields")
+ .withContents(tableWithHeader("Path", "Type", "Description")
+ .row("`a.b`", "`Number`", "one").row("`a.c`", "`String`", "two")
+ .row("`a`", "`Object`", "three"));
+
+ new RequestPartFieldsSnippet("part", Arrays.asList(fieldWithPath("a.b").description("one"),
+ fieldWithPath("a.c").description("two"),
+ fieldWithPath("a").description("three")))
+ .document(operationBuilder("map-request-parts-with-fields")
+ .request("http://localhost")
+ .part("part", "{\"a\": {\"b\": 5, \"c\": \"charlie\"}}".getBytes())
+ .build());
+ }
+
+}