Update field snippets to no longer document whole subsection by default
Previously, when a field was documented it would implicitly document the whole subsection of the payload identified by that field. This could lead to users inadvertently failing to document part of the payload. Arguably, this was a bug as it violated REST Docs' principle of producing accurate, detail documentation. However, fixing it requires a breaking change as people may also be relying on this behaviour. A balance needed to be struck so the fix is being made in a minor release. This commit introduces a new subsectionWithPath method which returns a SubsectionDescriptor; a specialisation of FieldDescriptor. Users that were intentionally relying on the old behaviour will have to replace some usage of fieldWithPath with subsectionWithPath instead. Users who were unintentionally relying on the old behaviour will have to add some additional descriptors produced using fieldWithPath and will receive more accurate documentation in return. Closes gh-274
This commit is contained in:
@@ -110,7 +110,19 @@ include::{examples-dir}/com/example/Hypermedia.java[tags=ignore-links]
|
||||
|
||||
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. For example:
|
||||
provided. Consider the following payload:
|
||||
|
||||
[source,json,indent=0]
|
||||
----
|
||||
{
|
||||
"contact": {
|
||||
"name": "Jane Doe",
|
||||
"email": "jane.doe@example.com"
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
It can be documented like this:
|
||||
|
||||
[source,java,indent=0,role="primary"]
|
||||
.MockMvc
|
||||
@@ -120,9 +132,9 @@ include::{examples-dir}/com/example/mockmvc/Payload.java[tags=response]
|
||||
<1> Configure Spring REST docs to produce a snippet describing the fields in the response
|
||||
payload. To document a request `requestFields` can be used. Both are static methods on
|
||||
`org.springframework.restdocs.payload.PayloadDocumentation`.
|
||||
<2> Expect a field with the path `contact`. Uses the static `fieldWithPath` method on
|
||||
`org.springframework.restdocs.payload.PayloadDocumentation`.
|
||||
<3> Expect a field with the path `contact.email`.
|
||||
<2> Expect a field with the path `contact.email`. Uses the static `fieldWithPath` method
|
||||
on `org.springframework.restdocs.payload.PayloadDocumentation`.
|
||||
<3> Expect a field with the path `contact.name`.
|
||||
|
||||
[source,java,indent=0,role="secondary"]
|
||||
.REST Assured
|
||||
@@ -132,9 +144,9 @@ include::{examples-dir}/com/example/restassured/Payload.java[tags=response]
|
||||
<1> Configure Spring REST docs to produce a snippet describing the fields in the response
|
||||
payload. To document a request `requestFields` can be used. Both are static methods on
|
||||
`org.springframework.restdocs.payload.PayloadDocumentation`.
|
||||
<2> Expect a field with the path `contact`. Uses the static `fieldWithPath` method on
|
||||
<2> Expect a field with the path `contact.email`. Uses the static `fieldWithPath` method on
|
||||
`org.springframework.restdocs.payload.PayloadDocumentation`.
|
||||
<3> Expect a field with the path `contact.email`.
|
||||
<3> Expect a field with the path `contact.name`.
|
||||
|
||||
The result is a snippet that contains a table describing the fields. For requests this
|
||||
snippet is named `request-fields.adoc`. For responses this snippet is named
|
||||
@@ -142,12 +154,36 @@ snippet is named `request-fields.adoc`. For responses this snippet is named
|
||||
|
||||
When documenting fields, the test will fail if an undocumented field is found in the
|
||||
payload. Similarly, the test will also fail if a documented field is not found in the
|
||||
payload 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.
|
||||
payload and the field has not been marked as optional.
|
||||
|
||||
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.
|
||||
If you don't want to provide detailed documentation for all of the fields, an entire
|
||||
subsection of a payload can be documented. For example:
|
||||
|
||||
[source,java,indent=0,role="primary"]
|
||||
.MockMvc
|
||||
----
|
||||
include::{examples-dir}/com/example/mockmvc/Payload.java[tags=subsection]
|
||||
----
|
||||
<1> Document the subsection with the path `contact`. `contact.email` and `contact.name`
|
||||
are now seen has having also been documented. Uses the static `subsectionWithPath`
|
||||
method on `org.springframework.restdocs.payload.PayloadDocumentation`.
|
||||
|
||||
[source,java,indent=0,role="secondary"]
|
||||
.REST Assured
|
||||
----
|
||||
include::{examples-dir}/com/example/restassured/Payload.java[tags=subsection]
|
||||
----
|
||||
<1> Document the subsection with the path `contact`. `contact.email` and `contact.name`
|
||||
are now seen has having also been documented. Uses the static `subsectionWithPath`
|
||||
method on `org.springframework.restdocs.payload.PayloadDocumentation`.
|
||||
|
||||
`subsectionWithPath` can be useful for providing a high-level overview of a particular
|
||||
section of a payload. Separate, more detailed documentation for a subsection can then
|
||||
<<documenting-your-api-request-response-payloads-subsections, be produced>>.
|
||||
|
||||
If you do not want to document a field or subsection at all, 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 `relaxedRequestFields` and `relaxedResponseFields`
|
||||
@@ -233,7 +269,7 @@ The following paths are all present:
|
||||
|
||||
|===
|
||||
|
||||
A response that uses an array at its root can also be documented. The path `[]` will refer
|
||||
A payload that uses an array at its root can also be documented. The path `[]` will refer
|
||||
to the entire array. You can then use bracket or dot notation to identify fields within
|
||||
the array's entries. For example, `[].id` corresponds to the `id` field of every object
|
||||
found in the following array:
|
||||
@@ -403,7 +439,7 @@ include::{examples-dir}/com/example/restassured/Payload.java[tags=book-array]
|
||||
<2> Document `[].title` and `[].author` using the existing descriptors prefixed with `[].`
|
||||
|
||||
[[documenting-your-api-request-response-payloads-subsections]]
|
||||
==== Documenting a portion of a request or response payload
|
||||
==== Documenting a subsection of a request or response payload
|
||||
|
||||
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
|
||||
@@ -433,7 +469,7 @@ be produced as follows:
|
||||
[source,java,indent=0,role="primary"]
|
||||
.MockMvc
|
||||
----
|
||||
include::{examples-dir}/com/example/mockmvc/Payload.java[tags=subsection]
|
||||
include::{examples-dir}/com/example/mockmvc/Payload.java[tags=beneath-path]
|
||||
----
|
||||
<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
|
||||
@@ -443,7 +479,7 @@ include::{examples-dir}/com/example/mockmvc/Payload.java[tags=subsection]
|
||||
[source,java,indent=0,role="secondary"]
|
||||
.REST Assured
|
||||
----
|
||||
include::{examples-dir}/com/example/restassured/Payload.java[tags=subsection]
|
||||
include::{examples-dir}/com/example/restassured/Payload.java[tags=beneath-path]
|
||||
----
|
||||
<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
|
||||
@@ -457,6 +493,7 @@ 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]
|
||||
----
|
||||
|
||||
@@ -26,6 +26,7 @@ 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.responseFields;
|
||||
import static org.springframework.restdocs.snippet.Attributes.attributes;
|
||||
@@ -41,10 +42,19 @@ public class Payload {
|
||||
this.mockMvc.perform(get("/user/5").accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andDo(document("index", responseFields( // <1>
|
||||
fieldWithPath("contact").description("The user's contact details"), // <2>
|
||||
fieldWithPath("contact.email").description("The user's email address")))); // <3>
|
||||
fieldWithPath("contact.email").description("The user's email address"), // <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>
|
||||
// end::subsection[]
|
||||
}
|
||||
|
||||
public void explicitType() throws Exception {
|
||||
this.mockMvc.perform(get("/user/5").accept(MediaType.APPLICATION_JSON))
|
||||
@@ -92,14 +102,14 @@ public class Payload {
|
||||
// end::book-array[]
|
||||
}
|
||||
|
||||
public void subsection() throws Exception {
|
||||
// tag::subsection[]
|
||||
public void subsectionBeneathPath() throws Exception {
|
||||
// tag::beneath-path[]
|
||||
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::subsection[]
|
||||
// end::beneath-path[]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import static org.springframework.restdocs.payload.PayloadDocumentation.beneathP
|
||||
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
|
||||
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
|
||||
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
|
||||
import static org.springframework.restdocs.payload.PayloadDocumentation.subsectionWithPath;
|
||||
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document;
|
||||
import static org.springframework.restdocs.snippet.Attributes.attributes;
|
||||
import static org.springframework.restdocs.snippet.Attributes.key;
|
||||
@@ -39,12 +40,22 @@ public class Payload {
|
||||
// tag::response[]
|
||||
RestAssured.given(this.spec).accept("application/json")
|
||||
.filter(document("user", responseFields( // <1>
|
||||
fieldWithPath("contact").description("The user's contact details"), // <2>
|
||||
fieldWithPath("contact.name").description("The user's name"), // <2>
|
||||
fieldWithPath("contact.email").description("The user's email address")))) // <3>
|
||||
.when().get("/user/5")
|
||||
.then().assertThat().statusCode(is(200));
|
||||
// end::response[]
|
||||
}
|
||||
|
||||
public void subsection() throws Exception {
|
||||
// tag::subsection[]
|
||||
RestAssured.given(this.spec).accept("application/json")
|
||||
.filter(document("user", responseFields(
|
||||
subsectionWithPath("contact").description("The user's contact details")))) // <1>
|
||||
.when().get("/user/5")
|
||||
.then().assertThat().statusCode(is(200));
|
||||
// end::response[]
|
||||
}
|
||||
|
||||
public void explicitType() throws Exception {
|
||||
RestAssured.given(this.spec).accept("application/json")
|
||||
@@ -96,15 +107,15 @@ public class Payload {
|
||||
// end::book-array[]
|
||||
}
|
||||
|
||||
public void subsection() throws Exception {
|
||||
// tag::subsection[]
|
||||
public void subsectionBeneathPath() throws Exception {
|
||||
// tag::beneath-path[]
|
||||
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::subsection[]
|
||||
// end::beneath-path[]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import static org.springframework.restdocs.operation.preprocess.Preprocessors.pr
|
||||
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath
|
||||
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields
|
||||
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields
|
||||
import static org.springframework.restdocs.payload.PayloadDocumentation.subsectionWithPath
|
||||
import static org.springframework.restdocs.restassured.operation.preprocess.RestAssuredPreprocessors.modifyUris
|
||||
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document
|
||||
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.documentationConfiguration
|
||||
@@ -75,8 +76,8 @@ class ApiDocumentationSpec extends Specification {
|
||||
fieldWithPath('appprofile').description('the profile of grails used in this project'),
|
||||
fieldWithPath('groovyversion').description('the version of groovy used in this project'),
|
||||
fieldWithPath('jvmversion').description('the version of the jvm used in this project'),
|
||||
fieldWithPath('controllers').type(JsonFieldType.ARRAY).description('the list of available controllers'),
|
||||
fieldWithPath('plugins').type(JsonFieldType.ARRAY).description('the plugins active for this project'),
|
||||
subsectionWithPath('controllers').type(JsonFieldType.ARRAY).description('the list of available controllers'),
|
||||
subsectionWithPath('plugins').type(JsonFieldType.ARRAY).description('the plugins active for this project'),
|
||||
)))
|
||||
.when()
|
||||
.port(this.serverPort)
|
||||
@@ -123,14 +124,14 @@ class ApiDocumentationSpec extends Specification {
|
||||
requestFields(
|
||||
fieldWithPath('title').description('the title of the note'),
|
||||
fieldWithPath('body').description('the body of the note'),
|
||||
fieldWithPath('tags').type(JsonFieldType.ARRAY).description('a list of tags associated to the note')
|
||||
subsectionWithPath('tags').type(JsonFieldType.ARRAY).description('a list of tags associated to the note')
|
||||
),
|
||||
responseFields(
|
||||
fieldWithPath('class').description('the class of the resource'),
|
||||
fieldWithPath('id').description('the id of the note'),
|
||||
fieldWithPath('title').description('the title of the note'),
|
||||
fieldWithPath('body').description('the body of the note'),
|
||||
fieldWithPath('tags').type(JsonFieldType.ARRAY).description('the list of tags associated with the note'),
|
||||
subsectionWithPath('tags').type(JsonFieldType.ARRAY).description('the list of tags associated with the note')
|
||||
)))
|
||||
.body('{ "body": "My test example", "title": "Eureka!", "tags": [{"name": "testing123"}] }')
|
||||
.when()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2015 the original author or authors.
|
||||
* 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.
|
||||
@@ -28,6 +28,7 @@ import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuild
|
||||
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
|
||||
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
|
||||
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
|
||||
import static org.springframework.restdocs.payload.PayloadDocumentation.subsectionWithPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
@@ -59,7 +60,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@SpringApplicationConfiguration(classes = RestNotesSpringDataRest.class)
|
||||
@WebAppConfiguration
|
||||
public class ApiDocumentation {
|
||||
|
||||
|
||||
@Rule
|
||||
public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();
|
||||
|
||||
@@ -116,7 +117,7 @@ public class ApiDocumentation {
|
||||
linkWithRel("tags").description("The <<resources-tags,Tags resource>>"),
|
||||
linkWithRel("profile").description("The ALPS profile for the service")),
|
||||
responseFields(
|
||||
fieldWithPath("_links").description("<<resources-index-links,Links>> to other resources"))));
|
||||
subsectionWithPath("_links").description("<<resources-index-links,Links>> to other resources"))));
|
||||
|
||||
}
|
||||
|
||||
@@ -137,8 +138,8 @@ public class ApiDocumentation {
|
||||
linkWithRel("self").description("Canonical link for this resource"),
|
||||
linkWithRel("profile").description("The ALPS profile for this resource")),
|
||||
responseFields(
|
||||
fieldWithPath("_embedded.notes").description("An array of <<resources-note, Note resources>>"),
|
||||
fieldWithPath("_links").description("<<resources-tags-list-links, Links>> to other resources"))));
|
||||
subsectionWithPath("_embedded.notes").description("An array of <<resources-note, Note resources>>"),
|
||||
subsectionWithPath("_links").description("<<resources-tags-list-links, Links>> to other resources"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -208,7 +209,7 @@ public class ApiDocumentation {
|
||||
responseFields(
|
||||
fieldWithPath("title").description("The title of the note"),
|
||||
fieldWithPath("body").description("The body of the note"),
|
||||
fieldWithPath("_links").description("<<resources-note-links,Links>> to other resources"))));
|
||||
subsectionWithPath("_links").description("<<resources-note-links,Links>> to other resources"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -227,8 +228,8 @@ public class ApiDocumentation {
|
||||
linkWithRel("self").description("Canonical link for this resource"),
|
||||
linkWithRel("profile").description("The ALPS profile for this resource")),
|
||||
responseFields(
|
||||
fieldWithPath("_embedded.tags").description("An array of <<resources-tag,Tag resources>>"),
|
||||
fieldWithPath("_links").description("<<resources-tags-list-links, Links>> to other resources"))));
|
||||
subsectionWithPath("_embedded.tags").description("An array of <<resources-tag,Tag resources>>"),
|
||||
subsectionWithPath("_links").description("<<resources-tags-list-links, Links>> to other resources"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -310,7 +311,7 @@ public class ApiDocumentation {
|
||||
linkWithRel("notes").description("The <<resources-tagged-notes,notes>> that have this tag")),
|
||||
responseFields(
|
||||
fieldWithPath("name").description("The name of the tag"),
|
||||
fieldWithPath("_links").description("<<resources-tag-links,Links>> to other resources"))));
|
||||
subsectionWithPath("_links").description("<<resources-tag-links,Links>> to other resources"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -33,6 +33,7 @@ import static org.springframework.restdocs.operation.preprocess.Preprocessors.pr
|
||||
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
|
||||
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
|
||||
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
|
||||
import static org.springframework.restdocs.payload.PayloadDocumentation.subsectionWithPath;
|
||||
import static org.springframework.restdocs.snippet.Attributes.key;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
@@ -68,11 +69,11 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@SpringApplicationConfiguration(classes = RestNotesSpringHateoas.class)
|
||||
@WebAppConfiguration
|
||||
public class ApiDocumentation {
|
||||
|
||||
|
||||
@Rule
|
||||
public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();
|
||||
|
||||
private RestDocumentationResultHandler documentationHandler;
|
||||
|
||||
private RestDocumentationResultHandler documentationHandler;
|
||||
|
||||
@Autowired
|
||||
private NoteRepository noteRepository;
|
||||
@@ -93,13 +94,13 @@ public class ApiDocumentation {
|
||||
this.documentationHandler = document("{method-name}",
|
||||
preprocessRequest(prettyPrint()),
|
||||
preprocessResponse(prettyPrint()));
|
||||
|
||||
|
||||
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
|
||||
.apply(documentationConfiguration(this.restDocumentation))
|
||||
.alwaysDo(this.documentationHandler)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void headersExample() throws Exception {
|
||||
this.mockMvc
|
||||
@@ -140,7 +141,7 @@ public class ApiDocumentation {
|
||||
linkWithRel("notes").description("The <<resources-notes,Notes resource>>"),
|
||||
linkWithRel("tags").description("The <<resources-tags,Tags resource>>")),
|
||||
responseFields(
|
||||
fieldWithPath("_links").description("<<resources-index-links,Links>> to other resources"))));
|
||||
subsectionWithPath("_links").description("<<resources-index-links,Links>> to other resources"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -150,13 +151,13 @@ public class ApiDocumentation {
|
||||
createNote("REST maturity model", "http://martinfowler.com/articles/richardsonMaturityModel.html");
|
||||
createNote("Hypertext Application Language (HAL)", "http://stateless.co/hal_specification.html");
|
||||
createNote("Application-Level Profile Semantics (ALPS)", "http://alps.io/spec/");
|
||||
|
||||
|
||||
this.mockMvc
|
||||
.perform(get("/notes"))
|
||||
.andExpect(status().isOk())
|
||||
.andDo(this.documentationHandler.document(
|
||||
responseFields(
|
||||
fieldWithPath("_embedded.notes").description("An array of <<resources-note, Note resources>>"))));
|
||||
subsectionWithPath("_embedded.notes").description("An array of <<resources-note, Note resources>>"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -177,7 +178,7 @@ public class ApiDocumentation {
|
||||
note.put("tags", Arrays.asList(tagLocation));
|
||||
|
||||
ConstrainedFields fields = new ConstrainedFields(NoteInput.class);
|
||||
|
||||
|
||||
this.mockMvc
|
||||
.perform(post("/notes")
|
||||
.contentType(MediaTypes.HAL_JSON)
|
||||
@@ -214,7 +215,7 @@ public class ApiDocumentation {
|
||||
.content(this.objectMapper.writeValueAsString(note)))
|
||||
.andExpect(status().isCreated())
|
||||
.andReturn().getResponse().getHeader("Location");
|
||||
|
||||
|
||||
this.mockMvc
|
||||
.perform(get(noteLocation))
|
||||
.andExpect(status().isOk())
|
||||
@@ -229,7 +230,7 @@ public class ApiDocumentation {
|
||||
responseFields(
|
||||
fieldWithPath("title").description("The title of the note"),
|
||||
fieldWithPath("body").description("The body of the note"),
|
||||
fieldWithPath("_links").description("<<resources-note-links,Links>> to other resources"))));
|
||||
subsectionWithPath("_links").description("<<resources-note-links,Links>> to other resources"))));
|
||||
|
||||
}
|
||||
|
||||
@@ -241,13 +242,13 @@ public class ApiDocumentation {
|
||||
createTag("REST");
|
||||
createTag("Hypermedia");
|
||||
createTag("HTTP");
|
||||
|
||||
|
||||
this.mockMvc
|
||||
.perform(get("/tags"))
|
||||
.andExpect(status().isOk())
|
||||
.andDo(this.documentationHandler.document(
|
||||
responseFields(
|
||||
fieldWithPath("_embedded.tags").description("An array of <<resources-tag,Tag resources>>"))));
|
||||
subsectionWithPath("_embedded.tags").description("An array of <<resources-tag,Tag resources>>"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -256,7 +257,7 @@ public class ApiDocumentation {
|
||||
tag.put("name", "REST");
|
||||
|
||||
ConstrainedFields fields = new ConstrainedFields(TagInput.class);
|
||||
|
||||
|
||||
this.mockMvc
|
||||
.perform(post("/tags")
|
||||
.contentType(MediaTypes.HAL_JSON)
|
||||
@@ -344,7 +345,7 @@ public class ApiDocumentation {
|
||||
linkWithRel("tagged-notes").description("The <<resources-tagged-notes,notes>> that have this tag")),
|
||||
responseFields(
|
||||
fieldWithPath("name").description("The name of the tag"),
|
||||
fieldWithPath("_links").description("<<resources-tag-links,Links>> to other resources"))));
|
||||
subsectionWithPath("_links").description("<<resources-tag-links,Links>> to other resources"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -363,7 +364,7 @@ public class ApiDocumentation {
|
||||
tagUpdate.put("name", "RESTful");
|
||||
|
||||
ConstrainedFields fields = new ConstrainedFields(TagPatchInput.class);
|
||||
|
||||
|
||||
this.mockMvc
|
||||
.perform(patch(tagLocation)
|
||||
.contentType(MediaTypes.HAL_JSON)
|
||||
|
||||
@@ -155,12 +155,10 @@ public abstract class AbstractFieldsSnippet extends TemplatedSnippet {
|
||||
for (FieldDescriptor descriptor : descriptors) {
|
||||
Assert.notNull(descriptor.getPath(), "Field descriptors must have a path");
|
||||
if (!descriptor.isIgnored()) {
|
||||
Assert.notNull(descriptor.getDescription(),
|
||||
"The descriptor for field '" + descriptor.getPath()
|
||||
+ "' must either have a description or" + " be marked as "
|
||||
+ "ignored");
|
||||
Assert.notNull(descriptor.getDescription() != null,
|
||||
"The descriptor for '" + descriptor.getPath() + "' must have a"
|
||||
+ " description or it must be marked as ignored");
|
||||
}
|
||||
|
||||
}
|
||||
this.fieldDescriptors = descriptors;
|
||||
this.ignoreUndocumentedFields = ignoreUndocumentedFields;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2015 the original author or authors.
|
||||
* 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.
|
||||
|
||||
@@ -65,7 +65,12 @@ class JsonContentHandler implements ContentHandler {
|
||||
Object content = readContent();
|
||||
for (FieldDescriptor fieldDescriptor : fieldDescriptors) {
|
||||
JsonFieldPath path = JsonFieldPath.compile(fieldDescriptor.getPath());
|
||||
this.fieldProcessor.remove(path, content);
|
||||
if (describesSubsection(fieldDescriptor)) {
|
||||
this.fieldProcessor.removeSubsection(path, content);
|
||||
}
|
||||
else {
|
||||
this.fieldProcessor.remove(path, content);
|
||||
}
|
||||
}
|
||||
if (!isEmpty(content)) {
|
||||
try {
|
||||
@@ -78,6 +83,10 @@ class JsonContentHandler implements ContentHandler {
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean describesSubsection(FieldDescriptor fieldDescriptor) {
|
||||
return fieldDescriptor instanceof SubsectionDescriptor;
|
||||
}
|
||||
|
||||
private Object readContent() {
|
||||
try {
|
||||
return new ObjectMapper().readValue(this.rawContent, Object.class);
|
||||
|
||||
@@ -76,6 +76,17 @@ final class JsonFieldProcessor {
|
||||
});
|
||||
}
|
||||
|
||||
void removeSubsection(final JsonFieldPath path, Object payload) {
|
||||
traverse(new ProcessingContext(payload, path), new MatchCallback() {
|
||||
|
||||
@Override
|
||||
public void foundMatch(Match match) {
|
||||
match.removeSubsection();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private void traverse(ProcessingContext context, MatchCallback matchCallback) {
|
||||
final String segment = context.getSegment();
|
||||
if (JsonFieldPath.isArraySegment(segment)) {
|
||||
@@ -149,12 +160,41 @@ final class JsonFieldProcessor {
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
Object removalCandidate = this.map.get(this.segment);
|
||||
if (isMapWithEntries(removalCandidate)
|
||||
|| isListWithNonScalarEntries(removalCandidate)) {
|
||||
return;
|
||||
}
|
||||
this.map.remove(this.segment);
|
||||
if (this.map.isEmpty() && this.parent != null) {
|
||||
this.parent.remove();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSubsection() {
|
||||
this.map.remove(this.segment);
|
||||
if (this.map.isEmpty() && this.parent != null) {
|
||||
this.parent.removeSubsection();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isMapWithEntries(Object object) {
|
||||
return object instanceof Map && !((Map<?, ?>) object).isEmpty();
|
||||
}
|
||||
|
||||
private boolean isListWithNonScalarEntries(Object object) {
|
||||
if (!(object instanceof List)) {
|
||||
return false;
|
||||
}
|
||||
for (Object entry : (List<?>) object) {
|
||||
if (entry instanceof Map || entry instanceof List) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class ListMatch implements Match {
|
||||
@@ -181,12 +221,35 @@ final class JsonFieldProcessor {
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
if (!itemIsEmpty()) {
|
||||
return;
|
||||
}
|
||||
this.items.remove();
|
||||
if (this.list.isEmpty() && this.parent != null) {
|
||||
this.parent.remove();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSubsection() {
|
||||
this.items.remove();
|
||||
if (this.list.isEmpty() && this.parent != null) {
|
||||
this.parent.removeSubsection();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean itemIsEmpty() {
|
||||
return !isMapWithEntries(this.item) && !isListWithEntries(this.item);
|
||||
}
|
||||
|
||||
private boolean isMapWithEntries(Object object) {
|
||||
return object instanceof Map && !((Map<?, ?>) object).isEmpty();
|
||||
}
|
||||
|
||||
private boolean isListWithEntries(Object object) {
|
||||
return object instanceof List && !((List<?>) object).isEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private interface MatchCallback {
|
||||
@@ -200,6 +263,8 @@ final class JsonFieldProcessor {
|
||||
Object getValue();
|
||||
|
||||
void remove();
|
||||
|
||||
void removeSubsection();
|
||||
}
|
||||
|
||||
private static final class ProcessingContext {
|
||||
|
||||
@@ -102,6 +102,74 @@ public abstract class PayloadDocumentation {
|
||||
return new FieldDescriptor(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code FieldDescriptor} that describes a subsection, i.e. a field and all
|
||||
* of its descendants, with the given {@code path}.
|
||||
* <p>
|
||||
* When documenting an XML payload, the {@code path} uses XPath, i.e. '/' is used to
|
||||
* descend to a child node.
|
||||
* <p>
|
||||
* When documenting a JSON payload, the {@code path} uses '.' to descend into a child
|
||||
* object and ' {@code []}' to descend into an array. For example, with this JSON
|
||||
* payload:
|
||||
*
|
||||
* <pre>
|
||||
* {
|
||||
* "a":{
|
||||
* "b":[
|
||||
* {
|
||||
* "c":"one"
|
||||
* },
|
||||
* {
|
||||
* "c":"two"
|
||||
* },
|
||||
* {
|
||||
* "d":"three"
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* The following paths are all present:
|
||||
*
|
||||
* <table summary="Paths and their values">
|
||||
* <tr>
|
||||
* <th>Path</th>
|
||||
* <th>Value</th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code a}</td>
|
||||
* <td>An object containing "b"</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code a.b}</td>
|
||||
* <td>An array containing three objects</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code a.b[]}</td>
|
||||
* <td>An array containing three objects</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code a.b[].c}</td>
|
||||
* <td>An array containing the strings "one" and "two"</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code a.b[].d}</td>
|
||||
* <td>The string "three"</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
* <p>
|
||||
* A subsection descriptor for the array with the path {@code a.b[]} will also
|
||||
* describe its descendants {@code a.b[].c} and {@code a.b[].d}.
|
||||
*
|
||||
* @param path The path of the subsection
|
||||
* @return a {@code SubsectionDescriptor} ready for further configuration
|
||||
*/
|
||||
public static SubsectionDescriptor subsectionWithPath(String path) {
|
||||
return new SubsectionDescriptor(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
@@ -110,16 +178,19 @@ public abstract class PayloadDocumentation {
|
||||
* 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.
|
||||
* a field with a {@link #subsectionWithPath(String) subsection descriptor} will mean
|
||||
* that all of its descendants are also 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.
|
||||
* If you do not want to document a field or subsection, a descriptor can be
|
||||
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
|
||||
* subsection will not appear in the generated snippet and the failure described above
|
||||
* will not occur.
|
||||
*
|
||||
* @param descriptors the descriptions of the request payload's fields
|
||||
* @return the snippet that will document the fields
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
* @see FieldDescriptor#description(Object)
|
||||
*/
|
||||
public static RequestFieldsSnippet requestFields(FieldDescriptor... descriptors) {
|
||||
return requestFields(Arrays.asList(descriptors));
|
||||
@@ -133,16 +204,18 @@ public abstract class PayloadDocumentation {
|
||||
* 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.
|
||||
* a field with a {@link #subsectionWithPath(String) subsection descriptor} will mean
|
||||
* that all of its descendants are also 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.
|
||||
* If you do not want to document a field or subsection, a descriptor can be
|
||||
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
|
||||
* subsection will not appear in the generated snippet and the failure described above
|
||||
* will not occur.
|
||||
*
|
||||
* @param descriptors the descriptions of the request payload's fields
|
||||
* @return the snippet that will document the fields
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
*/
|
||||
public static RequestFieldsSnippet requestFields(List<FieldDescriptor> descriptors) {
|
||||
return new RequestFieldsSnippet(descriptors);
|
||||
@@ -158,6 +231,7 @@ public abstract class PayloadDocumentation {
|
||||
* @param descriptors the descriptions of the request payload's fields
|
||||
* @return the snippet that will document the fields
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
*/
|
||||
public static RequestFieldsSnippet relaxedRequestFields(
|
||||
FieldDescriptor... descriptors) {
|
||||
@@ -174,6 +248,7 @@ public abstract class PayloadDocumentation {
|
||||
* @param descriptors the descriptions of the request payload's fields
|
||||
* @return the snippet that will document the fields
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
*/
|
||||
public static RequestFieldsSnippet relaxedRequestFields(
|
||||
List<FieldDescriptor> descriptors) {
|
||||
@@ -187,19 +262,21 @@ public abstract class PayloadDocumentation {
|
||||
* <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
|
||||
* payload, 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.
|
||||
* 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 with a {@link #subsectionWithPath(String) subsection descriptor} will mean
|
||||
* that all of its descendants are also 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.
|
||||
* If you do not want to document a field or subsection, a descriptor can be
|
||||
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
|
||||
* subsection will not appear in the generated snippet and the failure described above
|
||||
* will not occur.
|
||||
*
|
||||
* @param attributes the attributes
|
||||
* @param descriptors the descriptions of the request payload's fields
|
||||
* @return the snippet that will document the fields
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
*/
|
||||
public static RequestFieldsSnippet requestFields(Map<String, Object> attributes,
|
||||
FieldDescriptor... descriptors) {
|
||||
@@ -213,19 +290,21 @@ public abstract class PayloadDocumentation {
|
||||
* <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
|
||||
* payload, 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.
|
||||
* 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 with a {@link #subsectionWithPath(String) subsection descriptor} will mean
|
||||
* that all of its descendants are also 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.
|
||||
* If you do not want to document a field or subsection, a descriptor can be
|
||||
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
|
||||
* subsection will not appear in the generated snippet and the failure described above
|
||||
* will not occur.
|
||||
*
|
||||
* @param attributes the attributes
|
||||
* @param descriptors the descriptions of the request payload's fields
|
||||
* @return the snippet that will document the fields
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
*/
|
||||
public static RequestFieldsSnippet requestFields(Map<String, Object> attributes,
|
||||
List<FieldDescriptor> descriptors) {
|
||||
@@ -244,6 +323,7 @@ public abstract class PayloadDocumentation {
|
||||
* @param descriptors the descriptions of the request payload's fields
|
||||
* @return the snippet that will document the fields
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
*/
|
||||
public static RequestFieldsSnippet relaxedRequestFields(
|
||||
Map<String, Object> attributes, FieldDescriptor... descriptors) {
|
||||
@@ -262,6 +342,7 @@ public abstract class PayloadDocumentation {
|
||||
* @param descriptors the descriptions of the request payload's fields
|
||||
* @return the snippet that will document the fields
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
*/
|
||||
public static RequestFieldsSnippet relaxedRequestFields(
|
||||
Map<String, Object> attributes, List<FieldDescriptor> descriptors) {
|
||||
@@ -273,22 +354,25 @@ public abstract class PayloadDocumentation {
|
||||
* operations's request payload extracted by the given {@code subsectionExtractor}.
|
||||
* 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.
|
||||
* If a field is present in the subsection of 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 subsection, a failure will also occur.For payloads with a
|
||||
* hierarchical structure, documenting a field with a
|
||||
* {@link #subsectionWithPath(String) subsection descriptor} will mean that all of its
|
||||
* descendants are also 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.
|
||||
* If you do not want to document a field or subsection, a descriptor can be
|
||||
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
|
||||
* subsection will not appear in the generated snippet and the failure described above
|
||||
* will not occur.
|
||||
*
|
||||
* @param subsectionExtractor the subsection extractor
|
||||
* @param descriptors the descriptions of the request payload's fields
|
||||
* @return the snippet that will document the fields
|
||||
* @since 1.2.0
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
* @see #beneathPath(String)
|
||||
*/
|
||||
public static RequestFieldsSnippet requestFields(
|
||||
@@ -302,22 +386,25 @@ public abstract class PayloadDocumentation {
|
||||
* API operations's request payload extracted by the given {@code subsectionExtractor}
|
||||
* . 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.
|
||||
* If a field is present in the subsection of 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 subsection, a failure will also occur. For payloads with a
|
||||
* hierarchical structure, documenting a field with a
|
||||
* {@link #subsectionWithPath(String) subsection descriptor} will mean that all of its
|
||||
* descendants are also 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.
|
||||
* If you do not want to document a field or subsection, a descriptor can be
|
||||
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
|
||||
* subsection will not appear in the generated snippet and the failure described above
|
||||
* will not occur.
|
||||
*
|
||||
* @param subsectionExtractor the subsection extractor
|
||||
* @param descriptors the descriptions of the request payload's fields
|
||||
* @return the snippet that will document the fields
|
||||
* @since 1.2.0
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
* @see #beneathPath(String)
|
||||
*/
|
||||
public static RequestFieldsSnippet requestFields(
|
||||
@@ -339,6 +426,7 @@ public abstract class PayloadDocumentation {
|
||||
* @return the snippet that will document the fields
|
||||
* @since 1.2.0
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
* @see #beneathPath(String)
|
||||
*/
|
||||
public static RequestFieldsSnippet relaxedRequestFields(
|
||||
@@ -360,6 +448,7 @@ public abstract class PayloadDocumentation {
|
||||
* @return the snippet that will document the fields
|
||||
* @since 1.2.0
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
* @see #beneathPath(String)
|
||||
*/
|
||||
public static RequestFieldsSnippet relaxedRequestFields(
|
||||
@@ -374,16 +463,18 @@ public abstract class PayloadDocumentation {
|
||||
* The fields will be documented using the given {@code descriptors} and the given
|
||||
* {@code attributes} will be available during snippet generation.
|
||||
* <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
|
||||
* payload, 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 a field is present in the subsection of 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 subsection, a failure will also occur. For payloads with a
|
||||
* hierarchical structure, documenting a field with a
|
||||
* {@link #subsectionWithPath(String) subsection descriptor} will mean that all of its
|
||||
* descendants are also 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.
|
||||
* If you do not want to document a field or subsection, a descriptor can be
|
||||
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
|
||||
* subsection will not appear in the generated snippet and the failure described above
|
||||
* will not occur.
|
||||
*
|
||||
* @param subsectionExtractor the subsection extractor
|
||||
* @param attributes the attributes
|
||||
@@ -391,6 +482,7 @@ public abstract class PayloadDocumentation {
|
||||
* @return the snippet that will document the fields
|
||||
* @since 1.2.0
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
* @see #beneathPath(String)
|
||||
*/
|
||||
public static RequestFieldsSnippet requestFields(
|
||||
@@ -405,16 +497,18 @@ public abstract class PayloadDocumentation {
|
||||
* The fields will be documented using the given {@code descriptors} and the given
|
||||
* {@code attributes} will be available during snippet generation.
|
||||
* <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
|
||||
* payload, 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 a field is present in the subsection of 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 subsection, a failure will also occur. For payloads with a
|
||||
* hierarchical structure, documenting a field with a
|
||||
* {@link #subsectionWithPath(String) subsection descriptor} will mean that all of its
|
||||
* descendants are also 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.
|
||||
* If you do not want to document a field or subsection, a descriptor can be
|
||||
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
|
||||
* subsection will not appear in the generated snippet and the failure described above
|
||||
* will not occur.
|
||||
*
|
||||
* @param subsectionExtractor the subsection extractor
|
||||
* @param attributes the attributes
|
||||
@@ -422,6 +516,7 @@ public abstract class PayloadDocumentation {
|
||||
* @return the snippet that will document the fields
|
||||
* @since 1.2.0
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
* @see #beneathPath(String)
|
||||
*/
|
||||
public static RequestFieldsSnippet requestFields(
|
||||
@@ -445,6 +540,7 @@ public abstract class PayloadDocumentation {
|
||||
* @return the snippet that will document the fields
|
||||
* @since 1.2.0
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
* @see #beneathPath(String)
|
||||
*/
|
||||
public static RequestFieldsSnippet relaxedRequestFields(
|
||||
@@ -469,6 +565,7 @@ public abstract class PayloadDocumentation {
|
||||
* @return the snippet that will document the fields
|
||||
* @since 1.2.0
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
* @see #beneathPath(String)
|
||||
*/
|
||||
public static RequestFieldsSnippet relaxedRequestFields(
|
||||
@@ -483,22 +580,25 @@ public abstract class PayloadDocumentation {
|
||||
* {@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 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 a field is present in the payload of 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's payload, a failure will also occur. For payloads with a
|
||||
* hierarchical structure, documenting a field with a
|
||||
* {@link #subsectionWithPath(String) subsection descriptor} will mean that all of its
|
||||
* descendants are also 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.
|
||||
* If you do not want to document a field or subsection, a descriptor can be
|
||||
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
|
||||
* subsection will not appear in the generated snippet and the failure described above
|
||||
* will not occur.
|
||||
*
|
||||
* @param part the part name
|
||||
* @param descriptors the descriptions of the request part's fields
|
||||
* @return the snippet that will document the fields
|
||||
* @since 1.2.0
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
*/
|
||||
public static RequestPartFieldsSnippet requestPartFields(String part,
|
||||
FieldDescriptor... descriptors) {
|
||||
@@ -510,21 +610,24 @@ public abstract class PayloadDocumentation {
|
||||
* {@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 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 a field is present in the payload of 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's payload, a failure will also occur. For payloads with a
|
||||
* hierarchical structure, documenting a field with a
|
||||
* {@link #subsectionWithPath(String) subsection descriptor} will mean that all of its
|
||||
* descendants are also 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.
|
||||
* If you do not want to document a field or subsection, a descriptor can be
|
||||
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
|
||||
* subsection will not appear in the generated snippet and the failure described above
|
||||
* will not occur.
|
||||
*
|
||||
* @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)
|
||||
* @see #subsectionWithPath(String)
|
||||
*/
|
||||
public static RequestPartFieldsSnippet requestPartFields(String part,
|
||||
List<FieldDescriptor> descriptors) {
|
||||
@@ -543,6 +646,7 @@ public abstract class PayloadDocumentation {
|
||||
* @param descriptors the descriptions of the request part's fields
|
||||
* @return the snippet that will document the fields
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
*/
|
||||
public static RequestPartFieldsSnippet relaxedRequestPartFields(String part,
|
||||
FieldDescriptor... descriptors) {
|
||||
@@ -561,6 +665,7 @@ public abstract class PayloadDocumentation {
|
||||
* @param descriptors the descriptions of the request part's fields
|
||||
* @return the snippet that will document the fields
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
*/
|
||||
public static RequestPartFieldsSnippet relaxedRequestPartFields(String part,
|
||||
List<FieldDescriptor> descriptors) {
|
||||
@@ -573,22 +678,25 @@ public abstract class PayloadDocumentation {
|
||||
* using the given {@code descriptors} and the given {@code attributes} will be
|
||||
* available during snippet generation.
|
||||
* <p>
|
||||
* 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 a field is present in the payload of 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's payload, a failure will also occur. For payloads with a
|
||||
* hierarchical structure, documenting a field with a
|
||||
* {@link #subsectionWithPath(String) subsection descriptor} will mean that all of its
|
||||
* descendants are also 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.
|
||||
* If you do not want to document a field or subsection, a descriptor can be
|
||||
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
|
||||
* subsection will not appear in the generated snippet and the failure described above
|
||||
* will not occur.
|
||||
*
|
||||
* @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)
|
||||
* @see #subsectionWithPath(String)
|
||||
*/
|
||||
public static RequestPartFieldsSnippet requestPartFields(String part,
|
||||
Map<String, Object> attributes, FieldDescriptor... descriptors) {
|
||||
@@ -601,22 +709,25 @@ public abstract class PayloadDocumentation {
|
||||
* using the given {@code descriptors} and the given {@code attributes} will be
|
||||
* available during snippet generation.
|
||||
* <p>
|
||||
* 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 a field is present in the payload of 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's payload, a failure will also occur. For payloads with a
|
||||
* hierarchical structure, documenting a field with a
|
||||
* {@link #subsectionWithPath(String) subsection descriptor} will mean that all of its
|
||||
* descendants are also 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.
|
||||
* If you do not want to document a field or subsection, a descriptor can be
|
||||
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
|
||||
* subsection will not appear in the generated snippet and the failure described above
|
||||
* will not occur.
|
||||
*
|
||||
* @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)
|
||||
* @see #subsectionWithPath(String)
|
||||
*/
|
||||
public static RequestPartFieldsSnippet requestPartFields(String part,
|
||||
Map<String, Object> attributes, List<FieldDescriptor> descriptors) {
|
||||
@@ -637,6 +748,7 @@ public abstract class PayloadDocumentation {
|
||||
* @param descriptors the descriptions of the request part's fields
|
||||
* @return the snippet that will document the fields
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
*/
|
||||
public static RequestPartFieldsSnippet relaxedRequestPartFields(String part,
|
||||
Map<String, Object> attributes, FieldDescriptor... descriptors) {
|
||||
@@ -657,6 +769,7 @@ public abstract class PayloadDocumentation {
|
||||
* @param descriptors the descriptions of the request part's fields
|
||||
* @return the snippet that will document the fields
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
*/
|
||||
public static RequestPartFieldsSnippet relaxedRequestPartFields(String part,
|
||||
Map<String, Object> attributes, List<FieldDescriptor> descriptors) {
|
||||
@@ -669,16 +782,18 @@ public abstract class PayloadDocumentation {
|
||||
* be extracted by the given {@code subsectionExtractor}. The fields will be
|
||||
* documented using the given {@code descriptors}.
|
||||
* <p>
|
||||
* 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 a field is present in the subsection of the request part 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 subsection, a failure will also occur. For payloads with a
|
||||
* hierarchical structure, documenting a field with a
|
||||
* {@link #subsectionWithPath(String) subsection descriptor} will mean that all of its
|
||||
* descendants are also 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.
|
||||
* If you do not want to document a field or subsection, a descriptor can be
|
||||
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
|
||||
* subsection will not appear in the generated snippet and the failure described above
|
||||
* will not occur.
|
||||
*
|
||||
* @param part the part name
|
||||
* @param subsectionExtractor the subsection extractor
|
||||
@@ -686,6 +801,7 @@ public abstract class PayloadDocumentation {
|
||||
* @return the snippet that will document the fields
|
||||
* @since 1.2.0
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
* @see #beneathPath(String)
|
||||
*/
|
||||
public static RequestPartFieldsSnippet requestPartFields(String part,
|
||||
@@ -700,16 +816,18 @@ public abstract class PayloadDocumentation {
|
||||
* be extracted by the given {@code subsectionExtractor}. The fields will be
|
||||
* documented using the given {@code descriptors}.
|
||||
* <p>
|
||||
* 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 a field is present in the subsection of the request part 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 subsection, a failure will also occur. For payloads with a
|
||||
* hierarchical structure, documenting a field with a
|
||||
* {@link #subsectionWithPath(String) subsection descriptor} will mean that all of its
|
||||
* descendants are also 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.
|
||||
* If you do not want to document a field or subsection, a descriptor can be
|
||||
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
|
||||
* subsection will not appear in the generated snippet and the failure described above
|
||||
* will not occur.
|
||||
*
|
||||
* @param part the part name
|
||||
* @param subsectionExtractor the subsection extractor
|
||||
@@ -717,6 +835,7 @@ public abstract class PayloadDocumentation {
|
||||
* @return the snippet that will document the fields
|
||||
* @since 1.2.0
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
* @see #beneathPath(String)
|
||||
*/
|
||||
public static RequestPartFieldsSnippet requestPartFields(String part,
|
||||
@@ -740,6 +859,7 @@ public abstract class PayloadDocumentation {
|
||||
* @return the snippet that will document the fields
|
||||
* @since 1.2.0
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
* @see #beneathPath(String)
|
||||
*/
|
||||
public static RequestPartFieldsSnippet relaxedRequestPartFields(String part,
|
||||
@@ -764,6 +884,7 @@ public abstract class PayloadDocumentation {
|
||||
* @return the snippet that will document the fields
|
||||
* @since 1.2.0
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
* @see #beneathPath(String)
|
||||
*/
|
||||
public static RequestPartFieldsSnippet relaxedRequestPartFields(String part,
|
||||
@@ -779,16 +900,18 @@ public abstract class PayloadDocumentation {
|
||||
* documented using the given {@code descriptors} and the given {@code attributes}
|
||||
* will be available during snippet generation.
|
||||
* <p>
|
||||
* 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 a field is present in the subsection of the request part 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 subsection, a failure will also occur. For payloads with a
|
||||
* hierarchical structure, documenting a field with a
|
||||
* {@link #subsectionWithPath(String) subsection descriptor} will mean that all of its
|
||||
* descendants are also 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.
|
||||
* If you do not want to document a field or subsection, a descriptor can be
|
||||
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
|
||||
* subsection will not appear in the generated snippet and the failure described above
|
||||
* will not occur.
|
||||
*
|
||||
* @param part the part name
|
||||
* @param subsectionExtractor the subsection extractor
|
||||
@@ -797,6 +920,7 @@ public abstract class PayloadDocumentation {
|
||||
* @return the snippet that will document the fields
|
||||
* @since 1.2.0
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
* @see #beneathPath(String)
|
||||
*/
|
||||
public static RequestPartFieldsSnippet requestPartFields(String part,
|
||||
@@ -813,16 +937,18 @@ public abstract class PayloadDocumentation {
|
||||
* documented using the given {@code descriptors} and the given {@code attributes}
|
||||
* will be available during snippet generation.
|
||||
* <p>
|
||||
* 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 a field is present in the subsection of the request part 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 subsection, a failure will also occur. For payloads with a
|
||||
* hierarchical structure, documenting a field with a
|
||||
* {@link #subsectionWithPath(String) subsection descriptor} will mean that all of its
|
||||
* descendants are also 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.
|
||||
* If you do not want to document a field or subsection, a descriptor can be
|
||||
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
|
||||
* subsection will not appear in the generated snippet and the failure described above
|
||||
* will not occur.
|
||||
*
|
||||
* @param part the part name
|
||||
* @param subsectionExtractor the subsection extractor
|
||||
@@ -831,6 +957,7 @@ public abstract class PayloadDocumentation {
|
||||
* @return the snippet that will document the fields
|
||||
* @since 1.2.0
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
* @see #beneathPath(String)
|
||||
*/
|
||||
public static RequestPartFieldsSnippet requestPartFields(String part,
|
||||
@@ -857,6 +984,7 @@ public abstract class PayloadDocumentation {
|
||||
* @return the snippet that will document the fields
|
||||
* @since 1.2.0
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
* @see #beneathPath(String)
|
||||
*/
|
||||
public static RequestPartFieldsSnippet relaxedRequestPartFields(String part,
|
||||
@@ -883,6 +1011,7 @@ public abstract class PayloadDocumentation {
|
||||
* @return the snippet that will document the fields
|
||||
* @since 1.2.0
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
* @see #beneathPath(String)
|
||||
*/
|
||||
public static RequestPartFieldsSnippet relaxedRequestPartFields(String part,
|
||||
@@ -899,18 +1028,20 @@ public abstract class PayloadDocumentation {
|
||||
* <p>
|
||||
* If a field is present in the response 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 response
|
||||
* payload, 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.
|
||||
* field is documented, is not marked as optional, and is not present in the response,
|
||||
* a failure will also occur. For payloads with a hierarchical structure, documenting
|
||||
* a field with a {@link #subsectionWithPath(String) subsection descriptor} will mean
|
||||
* that all of its descendants are also 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.
|
||||
* If you do not want to document a field or subsection, a descriptor can be
|
||||
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
|
||||
* subsection will not appear in the generated snippet and the failure described above
|
||||
* will not occur.
|
||||
*
|
||||
* @param descriptors the descriptions of the response payload's fields
|
||||
* @return the snippet that will document the fields
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
*/
|
||||
public static ResponseFieldsSnippet responseFields(FieldDescriptor... descriptors) {
|
||||
return responseFields(Arrays.asList(descriptors));
|
||||
@@ -923,19 +1054,21 @@ public abstract class PayloadDocumentation {
|
||||
* <p>
|
||||
* If a field is present in the response 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 response
|
||||
* payload, 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.
|
||||
* field is documented, is not marked as optional, and is not present in the response,
|
||||
* a failure will also occur. For payloads with a hierarchical structure, documenting
|
||||
* a field with a {@link #subsectionWithPath(String) subsection descriptor} will mean
|
||||
* that all of its descendants are also 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.
|
||||
* If you do not want to document a field or subsection, a descriptor can be
|
||||
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
|
||||
* subsection will not appear in the generated snippet and the failure described above
|
||||
* will not occur.
|
||||
*
|
||||
* @param descriptors the descriptions of the response payload's fields
|
||||
* @return the snippet that will document the fields
|
||||
* @since 1.2.0
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
* @see #beneathPath(String)
|
||||
*/
|
||||
public static ResponseFieldsSnippet responseFields(
|
||||
@@ -955,6 +1088,7 @@ public abstract class PayloadDocumentation {
|
||||
* @return the snippet that will document the fields
|
||||
* @since 1.2.0
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
* @see #beneathPath(String)
|
||||
*/
|
||||
public static ResponseFieldsSnippet relaxedResponseFields(
|
||||
@@ -973,6 +1107,7 @@ public abstract class PayloadDocumentation {
|
||||
* @param descriptors the descriptions of the response payload's fields
|
||||
* @return the snippet that will document the fields
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
*/
|
||||
public static ResponseFieldsSnippet relaxedResponseFields(
|
||||
List<FieldDescriptor> descriptors) {
|
||||
@@ -986,19 +1121,21 @@ public abstract class PayloadDocumentation {
|
||||
* <p>
|
||||
* If a field is present in the response 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 response
|
||||
* payload, 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.
|
||||
* field is documented, is not marked as optional, and is not present in the response,
|
||||
* a failure will also occur. For payloads with a hierarchical structure, documenting
|
||||
* a field with a {@link #subsectionWithPath(String) subsection descriptor} will mean
|
||||
* that all of its descendants are also 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.
|
||||
* If you do not want to document a field or subsection, a descriptor can be
|
||||
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
|
||||
* subsection will not appear in the generated snippet and the failure described above
|
||||
* will not occur.
|
||||
*
|
||||
* @param attributes the attributes
|
||||
* @param descriptors the descriptions of the response payload's fields
|
||||
* @return the snippet that will document the fields
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
*/
|
||||
public static ResponseFieldsSnippet responseFields(Map<String, Object> attributes,
|
||||
FieldDescriptor... descriptors) {
|
||||
@@ -1012,19 +1149,21 @@ public abstract class PayloadDocumentation {
|
||||
* <p>
|
||||
* If a field is present in the response 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 response
|
||||
* payload, 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.
|
||||
* field is documented, is not marked as optional, and is not present in the response,
|
||||
* a failure will also occur. For payloads with a hierarchical structure, documenting
|
||||
* a field with a {@link #subsectionWithPath(String) subsection descriptor} will mean
|
||||
* that all of its descendants are also 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.
|
||||
* If you do not want to document a field or subsection, a descriptor can be
|
||||
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
|
||||
* subsection will not appear in the generated snippet and the failure described above
|
||||
* will not occur.
|
||||
*
|
||||
* @param attributes the attributes
|
||||
* @param descriptors the descriptions of the response payload's fields
|
||||
* @return the snippet that will document the fields
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
*/
|
||||
public static ResponseFieldsSnippet responseFields(Map<String, Object> attributes,
|
||||
List<FieldDescriptor> descriptors) {
|
||||
@@ -1043,6 +1182,7 @@ public abstract class PayloadDocumentation {
|
||||
* @param descriptors the descriptions of the response payload's fields
|
||||
* @return the snippet that will document the fields
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
*/
|
||||
public static ResponseFieldsSnippet relaxedResponseFields(
|
||||
Map<String, Object> attributes, FieldDescriptor... descriptors) {
|
||||
@@ -1061,6 +1201,7 @@ public abstract class PayloadDocumentation {
|
||||
* @param descriptors the descriptions of the response payload's fields
|
||||
* @return the snippet that will document the fields
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
*/
|
||||
public static ResponseFieldsSnippet relaxedResponseFields(
|
||||
Map<String, Object> attributes, List<FieldDescriptor> descriptors) {
|
||||
@@ -1077,18 +1218,21 @@ public abstract class PayloadDocumentation {
|
||||
* 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 response
|
||||
* payload, 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.
|
||||
* documenting a field with a {@link #subsectionWithPath(String) subsection
|
||||
* descriptor} will mean that all of its descendants are also 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.
|
||||
* If you do not want to document a field or subsection, a descriptor can be
|
||||
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
|
||||
* subsection will not appear in the generated snippet and the failure described above
|
||||
* will not occur.
|
||||
*
|
||||
* @param subsectionExtractor the subsection extractor
|
||||
* @param descriptors the descriptions of the response payload's fields
|
||||
* @return the snippet that will document the fields
|
||||
* @since 1.2.0
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
* @see #beneathPath(String)
|
||||
*/
|
||||
public static ResponseFieldsSnippet responseFields(
|
||||
@@ -1107,18 +1251,21 @@ public abstract class PayloadDocumentation {
|
||||
* 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 response
|
||||
* payload, 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.
|
||||
* documenting a field with a {@link #subsectionWithPath(String) subsection
|
||||
* descriptor} will mean that all of its descendants are also 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.
|
||||
* If you do not want to document a field or subsection, a descriptor can be
|
||||
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
|
||||
* subsection will not appear in the generated snippet and the failure described above
|
||||
* will not occur.
|
||||
*
|
||||
* @param subsectionExtractor the subsection extractor
|
||||
* @param descriptors the descriptions of the response payload's fields
|
||||
* @return the snippet that will document the fields
|
||||
* @since 1.2.0
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
* @see #beneathPath(String)
|
||||
*/
|
||||
public static ResponseFieldsSnippet responseFields(
|
||||
@@ -1141,6 +1288,7 @@ public abstract class PayloadDocumentation {
|
||||
* @return the snippet that will document the fields
|
||||
* @since 1.2.0
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
* @see #beneathPath(String)
|
||||
*/
|
||||
public static ResponseFieldsSnippet relaxedResponseFields(
|
||||
@@ -1163,6 +1311,7 @@ public abstract class PayloadDocumentation {
|
||||
* @return the snippet that will document the fields
|
||||
* @since 1.2.0
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
* @see #beneathPath(String)
|
||||
*/
|
||||
public static ResponseFieldsSnippet relaxedResponseFields(
|
||||
@@ -1182,12 +1331,14 @@ public abstract class PayloadDocumentation {
|
||||
* 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 response
|
||||
* payload, 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.
|
||||
* documenting a field with a {@link #subsectionWithPath(String) subsection
|
||||
* descriptor} will mean that all of its descendants are also 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.
|
||||
* If you do not want to document a field or subsection, a descriptor can be
|
||||
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
|
||||
* subsection will not appear in the generated snippet and the failure described above
|
||||
* will not occur.
|
||||
*
|
||||
* @param subsectionExtractor the subsection extractor
|
||||
* @param attributes the attributes
|
||||
@@ -1195,6 +1346,7 @@ public abstract class PayloadDocumentation {
|
||||
* @return the snippet that will document the fields
|
||||
* @since 1.2.0
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
* @see #beneathPath(String)
|
||||
*/
|
||||
public static ResponseFieldsSnippet responseFields(
|
||||
@@ -1215,12 +1367,14 @@ public abstract class PayloadDocumentation {
|
||||
* 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 response
|
||||
* payload, 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.
|
||||
* documenting a field with a {@link #subsectionWithPath(String) subsection
|
||||
* descriptor} will mean that all of its descendants are also 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.
|
||||
* If you do not want to document a field or subsection, a descriptor can be
|
||||
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
|
||||
* subsection will not appear in the generated snippet and the failure described above
|
||||
* will not occur.
|
||||
*
|
||||
* @param subsectionExtractor the subsection extractor
|
||||
* @param attributes the attributes
|
||||
@@ -1228,6 +1382,7 @@ public abstract class PayloadDocumentation {
|
||||
* @return the snippet that will document the fields
|
||||
* @since 1.2.0
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
* @see #beneathPath(String)
|
||||
*/
|
||||
public static ResponseFieldsSnippet responseFields(
|
||||
@@ -1252,6 +1407,7 @@ public abstract class PayloadDocumentation {
|
||||
* @return the snippet that will document the fields
|
||||
* @since 1.2.0
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
* @see #beneathPath(String)
|
||||
*/
|
||||
public static ResponseFieldsSnippet relaxedResponseFields(
|
||||
@@ -1277,6 +1433,7 @@ public abstract class PayloadDocumentation {
|
||||
* @return the snippet that will document the fields
|
||||
* @since 1.2.0
|
||||
* @see #fieldWithPath(String)
|
||||
* @see #subsectionWithPath(String)
|
||||
* @see #beneathPath(String)
|
||||
*/
|
||||
public static ResponseFieldsSnippet relaxedResponseFields(
|
||||
@@ -1298,11 +1455,13 @@ public abstract class PayloadDocumentation {
|
||||
List<FieldDescriptor> descriptors) {
|
||||
List<FieldDescriptor> prefixedDescriptors = new ArrayList<>();
|
||||
for (FieldDescriptor descriptor : descriptors) {
|
||||
FieldDescriptor prefixedDescriptor = new FieldDescriptor(
|
||||
pathPrefix + descriptor.getPath())
|
||||
.description(descriptor.getDescription())
|
||||
.type(descriptor.getType())
|
||||
.attributes(asArray(descriptor.getAttributes()));
|
||||
String prefixedPath = pathPrefix + descriptor.getPath();
|
||||
FieldDescriptor prefixedDescriptor = descriptor instanceof SubsectionDescriptor
|
||||
? new SubsectionDescriptor(prefixedPath)
|
||||
: new FieldDescriptor(prefixedPath);
|
||||
prefixedDescriptor.description(descriptor.getDescription())
|
||||
.type(descriptor.getType())
|
||||
.attributes(asArray(descriptor.getAttributes()));
|
||||
if (descriptor.isIgnored()) {
|
||||
prefixedDescriptor.ignored();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* A description of a subsection, i.e. a field and all of its descendants, in a request or
|
||||
* response payload.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public class SubsectionDescriptor extends FieldDescriptor {
|
||||
|
||||
/**
|
||||
* Creates a new {@code SubsectionDescriptor} describing the subsection with the given
|
||||
* {@code path}.
|
||||
* @param path the path
|
||||
*/
|
||||
protected SubsectionDescriptor(String path) {
|
||||
super(path);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package org.springframework.restdocs.payload;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
@@ -36,6 +37,7 @@ import javax.xml.xpath.XPathFactory;
|
||||
|
||||
import org.w3c.dom.Attr;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.InputSource;
|
||||
@@ -108,6 +110,7 @@ class XmlContentHandler implements ContentHandler {
|
||||
@Override
|
||||
public String getUndocumentedContent(List<FieldDescriptor> fieldDescriptors) {
|
||||
Document payload = readPayload();
|
||||
List<Node> matchedButNotRemoved = new ArrayList<>();
|
||||
for (FieldDescriptor fieldDescriptor : fieldDescriptors) {
|
||||
NodeList matchingNodes;
|
||||
try {
|
||||
@@ -124,17 +127,50 @@ class XmlContentHandler implements ContentHandler {
|
||||
attr.getOwnerElement().removeAttributeNode(attr);
|
||||
}
|
||||
else {
|
||||
node.getParentNode().removeChild(node);
|
||||
if (fieldDescriptor instanceof SubsectionDescriptor
|
||||
|| isLeafNode(node)) {
|
||||
node.getParentNode().removeChild(node);
|
||||
}
|
||||
else {
|
||||
matchedButNotRemoved.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
removeLeafNodes(matchedButNotRemoved);
|
||||
if (payload.getChildNodes().getLength() > 0) {
|
||||
return prettyPrint(payload);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void removeLeafNodes(List<Node> candidates) {
|
||||
boolean changed = true;
|
||||
while (changed) {
|
||||
changed = false;
|
||||
Iterator<Node> iterator = candidates.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Node node = iterator.next();
|
||||
if (isLeafNode(node)) {
|
||||
node.getParentNode().removeChild(node);
|
||||
iterator.remove();
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isLeafNode(Node node) {
|
||||
NodeList childNodes = node.getChildNodes();
|
||||
for (int i = 0; i < childNodes.getLength(); i++) {
|
||||
if (childNodes.item(i) instanceof Element) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private String prettyPrint(Document document) {
|
||||
try {
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
|
||||
@@ -201,6 +201,26 @@ public class JsonFieldProcessorTests {
|
||||
assertThat(payload.size(), equalTo(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mapWithEntriesIsNotRemovedWhenNotAlsoRemovingDescendants() {
|
||||
Map<String, Object> payload = new HashMap<>();
|
||||
Map<String, Object> alpha = new HashMap<>();
|
||||
payload.put("a", alpha);
|
||||
alpha.put("b", "bravo");
|
||||
this.fieldProcessor.remove(JsonFieldPath.compile("a"), payload);
|
||||
assertThat(payload.size(), equalTo(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeSubsectionRemovesMapWithEntries() {
|
||||
Map<String, Object> payload = new HashMap<>();
|
||||
Map<String, Object> alpha = new HashMap<>();
|
||||
payload.put("a", alpha);
|
||||
alpha.put("b", "bravo");
|
||||
this.fieldProcessor.removeSubsection(JsonFieldPath.compile("a"), payload);
|
||||
assertThat(payload.size(), equalTo(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeNestedMapEntry() {
|
||||
Map<String, Object> payload = new HashMap<>();
|
||||
@@ -229,6 +249,51 @@ public class JsonFieldProcessorTests {
|
||||
assertThat(payload.size(), equalTo(0));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void removeDoesNotRemoveArrayWithMapEntries() throws IOException {
|
||||
Map<String, Object> payload = new ObjectMapper()
|
||||
.readValue("{\"a\": [{\"b\":\"bravo\"},{\"b\":\"bravo\"}]}", Map.class);
|
||||
this.fieldProcessor.remove(JsonFieldPath.compile("a[]"), payload);
|
||||
assertThat(payload.size(), equalTo(1));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void removeDoesNotRemoveArrayWithListEntries() throws IOException {
|
||||
Map<String, Object> payload = new ObjectMapper().readValue("{\"a\": [[2],[3]]}",
|
||||
Map.class);
|
||||
this.fieldProcessor.remove(JsonFieldPath.compile("a[]"), payload);
|
||||
assertThat(payload.size(), equalTo(1));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void removeRemovesArrayWithOnlyScalarEntries() throws IOException {
|
||||
Map<String, Object> payload = new ObjectMapper()
|
||||
.readValue("{\"a\": [\"bravo\", \"charlie\"]}", Map.class);
|
||||
this.fieldProcessor.remove(JsonFieldPath.compile("a"), payload);
|
||||
assertThat(payload.size(), equalTo(0));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void removeSubsectionRemovesArrayWithMapEntries() throws IOException {
|
||||
Map<String, Object> payload = new ObjectMapper()
|
||||
.readValue("{\"a\": [{\"b\":\"bravo\"},{\"b\":\"bravo\"}]}", Map.class);
|
||||
this.fieldProcessor.removeSubsection(JsonFieldPath.compile("a[]"), payload);
|
||||
assertThat(payload.size(), equalTo(0));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void removeSubsectionRemovesArrayWithListEntries() throws IOException {
|
||||
Map<String, Object> payload = new ObjectMapper().readValue("{\"a\": [[2],[3]]}",
|
||||
Map.class);
|
||||
this.fieldProcessor.removeSubsection(JsonFieldPath.compile("a[]"), payload);
|
||||
assertThat(payload.size(), equalTo(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void extractNestedEntryWithDotInKeys() throws IOException {
|
||||
Map<String, Object> payload = new HashMap<>();
|
||||
|
||||
@@ -131,8 +131,8 @@ public class RequestFieldsSnippetFailureTests {
|
||||
@Test
|
||||
public void undocumentedXmlRequestField() 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 RequestFieldsSnippet(Collections.<FieldDescriptor>emptyList())
|
||||
.document(this.operationBuilder.request("http://localhost")
|
||||
.content("<a><b>5</b></a>").header(HttpHeaders.CONTENT_TYPE,
|
||||
@@ -140,6 +140,20 @@ public class RequestFieldsSnippetFailureTests {
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void xmlDescendentsAreNotDocumentedByFieldDescriptor() throws IOException {
|
||||
this.thrown.expect(SnippetException.class);
|
||||
this.thrown.expectMessage(
|
||||
startsWith("The following parts of the payload were not documented:"));
|
||||
new RequestFieldsSnippet(
|
||||
Arrays.asList(fieldWithPath("a").type("a").description("one")))
|
||||
.document(this.operationBuilder.request("http://localhost")
|
||||
.content("<a><b>5</b></a>")
|
||||
.header(HttpHeaders.CONTENT_TYPE,
|
||||
MediaType.APPLICATION_XML_VALUE)
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void xmlRequestFieldWithNoType() throws IOException {
|
||||
this.thrown.expect(FieldTypeRequiredException.class);
|
||||
|
||||
@@ -36,6 +36,7 @@ import static org.mockito.Mockito.mock;
|
||||
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.subsectionWithPath;
|
||||
import static org.springframework.restdocs.snippet.Attributes.attributes;
|
||||
import static org.springframework.restdocs.snippet.Attributes.key;
|
||||
|
||||
@@ -65,6 +66,19 @@ public class RequestFieldsSnippetTests extends AbstractSnippetTests {
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void entireSubsectionsCanBeDocumented() throws IOException {
|
||||
this.snippets.expectRequestFields()
|
||||
.withContents(tableWithHeader("Path", "Type", "Description").row("`a`",
|
||||
"`Object`", "one"));
|
||||
|
||||
new RequestFieldsSnippet(
|
||||
Arrays.asList(subsectionWithPath("a").description("one")))
|
||||
.document(this.operationBuilder.request("http://localhost")
|
||||
.content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}")
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subsectionOfMapRequest() throws IOException {
|
||||
this.snippets.expect("request-fields-beneath-a")
|
||||
@@ -121,6 +135,18 @@ public class RequestFieldsSnippetTests extends AbstractSnippetTests {
|
||||
.content("{\"a\": 5, \"b\": 4}").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void entireSubsectionCanBeIgnored() throws IOException {
|
||||
this.snippets.expectRequestFields()
|
||||
.withContents(tableWithHeader("Path", "Type", "Description").row("`c`",
|
||||
"`Number`", "Field c"));
|
||||
|
||||
new RequestFieldsSnippet(Arrays.asList(subsectionWithPath("a").ignored(),
|
||||
fieldWithPath("c").description("Field c")))
|
||||
.document(this.operationBuilder.request("http://localhost")
|
||||
.content("{\"a\": {\"b\": 5}, \"c\": 4}").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void allUndocumentedRequestFieldsCanBeIgnored() throws IOException {
|
||||
this.snippets.expectRequestFields()
|
||||
@@ -256,6 +282,20 @@ public class RequestFieldsSnippetTests extends AbstractSnippetTests {
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void entireSubsectionOfXmlPayloadCanBeDocumented() throws IOException {
|
||||
this.snippets.expectRequestFields().withContents(
|
||||
tableWithHeader("Path", "Type", "Description").row("`a`", "`a`", "one"));
|
||||
|
||||
new RequestFieldsSnippet(
|
||||
Arrays.asList(subsectionWithPath("a").description("one").type("a")))
|
||||
.document(this.operationBuilder.request("http://localhost")
|
||||
.content("<a><b>5</b><c>charlie</c></a>")
|
||||
.header(HttpHeaders.CONTENT_TYPE,
|
||||
MediaType.APPLICATION_XML_VALUE)
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void additionalDescriptors() throws IOException {
|
||||
this.snippets.expectRequestFields()
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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.util.Arrays;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.nullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
|
||||
import static org.springframework.restdocs.payload.PayloadDocumentation.subsectionWithPath;
|
||||
|
||||
/**
|
||||
* Tests for {@link XmlContentHandler}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class XmlContentHandlerTests {
|
||||
|
||||
@Test
|
||||
public void topLevelElementCanBeDocumented() {
|
||||
String undocumentedContent = createHandler("<a>5</a>").getUndocumentedContent(
|
||||
Arrays.asList(fieldWithPath("a").type("a").description("description")));
|
||||
assertThat(undocumentedContent, is(nullValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nestedElementCanBeDocumentedLeavingAncestors() {
|
||||
String undocumentedContent = createHandler("<a><b>5</b></a>")
|
||||
.getUndocumentedContent(Arrays.asList(
|
||||
fieldWithPath("a/b").type("b").description("description")));
|
||||
assertThat(undocumentedContent, is(equalTo(String.format("<a/>%n"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fieldDescriptorDoesNotDocumentEntireSubsection() {
|
||||
String undocumentedContent = createHandler("<a><b>5</b></a>")
|
||||
.getUndocumentedContent(Arrays
|
||||
.asList(fieldWithPath("a").type("a").description("description")));
|
||||
assertThat(undocumentedContent,
|
||||
is(equalTo(String.format("<a>%n <b>5</b>%n</a>%n"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subsectionDescriptorDocumentsEntireSubsection() {
|
||||
String undocumentedContent = createHandler("<a><b>5</b></a>")
|
||||
.getUndocumentedContent(Arrays.asList(
|
||||
subsectionWithPath("a").type("a").description("description")));
|
||||
assertThat(undocumentedContent, is(nullValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleElementsCanBeInDescendingOrderDocumented() {
|
||||
String undocumentedContent = createHandler("<a><b>5</b></a>")
|
||||
.getUndocumentedContent(Arrays.asList(
|
||||
fieldWithPath("a").type("a").description("description"),
|
||||
fieldWithPath("a/b").type("b").description("description")));
|
||||
assertThat(undocumentedContent, is(nullValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleElementsCanBeInAscendingOrderDocumented() {
|
||||
String undocumentedContent = createHandler("<a><b>5</b></a>")
|
||||
.getUndocumentedContent(Arrays.asList(
|
||||
fieldWithPath("a/b").type("b").description("description"),
|
||||
fieldWithPath("a").type("a").description("description")));
|
||||
assertThat(undocumentedContent, is(nullValue()));
|
||||
}
|
||||
|
||||
private XmlContentHandler createHandler(String xml) {
|
||||
return new XmlContentHandler(xml.getBytes());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -72,6 +72,7 @@ import static org.springframework.restdocs.operation.preprocess.Preprocessors.re
|
||||
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
|
||||
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
|
||||
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
|
||||
import static org.springframework.restdocs.payload.PayloadDocumentation.subsectionWithPath;
|
||||
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
|
||||
import static org.springframework.restdocs.request.RequestDocumentation.partWithName;
|
||||
import static org.springframework.restdocs.request.RequestDocumentation.pathParameters;
|
||||
@@ -316,9 +317,10 @@ public class MockMvcRestDocumentationIntegrationTests {
|
||||
|
||||
mockMvc.perform(get("/").param("foo", "bar").accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andDo(document("links", responseFields(
|
||||
fieldWithPath("a").description("The description"),
|
||||
fieldWithPath("links").description("Links to other resources"))));
|
||||
.andDo(document("links",
|
||||
responseFields(fieldWithPath("a").description("The description"),
|
||||
subsectionWithPath("links")
|
||||
.description("Links to other resources"))));
|
||||
|
||||
assertExpectedSnippetFilesExist(new File("build/generated-snippets/links"),
|
||||
"http-request.adoc", "http-response.adoc", "curl-request.adoc",
|
||||
|
||||
@@ -65,6 +65,7 @@ import static org.springframework.restdocs.operation.preprocess.Preprocessors.re
|
||||
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
|
||||
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
|
||||
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
|
||||
import static org.springframework.restdocs.payload.PayloadDocumentation.subsectionWithPath;
|
||||
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
|
||||
import static org.springframework.restdocs.request.RequestDocumentation.partWithName;
|
||||
import static org.springframework.restdocs.request.RequestDocumentation.pathParameters;
|
||||
@@ -204,7 +205,7 @@ public class RestAssuredRestDocumentationIntegrationTests {
|
||||
given().port(this.port).filter(documentationConfiguration(this.restDocumentation))
|
||||
.filter(document("response-fields",
|
||||
responseFields(fieldWithPath("a").description("The description"),
|
||||
fieldWithPath("links")
|
||||
subsectionWithPath("links")
|
||||
.description("Links to other resources"))))
|
||||
.accept("application/json").get("/").then().statusCode(200);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user