Add support for documenting request part payload fields

See gh-270
This commit is contained in:
Mathieu POUSSE
2016-06-23 17:28:26 +02:00
committed by Andy Wilkinson
parent 4f19294220
commit 13e745a9a9
5 changed files with 385 additions and 0 deletions

View File

@@ -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

View File

@@ -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}.
* <p>
* 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.
* <p>
* 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}.
* <p>
* 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.
* <p>
* 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<FieldDescriptor> 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}.

View File

@@ -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<FieldDescriptor> 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<FieldDescriptor> 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<FieldDescriptor> descriptors,
Map<String, Object> 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<FieldDescriptor> descriptors,
Map<String, Object> 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<FieldDescriptor> 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<FieldDescriptor> 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<FieldDescriptor> additionalDescriptors) {
List<FieldDescriptor> combinedDescriptors = new ArrayList<>(
getFieldDescriptors());
combinedDescriptors.addAll(
PayloadDocumentation.applyPathPrefix(pathPrefix, additionalDescriptors));
return new RequestPartFieldsSnippet(this.partName, combinedDescriptors, this.getAttributes());
}
}

View File

@@ -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.<FieldDescriptor>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());
}
}

View File

@@ -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());
}
}