diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/http/HttpRequestSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/http/HttpRequestSnippet.java index 23bba3a9..092e1fdb 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/http/HttpRequestSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/http/HttpRequestSnippet.java @@ -16,6 +16,7 @@ package org.springframework.restdocs.http; +import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; @@ -30,6 +31,7 @@ import org.springframework.http.MediaType; import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.operation.OperationRequest; import org.springframework.restdocs.operation.OperationRequestPart; +import org.springframework.restdocs.snippet.ModelCreationException; import org.springframework.restdocs.snippet.Snippet; import org.springframework.restdocs.snippet.TemplatedSnippet; import org.springframework.util.StringUtils; @@ -107,8 +109,7 @@ public class HttpRequestSnippet extends TemplatedSnippet { StringWriter httpRequest = new StringWriter(); PrintWriter writer = new PrintWriter(httpRequest); if (request.getContent().length > 0) { - writer.println(); - writer.print(new String(request.getContent())); + writer.print(requestBody(request)); } else if (isPutOrPost(request)) { if (request.getParts().isEmpty()) { @@ -125,6 +126,16 @@ public class HttpRequestSnippet extends TemplatedSnippet { return httpRequest.toString(); } + private String requestBody(OperationRequest request) { + try { + String content = request.getContentAsString(); + return content.isEmpty() ? content : String.format("%n%s", content); + } + catch (IOException e) { + throw new ModelCreationException("Failed to create response body.", e); + } + } + private boolean isPutOrPost(OperationRequest request) { return HttpMethod.PUT.equals(request.getMethod()) || HttpMethod.POST.equals(request.getMethod()); diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/http/HttpResponseSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/http/HttpResponseSnippet.java index e8f65950..69ee312f 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/http/HttpResponseSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/http/HttpResponseSnippet.java @@ -16,6 +16,7 @@ package org.springframework.restdocs.http; +import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -25,6 +26,7 @@ import java.util.Map.Entry; import org.springframework.http.HttpStatus; import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.operation.OperationResponse; +import org.springframework.restdocs.snippet.ModelCreationException; import org.springframework.restdocs.snippet.Snippet; import org.springframework.restdocs.snippet.TemplatedSnippet; @@ -59,16 +61,23 @@ public class HttpResponseSnippet extends TemplatedSnippet { OperationResponse response = operation.getResponse(); HttpStatus status = response.getStatus(); Map model = new HashMap(); - model.put( - "responseBody", - response.getContent().length > 0 ? String.format("%n%s", new String( - response.getContent())) : ""); + model.put("responseBody", responseBody(response)); model.put("statusCode", status.value()); model.put("statusReason", status.getReasonPhrase()); model.put("headers", headers(response)); return model; } + private String responseBody(OperationResponse response) { + try { + String content = response.getContentAsString(); + return content.isEmpty() ? content : String.format("%n%s", content); + } + catch (IOException e) { + throw new ModelCreationException("Failed to create response body.", e); + } + } + private List> headers(OperationResponse response) { List> headers = new ArrayList<>(); for (Entry> header : response.getHeaders().entrySet()) { diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/OperationRequest.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/OperationRequest.java index 052093ce..a56eae40 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/OperationRequest.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/OperationRequest.java @@ -16,6 +16,7 @@ package org.springframework.restdocs.operation; +import java.io.IOException; import java.net.URI; import java.util.Collection; @@ -38,6 +39,14 @@ public interface OperationRequest { */ byte[] getContent(); + /** + * Returns the contents as string of the request. If the request has no content an empty string + * is returned + * @return the contents as string, never {@code null} + * @throws IOException if an input or output exception occurred + */ + String getContentAsString() throws IOException; + /** * Returns the headers that were included in the request. * diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/OperationResponse.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/OperationResponse.java index 18485fdb..4f0cd181 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/OperationResponse.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/OperationResponse.java @@ -16,6 +16,8 @@ package org.springframework.restdocs.operation; +import java.io.IOException; + import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; @@ -49,4 +51,12 @@ public interface OperationResponse { * @return the contents, never {@code null} */ byte[] getContent(); + + /** + * Returns the contents as string of the response. If the response has no content an empty string + * is returned + * @return the contents as string, never {@code null} + * @throws IOException if an input or output exception occurred + */ + String getContentAsString() throws IOException; } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/StandardOperationRequest.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/StandardOperationRequest.java index 9dfac8c0..f7885e5b 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/StandardOperationRequest.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/StandardOperationRequest.java @@ -16,6 +16,7 @@ package org.springframework.restdocs.operation; +import java.io.UnsupportedEncodingException; import java.net.URI; import java.util.Arrays; import java.util.Collection; @@ -23,6 +24,7 @@ import java.util.Collections; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; /** * Standard implementation of {@link OperationRequest}. @@ -33,6 +35,8 @@ public class StandardOperationRequest implements OperationRequest { private byte[] content; + private String characterEncoding; + private HttpHeaders headers; private HttpMethod method; @@ -60,6 +64,7 @@ public class StandardOperationRequest implements OperationRequest { this.uri = uri; this.method = method; this.content = content; + this.characterEncoding = detectCharsetFromContentTypeHeader(headers); this.headers = headers; this.parameters = parameters; this.parts = parts; @@ -69,6 +74,17 @@ public class StandardOperationRequest implements OperationRequest { public byte[] getContent() { return Arrays.copyOf(this.content, this.content.length); } + + @Override + public String getContentAsString() throws UnsupportedEncodingException { + if (content.length > 0) { + return characterEncoding != null ? + new String(content, characterEncoding) : new String(content); + } + else { + return ""; + } + } @Override public HttpHeaders getHeaders() { @@ -95,4 +111,14 @@ public class StandardOperationRequest implements OperationRequest { return this.uri; } + private String detectCharsetFromContentTypeHeader(HttpHeaders headers) { + if (headers == null) { + return null; + } + MediaType contentType = headers.getContentType(); + if (contentType == null) { + return null; + } + return contentType.getParameter("charset"); + } } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/StandardOperationResponse.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/StandardOperationResponse.java index faac5f5a..628e6f19 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/StandardOperationResponse.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/StandardOperationResponse.java @@ -16,8 +16,11 @@ package org.springframework.restdocs.operation; +import java.io.UnsupportedEncodingException; + import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; /** * Standard implementation of {@link OperationResponse}. @@ -32,6 +35,8 @@ public class StandardOperationResponse implements OperationResponse { private final byte[] content; + private String characterEncoding; + /** * Creates a new response with the given {@code status}, {@code headers}, and * {@code content}. @@ -45,6 +50,7 @@ public class StandardOperationResponse implements OperationResponse { this.status = status; this.headers = headers; this.content = content; + this.characterEncoding = detectCharsetFromContentTypeHeader(headers); } @Override @@ -62,4 +68,25 @@ public class StandardOperationResponse implements OperationResponse { return this.content; } + @Override + public String getContentAsString() throws UnsupportedEncodingException { + if (content.length > 0) { + return characterEncoding != null ? + new String(content, characterEncoding) : new String(content); + } + else { + return ""; + } + } + + private String detectCharsetFromContentTypeHeader(HttpHeaders headers) { + if (headers == null) { + return null; + } + MediaType contentType = headers.getContentType(); + if (contentType == null) { + return null; + } + return contentType.getParameter("charset"); + } } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/http/HttpRequestSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/http/HttpRequestSnippetTests.java index 6dcdcbbe..e225fa70 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/http/HttpRequestSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/http/HttpRequestSnippetTests.java @@ -84,7 +84,23 @@ public class HttpRequestSnippetTests { .request("http://localhost/foo").method("POST").content("Hello, world") .build()); } + + @Test + public void postRequestWithCharset() throws IOException { + this.snippet.expectHttpRequest("post-request-with-charset").withContents( + httpRequest(POST, "/foo") + .header(HttpHeaders.HOST, "localhost") + .header("Content-Type", "text/plain;charset=UTF-8").content( + "こんにちわ, 世界")); // Hello, World in japanese. + new HttpRequestSnippet().document(new OperationBuilder( + "post-request-with-charset", this.snippet.getOutputDirectory()) + .request("http://localhost/foo").method("POST") + .header("Content-Type", "text/plain;charset=UTF-8") + .content("こんにちわ, 世界") + .build()); + } + @Test public void postRequestWithParameter() throws IOException { this.snippet.expectHttpRequest("post-request-with-parameter").withContents( diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/http/HttpResponseSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/http/HttpResponseSnippetTests.java index ab13d950..4349f5d0 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/http/HttpResponseSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/http/HttpResponseSnippetTests.java @@ -84,7 +84,15 @@ public class HttpResponseSnippetTests { new HttpResponseSnippet().document(new OperationBuilder("response-with-content", this.snippet.getOutputDirectory()).response().content("content").build()); } - + + @Test + public void responseWithCharset() throws IOException { + this.snippet.expectHttpResponse("response-with-charset").withContents( + httpResponse(OK).header("Content-Type", "text/plain;charset=UTF-8").content("コンテンツ")); + new HttpResponseSnippet().document(new OperationBuilder("response-with-charset", + this.snippet.getOutputDirectory()).response().header("Content-Type", "text/plain;charset=UTF-8").content("コンテンツ").build()); + } + @Test public void responseWithCustomSnippetAttributes() throws IOException { this.snippet.expectHttpResponse("response-with-snippet-attributes").withContents(