I have an OrderInfo class:
@ApiModel(description = "object needed to make an order")
public class OrderInfo implements Serializable {
private static final long serialVersionUID = 1L;
@JsonProperty("purchase")
private PurchaseInfo purchase = null;
@JsonProperty("paymentMode")
private PaymentMode paymentMode = null;
@JsonProperty("serialNumber")
private String serialNumber = null;
public OrderInfo purchase(PurchaseInfo purchase) {
this.purchase = purchase;
return this;
}
@ApiModelProperty(required = true, value = "coming from commercialization")
@NotNull
@Valid
public PurchaseInfo getPurchase() {
return purchase;
}
public void setPurchase(PurchaseInfo purchase) {
this.purchase = purchase;
}
public OrderInfo paymentMode(PaymentMode paymentMode) {
this.paymentMode = paymentMode;
return this;
}
@ApiModelProperty(required = true, value = "")
@NotNull
@Valid
public PaymentMode getPaymentMode() {
return paymentMode;
}
public void setPaymentMode(PaymentMode paymentMode) {
this.paymentMode = paymentMode;
}
public OrderInfo serialNumber(String serialNumber) {
this.serialNumber = serialNumber;
return this;
}
@ApiModelProperty(value = "The serial number of the registered device")
public String getSerialNumber() {
return serialNumber;
}
public void setSerialNumber(String serialNumber) {
this.serialNumber = serialNumber;
}
}
Having a PurchaseInfo child object:
@ApiModel(description = "Info necessary to order a video, an episode or a season")
public class PurchaseInfo implements Serializable {
private static final long serialVersionUID = 1L;
@JsonProperty("id")
private String id = null;
@JsonProperty("pricingId")
private String pricingId = null;
@JsonProperty("price")
private Double price = null;
@JsonProperty("type")
private ArticleType type = null;
public PurchaseInfo id(String id) {
this.id = id;
return this;
}
@ApiModelProperty(required = true, value = "id of the commercialization")
@NotNull
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public PurchaseInfo pricingId(String pricingId) {
this.pricingId = pricingId;
return this;
}
@ApiModelProperty(required = true, value = "additional pricing of the commercialization")
@NotNull
public String getPricingId() {
return pricingId;
}
public void setPricingId(String pricingId) {
this.pricingId = pricingId;
}
public PurchaseInfo price(Double price) {
this.price = price;
return this;
}
@ApiModelProperty(required = true, value = "price of the commercialization")
@NotNull
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public PurchaseInfo type(ArticleType type) {
this.type = type;
return this;
}
@ApiModelProperty(required = true, value = "")
@NotNull
@Valid
public ArticleType getType() {
return type;
}
public void setType(ArticleType type) {
this.type = type;
}
}
And the generated corresponding FieldDescriptor classes:
public class OrderInfoFieldDescriptor {
public static FieldDescriptor[] fdOrderInfo = new FieldDescriptor[] {
fieldWithPath("purchase").description("coming from commercialization").type(PurchaseInfo.class),
fieldWithPath("paymentMode").description("").type(PaymentMode.class),
fieldWithPath("serialNumber").description("The serial number of the registered device").type(java.lang.String.class).optional() };
public static FieldDescriptor[] fdOrderInfoList = new FieldDescriptor[] {
fieldWithPath("[].purchase").description("coming from commercialization").type(PurchaseInfo.class),
fieldWithPath("[].paymentMode").description("").type(PaymentMode.class),
fieldWithPath("[].serialNumber").description("The serial number of the registered device").type(java.lang.String.class).optional() };
}
And:
public class PurchaseInfoFieldDescriptor {
public static FieldDescriptor[] fdPurchaseInfo = new FieldDescriptor[] {
fieldWithPath("id").description("id of the commercialization").type(java.lang.String.class),
fieldWithPath("pricingId").description("additional pricing of the commercialization").type(java.lang.String.class),
fieldWithPath("price").description("price of the commercialization").type(java.lang.Double.class),
fieldWithPath("type").description("").type(ArticleType.class) };
public static FieldDescriptor[] fdPurchaseInfoList = new FieldDescriptor[] {
fieldWithPath("[].id").description("id of the commercialization").type(java.lang.String.class),
fieldWithPath("[].pricingId").description("additional pricing of the commercialization").type(java.lang.String.class),
fieldWithPath("[].price").description("price of the commercialization").type(java.lang.Double.class),
fieldWithPath("[].type").description("").type(ArticleType.class) };
}
When mockMvc.perform is called in order to make a HTTP POST with requestBody and responseBody of type OrderInfo.class:
mockMvc.perform(postRequest)
.andDo(document("orderInfoCreate", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()),
pathParameters(parameterWithName("username").description("the name of the user")),
responseFields(OrderInfoFieldDescriptor.fdOrderInfo),
requestFields(OrderInfoFieldDescriptor.fdOrderInfo))).andExpect(status().isCreated())
.andExpect(content().json(orderInfoAsJson));
The validation works for paymentMode and serialNumber but fails for purchase with the following exception:
org.springframework.restdocs.snippet.SnippetException: The following parts of the payload were not documented:
{
"purchase" : {
"id" : "purchaseId",
"pricingId" : "pricingId",
"price" : 12.0,
"type" : "EPISODE"
},
"serialNumber" : "serialNumber"
}
Even though the request body and the response body look fine:
MockHttpServletRequest:
HTTP Method = POST
Request URI = /api/users/myUserName/orders
Parameters = {}
Headers = [Content-Type:"application/json;charset=UTF-8", Host:"host:posrt", Accept:"application/json;charset=UTF-8", Cookie:"identity=cookieForTest"]
Body = {"purchase":{"id":"purchaseId","pricingId":"pricingId","price":12.0,"type":"EPISODE"},"paymentMode":"POSTPAID","serialNumber":"serialNumber"}
Session Attrs = {}
And:
MockHttpServletResponse:
Status = 201
Error message = null
Headers = [Content-Type:"application/json;charset=UTF-8"]
Content type = application/json;charset=UTF-8
Body = {"purchase":{"id":"purchaseId","pricingId":"pricingId","price":12.0,"type":"EPISODE"},"paymentMode":"POSTPAID","serialNumber":"serialNumber"}
Forwarded URL = null
Redirected URL = null
Cookies = []
The issue appeared after migrating from Spring Boot 1x to 2x.
Do you have any idea what the issue could be?
Thanks :)
It looks like you are relying on purchase
to document both purchase
and everything it contains, i.e. purchase.id
, purchase.pricingId
, purchase.price
, and purchase.type
. This worked in REST Docs up to and including 1.1 but it created a problem where people would accidentally miss documenting a nested field as they had documented its parent. This was discussed in this question and also in this Spring REST Docs issue. The issue introduced a change in REST Docs 1.2 where documenting a field no longer documents all of its descendants by default. You are affected by this change as upgrading to Spring Boot 2 means that you have also upgraded to REST Docs 2.
If you want to keep the existing behaviour and just use purchase
to document both it and all of its descendants, you should replace fieldWithPath
with subsectionWithPath
. If you want to document everything beneath purchase
, you should add extra descriptors for purchase.id
, purchase.pricingId
, purchase.price
, and purchase.type
.