Support documenting fields in XML payloads with any content type
Previously, the document fields in an XML payload, the request or response had to have an application/xml content type. This prevented documenting standard XML content types such as text/xml and application/rss+xml as well as payloads with custom XML content types. This commit updates the logic that sets up the ContentHandler to first attempt to parse the content as JSON. If that fails it then parses it as XML. If that fails an exception is thrown. This allows any JSON or XML content, irrespective of the actual content type, to be documented. Closes gh-393
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
* Copyright 2014-2017 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.
|
||||
@@ -219,17 +219,32 @@ public abstract class AbstractFieldsSnippet extends TemplatedSnippet {
|
||||
}
|
||||
|
||||
private ContentHandler getContentHandler(byte[] content, MediaType contentType) {
|
||||
try {
|
||||
if (contentType != null
|
||||
&& MediaType.APPLICATION_XML.isCompatibleWith(contentType)) {
|
||||
return new XmlContentHandler(content);
|
||||
}
|
||||
else {
|
||||
return new JsonContentHandler(content);
|
||||
ContentHandler contentHandler = createJsonContentHandler(content);
|
||||
if (contentHandler == null) {
|
||||
contentHandler = createXmlContentHandler(content);
|
||||
if (contentHandler == null) {
|
||||
throw new PayloadHandlingException("Cannot handle " + contentType
|
||||
+ " content as it could not be parsed as JSON or XML");
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new ModelCreationException(ex);
|
||||
return contentHandler;
|
||||
}
|
||||
|
||||
private ContentHandler createJsonContentHandler(byte[] content) {
|
||||
try {
|
||||
return new JsonContentHandler(content);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private ContentHandler createXmlContentHandler(byte[] content) {
|
||||
try {
|
||||
return new XmlContentHandler(content);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,8 +41,9 @@ class JsonContentHandler implements ContentHandler {
|
||||
|
||||
private final byte[] rawContent;
|
||||
|
||||
JsonContentHandler(byte[] content) throws IOException {
|
||||
JsonContentHandler(byte[] content) {
|
||||
this.rawContent = content;
|
||||
readContent();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2015 the original author or authors.
|
||||
* Copyright 2014-2017 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.
|
||||
@@ -62,6 +62,7 @@ class XmlContentHandler implements ContentHandler {
|
||||
throw new IllegalStateException("Failed to create document builder", ex);
|
||||
}
|
||||
this.rawContent = rawContent;
|
||||
readPayload();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -51,4 +51,10 @@ public class JsonContentHandlerTests {
|
||||
assertThat((JsonFieldType) fieldType, is(equalTo(JsonFieldType.STRING)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void failsFastWithNonJsonContent() {
|
||||
this.thrown.expect(PayloadHandlingException.class);
|
||||
new JsonContentHandler("Non-JSON content".getBytes());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
* Copyright 2014-2017 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.
|
||||
@@ -135,8 +135,8 @@ public class RequestFieldsSnippetFailureTests {
|
||||
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,
|
||||
MediaType.APPLICATION_XML_VALUE)
|
||||
.content("<a><b>5</b></a>")
|
||||
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
|
||||
.build());
|
||||
}
|
||||
|
||||
@@ -159,8 +159,8 @@ public class RequestFieldsSnippetFailureTests {
|
||||
this.thrown.expect(FieldTypeRequiredException.class);
|
||||
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one")))
|
||||
.document(this.operationBuilder.request("http://localhost")
|
||||
.content("<a>5</a>").header(HttpHeaders.CONTENT_TYPE,
|
||||
MediaType.APPLICATION_XML_VALUE)
|
||||
.content("<a>5</a>")
|
||||
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
|
||||
.build());
|
||||
}
|
||||
|
||||
@@ -187,8 +187,20 @@ public class RequestFieldsSnippetFailureTests {
|
||||
+ " in the payload: [a/b]"));
|
||||
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a/b").description("one")))
|
||||
.document(this.operationBuilder.request("http://localhost")
|
||||
.content("<a><c>5</c></a>").header(HttpHeaders.CONTENT_TYPE,
|
||||
MediaType.APPLICATION_XML_VALUE)
|
||||
.content("<a><c>5</c></a>")
|
||||
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unsupportedContent() throws IOException {
|
||||
this.thrown.expect(PayloadHandlingException.class);
|
||||
this.thrown.expectMessage(equalTo("Cannot handle text/plain content as it could"
|
||||
+ " not be parsed as JSON or XML"));
|
||||
new RequestFieldsSnippet(Collections.<FieldDescriptor>emptyList())
|
||||
.document(this.operationBuilder.request("http://localhost")
|
||||
.content("Some plain text")
|
||||
.header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE)
|
||||
.build());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
* Copyright 2014-2017 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.
|
||||
@@ -264,21 +264,33 @@ public class RequestFieldsSnippetTests extends AbstractSnippetTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void xmlRequestFields() throws IOException {
|
||||
public void applicationXmlRequestFields() throws IOException {
|
||||
xmlRequestFields(MediaType.APPLICATION_XML);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void textXmlRequestFields() throws IOException {
|
||||
xmlRequestFields(MediaType.TEXT_XML);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customXmlRequestFields() throws IOException {
|
||||
xmlRequestFields(MediaType.parseMediaType("application/vnd.com.example+xml"));
|
||||
}
|
||||
|
||||
private void xmlRequestFields(MediaType contentType) throws IOException {
|
||||
this.snippets.expectRequestFields()
|
||||
.withContents(tableWithHeader("Path", "Type", "Description")
|
||||
.row("`a/b`", "`b`", "one").row("`a/c`", "`c`", "two")
|
||||
.row("`a`", "`a`", "three"));
|
||||
|
||||
new RequestFieldsSnippet(
|
||||
Arrays.asList(fieldWithPath("a/b").description("one").type("b"),
|
||||
fieldWithPath("a/c").description("two").type("c"),
|
||||
fieldWithPath("a").description("three").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)
|
||||
new RequestFieldsSnippet(Arrays.asList(
|
||||
fieldWithPath("a/b").description("one").type("b"),
|
||||
fieldWithPath("a/c").description("two").type("c"),
|
||||
fieldWithPath("a").description("three").type("a")))
|
||||
.document(this.operationBuilder.request("http://localhost")
|
||||
.content("<a><b>5</b><c>charlie</c></a>")
|
||||
.header(HttpHeaders.CONTENT_TYPE, contentType.toString())
|
||||
.build());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
* Copyright 2014-2017 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.
|
||||
@@ -108,7 +108,7 @@ public class ResponseFieldsSnippetFailureTests {
|
||||
.content("<a>foo</a>")
|
||||
.header(HttpHeaders.CONTENT_TYPE,
|
||||
MediaType.APPLICATION_XML_VALUE)
|
||||
.build());
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -162,4 +162,15 @@ public class ResponseFieldsSnippetFailureTests {
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unsupportedContent() throws IOException {
|
||||
this.thrown.expect(PayloadHandlingException.class);
|
||||
this.thrown.expectMessage(equalTo("Cannot handle text/plain content as it could"
|
||||
+ " not be parsed as JSON or XML"));
|
||||
new ResponseFieldsSnippet(Collections.<FieldDescriptor>emptyList())
|
||||
.document(this.operationBuilder.response().content("Some plain text")
|
||||
.header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE)
|
||||
.build());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
* Copyright 2014-2017 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.
|
||||
@@ -238,21 +238,33 @@ public class ResponseFieldsSnippetTests extends AbstractSnippetTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void xmlResponseFields() throws IOException {
|
||||
public void applicationXmlResponseFields() throws IOException {
|
||||
xmlResponseFields(MediaType.APPLICATION_XML);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void textXmlResponseFields() throws IOException {
|
||||
xmlResponseFields(MediaType.TEXT_XML);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customXmlResponseFields() throws IOException {
|
||||
xmlResponseFields(MediaType.parseMediaType("application/vnd.com.example+xml"));
|
||||
}
|
||||
|
||||
private void xmlResponseFields(MediaType contentType) throws IOException {
|
||||
this.snippets.expectResponseFields()
|
||||
.withContents(tableWithHeader("Path", "Type", "Description")
|
||||
.row("`a/b`", "`b`", "one").row("`a/c`", "`c`", "two")
|
||||
.row("`a`", "`a`", "three"));
|
||||
new ResponseFieldsSnippet(
|
||||
Arrays.asList(fieldWithPath("a/b").description("one").type("b"),
|
||||
fieldWithPath("a/c").description("two").type("c"),
|
||||
fieldWithPath("a").description("three").type("a")))
|
||||
.document(
|
||||
this.operationBuilder.response()
|
||||
.content("<a><b>5</b><c>charlie</c></a>")
|
||||
.header(HttpHeaders.CONTENT_TYPE,
|
||||
MediaType.APPLICATION_XML_VALUE)
|
||||
.build());
|
||||
new ResponseFieldsSnippet(Arrays.asList(
|
||||
fieldWithPath("a/b").description("one").type("b"),
|
||||
fieldWithPath("a/c").description("two").type("c"),
|
||||
fieldWithPath("a").description("three").type("a")))
|
||||
.document(this.operationBuilder.response()
|
||||
.content("<a><b>5</b><c>charlie</c></a>")
|
||||
.header(HttpHeaders.CONTENT_TYPE, contentType.toString())
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
* Copyright 2014-2017 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.
|
||||
@@ -18,7 +18,9 @@ package org.springframework.restdocs.payload;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
@@ -34,6 +36,9 @@ import static org.springframework.restdocs.payload.PayloadDocumentation.subsecti
|
||||
*/
|
||||
public class XmlContentHandlerTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void topLevelElementCanBeDocumented() {
|
||||
String undocumentedContent = createHandler("<a>5</a>").getUndocumentedContent(
|
||||
@@ -84,6 +89,12 @@ public class XmlContentHandlerTests {
|
||||
assertThat(undocumentedContent, is(nullValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void failsFastWithNonXmlContent() {
|
||||
this.thrown.expect(PayloadHandlingException.class);
|
||||
createHandler("non-XML content");
|
||||
}
|
||||
|
||||
private XmlContentHandler createHandler(String xml) {
|
||||
return new XmlContentHandler(xml.getBytes());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user