diff --git a/docs/src/docs/asciidoc/documenting-your-api.adoc b/docs/src/docs/asciidoc/documenting-your-api.adoc index 96117ffd..4b3d58fa 100644 --- a/docs/src/docs/asciidoc/documenting-your-api.adoc +++ b/docs/src/docs/asciidoc/documenting-your-api.adoc @@ -415,7 +415,7 @@ include::{examples-dir}/com/example/mockmvc/RequestParameters.java[tags=request- ---- <1> Perform a `GET` request with two parameters, `page` and `per_page` in the query string. -<2> Configure Spring REST Docs to produce a snippet describing the request's parameters. +<2> Configure Spring REST Docs to produce a snippet describing the request's parameters. Uses the static `requestParameters` method on `org.springframework.restdocs.request.RequestDocumentation`. <3> Document the `page` parameter. Uses the static `parameterWithName` method on @@ -575,12 +575,58 @@ prevent it from appearing in the generated snippet while avoiding the failure de above. -[[documenting-your-api-request-parts]] -=== Request parts content +[[documenting-your-api-request-parts-payloads]] +=== Request part payloads -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. +The payload of a request part can be documented in much the same way as the +<>: +[source,java,indent=0,role="primary"] +.MockMvc +---- +include::{examples-dir}/com/example/mockmvc/RequestPartPayload.java[tags=payload] +---- +<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 + `PayloadDocumentation`. + payload. +<2> Expect a field with the path `version`. Uses the static `fieldWithPath` method on + `org.springframework.restdocs.payload.PayloadDocumentation`. + +[source,java,indent=0,role="secondary"] +.REST Assured +---- +include::{examples-dir}/com/example/restassured/RequestPartPayload.java[tags=payload] +---- +<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 + `PayloadDocumentation`. +<2> Expect a field with the path `version`. Uses the static `fieldWithPath` method on + `org.springframework.restdocs.payload.PayloadDocumentation`. + +The result is a snippet that contains a table describing the part's fields. This snippet +is named `request-part-${part-name}-fields.adoc`. For example, documenting a part named +`metadata` will produce a snippet named `request-part-metadata-fields.adoc`. + +When documenting fields, the test will fail if an undocumented field is found in the +payload of the part. Similarly, the test will also fail if a documented field is not found +in the payload of the part and the field has not been marked as optional. 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, you can mark it as ignored. This will prevent it +from appearing in the generated snippet while avoiding the failure described above. + +Fields can also be documented in a relaxed mode where any undocumented fields will not +cause a test failure. To do so, use the `relaxedRequestPartFields` method on +`org.springframework.restdocs.payload.PayloadDocumentation`. This can be useful when +documenting a particular scenario where you only want to focus on a subset of the payload +of the part. + +For further information on describing fields, documenting payloads that use XML, +and more please refer to the +<>. [[documenting-your-api-http-headers]] === HTTP headers diff --git a/docs/src/test/java/com/example/mockmvc/RequestPartPayload.java b/docs/src/test/java/com/example/mockmvc/RequestPartPayload.java new file mode 100644 index 00000000..56d6ae85 --- /dev/null +++ b/docs/src/test/java/com/example/mockmvc/RequestPartPayload.java @@ -0,0 +1,48 @@ +/* + * 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 com.example.mockmvc; + +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; +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.requestPartFields; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +public class RequestPartPayload { + + private MockMvc mockMvc; + + public void response() throws Exception { + // tag::payload[] + MockMultipartFile image = new MockMultipartFile("image", "image.png", "image/png", + "<>".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", requestPartFields("metadata", // <1> + fieldWithPath("version").description("The version of the image")))); // <2> + // end::payload[] + } + +} diff --git a/docs/src/test/java/com/example/restassured/RequestPartPayload.java b/docs/src/test/java/com/example/restassured/RequestPartPayload.java new file mode 100644 index 00000000..5dc8eda5 --- /dev/null +++ b/docs/src/test/java/com/example/restassured/RequestPartPayload.java @@ -0,0 +1,48 @@ +/* + * 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 com.example.restassured; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +import com.jayway.restassured.RestAssured; +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.requestPartFields; +import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document; + +public class RequestPartPayload { + + private RequestSpecification spec; + + public void response() throws Exception { + // tag::payload[] + Map metadata = new HashMap<>(); + metadata.put("version", "1.0"); + RestAssured.given(this.spec).accept("application/json") + .filter(document("image-upload", requestPartFields("metadata", // <1> + fieldWithPath("version").description("The version of the image")))) // <2> + .when().multiPart("image", new File("image.png"), "image/png") + .multiPart("metadata", metadata).post("images") + .then().assertThat().statusCode(is(200)); + // end::payload[] + } + +} diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/AbstractFieldsSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/AbstractFieldsSnippet.java index 16f7ba61..ae6fb3fe 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/AbstractFieldsSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/AbstractFieldsSnippet.java @@ -65,10 +65,11 @@ public abstract class AbstractFieldsSnippet extends TemplatedSnippet { /** * Creates a new {@code AbstractFieldsSnippet} that will produce a snippet named - * {@code -fields}. The fields will be documented using the given - * {@code descriptors} and 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. + * {@code -fields} using a template named {@code -fields}. The fields will + * be documented using the given {@code descriptors} and 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 type the type of the fields * @param descriptors the field descriptors @@ -77,7 +78,27 @@ public abstract class AbstractFieldsSnippet extends TemplatedSnippet { */ protected AbstractFieldsSnippet(String type, List descriptors, Map attributes, boolean ignoreUndocumentedFields) { - super(type + "-fields", attributes); + this(type, type, descriptors, attributes, ignoreUndocumentedFields); + } + + /** + * Creates a new {@code AbstractFieldsSnippet} that will produce a snippet named + * {@code -fields} using a template named {@code -fields}. The fields will + * be documented using the given {@code descriptors} and 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 name the name of the snippet + * @param type the type of the fields + * @param descriptors the field descriptors + * @param attributes the additional attributes + * @param ignoreUndocumentedFields whether undocumented fields should be ignored + */ + protected AbstractFieldsSnippet(String name, String type, + List descriptors, Map attributes, + boolean ignoreUndocumentedFields) { + super(name + "-fields", type + "-fields", attributes); for (FieldDescriptor descriptor : descriptors) { Assert.notNull(descriptor.getPath(), "Field descriptors must have a path"); if (!descriptor.isIgnored()) { 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 fed726f1..2e1d8ea2 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,56 +148,6 @@ 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}. @@ -318,6 +268,190 @@ public abstract class PayloadDocumentation { return new RequestFieldsSnippet(descriptors, attributes, true); } + /** + * 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 part, 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 + * part, 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 part'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 part, 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 + * part, 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 part'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 specified + * {@code part} of the API operations's request payload. The fields will be documented + * using the given {@code descriptors}. + *

+ * If a field is documented, is not marked as optional, and is not present in the + * request, a failure will occur. Any undocumented fields will be ignored. + * + * @param part the part name + * @param descriptors the descriptions of the request part's fields + * @return the snippet that will document the fields + * @see #fieldWithPath(String) + */ + public static RequestPartFieldsSnippet relaxedRequestPartFields(String part, + FieldDescriptor... descriptors) { + return relaxedRequestPartFields(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 documented, is not marked as optional, and is not present in the + * request, a failure will occur. Any undocumented fields will be ignored. + * + * @param part the part name + * @param descriptors the descriptions of the request part's fields + * @return the snippet that will document the fields + * @see #fieldWithPath(String) + */ + public static RequestPartFieldsSnippet relaxedRequestPartFields(String part, + List descriptors) { + return new RequestPartFieldsSnippet(part, descriptors, true); + } + + /** + * 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} and the given {@code attributes} will be + * available during snippet generation. + *

+ * If a field is present in the request part, 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 + * part, 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 attributes the attributes + * @param descriptors the descriptions of the request part's fields + * @return the snippet that will document the fields + * @see #fieldWithPath(String) + */ + public static RequestPartFieldsSnippet requestPartFields(String part, + Map attributes, 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} and the given {@code attributes} will be + * available during snippet generation. + *

+ * If a field is present in the request part, 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 + * part, 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 attributes the attributes + * @param descriptors the descriptions of the request part's fields + * @return the snippet that will document the fields + * @see #fieldWithPath(String) + */ + public static RequestPartFieldsSnippet requestPartFields(String part, + Map attributes, List descriptors) { + return new RequestPartFieldsSnippet(part, 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} and the given {@code attributes} will be + * available during snippet generation. + *

+ * If a field is documented, is not marked as optional, and is not present in the + * request, a failure will occur. Any undocumented fields will be ignored. + * + * @param part the part name + * @param attributes the attributes + * @param descriptors the descriptions of the request part's fields + * @return the snippet that will document the fields + * @see #fieldWithPath(String) + */ + public static RequestPartFieldsSnippet relaxedRequestPartFields(String part, + Map attributes, FieldDescriptor... descriptors) { + return relaxedRequestPartFields(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} and the given {@code attributes} will be + * available during snippet generation. + *

+ * If a field is documented, is not marked as optional, and is not present in the + * request, a failure will occur. Any undocumented fields will be ignored. + * + * @param part the part name + * @param attributes the attributes + * @param descriptors the descriptions of the request part's fields + * @return the snippet that will document the fields + * @see #fieldWithPath(String) + */ + public static RequestPartFieldsSnippet relaxedRequestPartFields(String part, + Map attributes, List descriptors) { + return new RequestPartFieldsSnippet(part, descriptors, true); + } + /** * Returns a {@code Snippet} that will document the fields of the API operation's * response 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 index 4de8f848..f07923de 100644 --- 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 @@ -29,10 +29,12 @@ import org.springframework.restdocs.snippet.Snippet; import org.springframework.restdocs.snippet.SnippetException; /** - * A {@link Snippet} that documents the fields in a request. + * A {@link Snippet} that documents the fields in a request part. * * @author Mathieu Pousse + * @author Andy Wilkinson * @see PayloadDocumentation#requestPartFields(String, FieldDescriptor...) + * @see PayloadDocumentation#requestPartFields(String, List) */ public class RequestPartFieldsSnippet extends AbstractFieldsSnippet { @@ -40,27 +42,29 @@ public class RequestPartFieldsSnippet extends AbstractFieldsSnippet { /** * 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. + * request part using the given {@code descriptors}. Undocumented fields will trigger + * a failure. * - * @param partName the part name + * @param partName the part name * @param descriptors the descriptors */ - protected RequestPartFieldsSnippet(String partName, List 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. + * 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 partName the part name + * @param descriptors the descriptors * @param ignoreUndocumentedFields whether undocumented fields should be ignored */ protected RequestPartFieldsSnippet(String partName, List descriptors, - boolean ignoreUndocumentedFields) { + boolean ignoreUndocumentedFields) { this(partName, descriptors, null, ignoreUndocumentedFields); } @@ -70,12 +74,12 @@ public class RequestPartFieldsSnippet extends AbstractFieldsSnippet { * included in the model during template rendering. Undocumented fields will trigger a * failure. * - * @param partName the part name + * @param partName the part name * @param descriptors the descriptors - * @param attributes the additional attributes + * @param attributes the additional attributes */ protected RequestPartFieldsSnippet(String partName, List descriptors, - Map attributes) { + Map attributes) { this(partName, descriptors, attributes, false); } @@ -86,44 +90,36 @@ public class RequestPartFieldsSnippet extends AbstractFieldsSnippet { * {@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 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); + Map attributes, boolean ignoreUndocumentedFields) { + super("request-part-" + partName, "request-part", 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()); + return findPart(operation).getHeaders().getContentType(); } @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()); + return findPart(operation).getContent(); } - /** - * 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; + 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"); } /** @@ -146,7 +142,8 @@ public class RequestPartFieldsSnippet extends AbstractFieldsSnippet { * @param additionalDescriptors the additional descriptors * @return the new snippet */ - public final RequestPartFieldsSnippet and(List additionalDescriptors) { + public final RequestPartFieldsSnippet and( + List additionalDescriptors) { return andWithPrefix("", additionalDescriptors); } @@ -156,17 +153,18 @@ public class RequestPartFieldsSnippet extends AbstractFieldsSnippet { * {@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 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) { + 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()); + combinedDescriptors.addAll(PayloadDocumentation.applyPathPrefix(pathPrefix, + Arrays.asList(additionalDescriptors))); + return new RequestPartFieldsSnippet(this.partName, combinedDescriptors, + this.getAttributes()); } /** @@ -175,17 +173,18 @@ public class RequestPartFieldsSnippet extends AbstractFieldsSnippet { * {@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 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 additionalDescriptors) { List combinedDescriptors = new ArrayList<>( getFieldDescriptors()); combinedDescriptors.addAll( PayloadDocumentation.applyPathPrefix(pathPrefix, additionalDescriptors)); - return new RequestPartFieldsSnippet(this.partName, combinedDescriptors, this.getAttributes()); + return new RequestPartFieldsSnippet(this.partName, combinedDescriptors, + this.getAttributes()); } } diff --git a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/default-request-part-fields.snippet b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/default-request-part-fields.snippet new file mode 100644 index 00000000..46cd43fe --- /dev/null +++ b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/default-request-part-fields.snippet @@ -0,0 +1,10 @@ +|=== +|Path|Type|Description + +{{#fields}} +|{{#tableCellContent}}`{{path}}`{{/tableCellContent}} +|{{#tableCellContent}}`{{type}}`{{/tableCellContent}} +|{{#tableCellContent}}{{description}}{{/tableCellContent}} + +{{/fields}} +|=== \ No newline at end of file diff --git a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/default-request-part-fields.snippet b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/default-request-part-fields.snippet new file mode 100644 index 00000000..27a4e437 --- /dev/null +++ b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/default-request-part-fields.snippet @@ -0,0 +1,5 @@ +Path | Type | Description +---- | ---- | ----------- +{{#fields}} +`{{path}}` | `{{type}}` | {{description}} +{{/fields}} \ No newline at end of file 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/RequestPartFieldsSnippetFailureTests.java similarity index 62% rename from spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestPartsFieldsSnippetFailureTests.java rename to spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestPartFieldsSnippetFailureTests.java index a025514c..03cf666e 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestPartsFieldsSnippetFailureTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestPartFieldsSnippetFailureTests.java @@ -26,7 +26,6 @@ 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; @@ -38,11 +37,13 @@ import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWit * undocumented fields. * * @author Mathieu Pousse + * @author Andy Wilkinson */ -public class RequestPartsFieldsSnippetFailureTests { +public class RequestPartFieldsSnippetFailureTests { @Rule - public ExpectedSnippet snippet = new ExpectedSnippet(TemplateFormats.asciidoctor()); + public OperationBuilder operationBuilder = new OperationBuilder( + TemplateFormats.asciidoctor()); @Rule public ExpectedException thrown = ExpectedException.none(); @@ -50,33 +51,33 @@ public class RequestPartsFieldsSnippetFailureTests { @Test public void undocumentedRequestPartField() throws IOException { this.thrown.expect(SnippetException.class); - this.thrown.expectMessage(startsWith( - "The following parts of the payload were not" + " documented:")); + 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()); + .document(this.operationBuilder.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()); + this.thrown.expectMessage( + startsWith("The following parts of the payload were not documented:")); + new RequestPartFieldsSnippet("part", + Arrays.asList(fieldWithPath("b").description("one"))) + .document(this.operationBuilder.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()); + this.thrown.expectMessage( + equalTo("A request part named 'another' was not found in the request")); + new RequestPartFieldsSnippet("another", + Arrays.asList(fieldWithPath("a.b").description("one"))) + .document(this.operationBuilder.request("http://localhost") + .part("part", "{\"a\": {\"b\": 5}}".getBytes()).build()); } } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestPartFieldsSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestPartFieldsSnippetTests.java new file mode 100644 index 00000000..0d4c7c32 --- /dev/null +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestPartFieldsSnippetTests.java @@ -0,0 +1,118 @@ +/* + * 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.Test; + +import org.springframework.restdocs.AbstractSnippetTests; +import org.springframework.restdocs.operation.Operation; +import org.springframework.restdocs.templates.TemplateFormat; + +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; + +/** + * Tests for {@link RequestPartFieldsSnippet}. + * + * @author Mathieu Pousse + * @author Andy Wilkinson + */ +public class RequestPartFieldsSnippetTests extends AbstractSnippetTests { + + public RequestPartFieldsSnippetTests(String name, TemplateFormat templateFormat) { + super(name, templateFormat); + } + + @Test + public void mapRequestPartFields() throws IOException { + this.snippets.expectRequestPartFields("one") + .withContents(tableWithHeader("Path", "Type", "Description") + .row("`a.b`", "`Number`", "one").row("`a.c`", "`String`", "two") + .row("`a`", "`Object`", "three")); + + new RequestPartFieldsSnippet("one", + Arrays.asList(fieldWithPath("a.b").description("one"), + fieldWithPath("a.c").description("two"), fieldWithPath("a") + .description("three"))) + .document( + this.operationBuilder + .request("http://localhost") + .part("one", + "{\"a\": {\"b\": 5, \"c\": \"charlie\"}}" + .getBytes()) + .build()); + } + + @Test + public void multipleRequestParts() throws IOException { + this.snippets.expectRequestPartFields("one"); + this.snippets.expectRequestPartFields("two"); + Operation operation = this.operationBuilder.request("http://localhost") + .part("one", "{}".getBytes()).and().part("two", "{}".getBytes()).build(); + new RequestPartFieldsSnippet("one", Collections.emptyList()) + .document(operation); + new RequestPartFieldsSnippet("two", Collections.emptyList()) + .document(operation); + } + + @Test + public void allUndocumentedRequestPartFieldsCanBeIgnored() throws IOException { + this.snippets.expectRequestPartFields("one") + .withContents(tableWithHeader("Path", "Type", "Description").row("`b`", + "`Number`", "Field b")); + new RequestPartFieldsSnippet("one", + Arrays.asList(fieldWithPath("b").description("Field b")), true) + .document(this.operationBuilder.request("http://localhost") + .part("one", "{\"a\": 5, \"b\": 4}".getBytes()).build()); + } + + @Test + public void additionalDescriptors() throws IOException { + this.snippets.expectRequestPartFields("one") + .withContents(tableWithHeader("Path", "Type", "Description") + .row("`a.b`", "`Number`", "one").row("`a.c`", "`String`", "two") + .row("`a`", "`Object`", "three")); + + PayloadDocumentation + .requestPartFields("one", fieldWithPath("a.b").description("one"), + fieldWithPath("a.c").description("two")) + .and(fieldWithPath("a").description("three")) + .document(this.operationBuilder.request("http://localhost") + .part("one", "{\"a\": {\"b\": 5, \"c\": \"charlie\"}}".getBytes()) + .build()); + } + + @Test + public void prefixedAdditionalDescriptors() throws IOException { + this.snippets.expectRequestPartFields("one") + .withContents(tableWithHeader("Path", "Type", "Description") + .row("`a`", "`Object`", "one").row("`a.b`", "`Number`", "two") + .row("`a.c`", "`String`", "three")); + + PayloadDocumentation + .requestPartFields("one", fieldWithPath("a").description("one")) + .andWithPrefix("a.", fieldWithPath("b").description("two"), + fieldWithPath("c").description("three")) + .document(this.operationBuilder.request("http://localhost") + .part("one", "{\"a\": {\"b\": 5, \"c\": \"charlie\"}}".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 deleted file mode 100644 index 8dbff585..00000000 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestPartsFieldsSnippetTests.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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()); - } - -} diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/test/ExpectedSnippets.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/test/ExpectedSnippets.java index f57c5b95..3a174532 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/test/ExpectedSnippets.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/test/ExpectedSnippets.java @@ -81,6 +81,10 @@ public class ExpectedSnippets extends OperationTestRule { return expect("request-fields"); } + public ExpectedSnippet expectRequestPartFields(String partName) { + return expect("request-part-" + partName + "-fields"); + } + public ExpectedSnippet expectResponseFields() { return expect("response-fields"); }