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:
Andy Wilkinson
2016-10-27 17:33:34 +01:00
parent 7bcfbd9e35
commit cbd96f301d
19 changed files with 830 additions and 252 deletions

View File

@@ -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[]
}
}

View File

@@ -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[]
}
}