From 0c0d3b89bd89436ab9a9c72a9a2aaf11f5c27b2c Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 12 May 2015 18:11:48 +0100 Subject: [PATCH] Default snippet encoding to UTF-8 and make it configurable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, snippets were written to disk using the JVM’s default encoding. While this could be controlled by the file.encoding system property it was not ideal as, if the proper was not always set, it could lead to platform-dependent output being generated. Furthermore, Asciidoctor uses UTF-8 encoding by default so there was a risk of the snippets being generated in a different encoding to the encoding expected by Asciidoctor. This commit updates the configuration so that, by default, snippets are encoded as UTF-8. The RestDocumentationConfigurer API has been enhanced to allow this default to be configured as part of building the MockMvc instance. This enhancement as brought with it a more fluent configuration API that separates configuration that is related to URIs from configuration that is related to snippets. Closes gh-67 --- README.md | 40 ++++- .../com/example/notes/ApiDocumentation.java | 15 +- .../notes/GettingStartedDocumentation.java | 4 +- .../com/example/notes/ApiDocumentation.java | 4 +- .../notes/GettingStartedDocumentation.java | 4 +- .../restdocs/RestDocumentation.java | 13 ++ .../restdocs/config/AbstractConfigurer.java | 35 ++++ .../config/AbstractNestedConfigurer.java | 55 ++++++ .../restdocs/config/NestedConfigurer.java | 35 ++++ .../config/RestDocumentationConfigurer.java | 158 +++++++----------- .../config/RestDocumentationContext.java | 15 ++ .../restdocs/config/SnippetConfigurer.java | 61 +++++++ .../restdocs/config/UriConfigurer.java | 122 ++++++++++++++ .../snippet/SnippetWritingResultHandler.java | 9 +- .../RestDocumentationConfigurerTests.java | 54 ++++-- 15 files changed, 501 insertions(+), 123 deletions(-) create mode 100644 spring-restdocs/src/main/java/org/springframework/restdocs/config/AbstractConfigurer.java create mode 100644 spring-restdocs/src/main/java/org/springframework/restdocs/config/AbstractNestedConfigurer.java create mode 100644 spring-restdocs/src/main/java/org/springframework/restdocs/config/NestedConfigurer.java create mode 100644 spring-restdocs/src/main/java/org/springframework/restdocs/config/SnippetConfigurer.java create mode 100644 spring-restdocs/src/main/java/org/springframework/restdocs/config/UriConfigurer.java diff --git a/README.md b/README.md index 9f8ca3f5..c5961115 100644 --- a/README.md +++ b/README.md @@ -243,24 +243,48 @@ documenting. A custom `ResultHandler` is used to produce individual documentatio snippets for its request and its response as well as a snippet that contains both its request and its response. -You can configure the scheme, host, and port of any URIs that appear in the -documentation snippets: +The first step is to create a `MockMvc` instance using `MockMvcBuilders`, configuring +it by applying a `RestDocumentationConfigurer` that can be obtained from the static +`RestDocumentation.documentationConfiguration` method: ```java @Before public void setUp() { this.mockMvc = MockMvcBuilders .webAppContextSetup(this.context) - .apply(new RestDocumentationConfigurer() - .withScheme("https") - .withHost("localhost") - .withPort(8443)) + .apply(documentationConfiguration()) .build(); } ``` -The default values are `http`, `localhost`, and `8080`. You can omit the above -configuration if these defaults meet your needs. +This will apply the default REST documentation configuration: + +| Setting | Default value +| ---------------- | ------------- +| Scheme | http +| Host | localhost +| Port | 8080 +| Context path | Empty string +| Snippet encoding | UTF-8 + +One or more of these settings can be overridden: + +```java +@Before +public void setUp() { + this.mockMvc = MockMvcBuilders + .webAppContextSetup(this.context) + .apply(documentationConfiguration() + .uris() + .withScheme("https") + .withHost("api.example.com") + .withPort(443) + .withContextPath("/v3") + .and().snippets() + .withEncoding("ISO-8859-1")) + .build(); +} +``` To document a MockMvc call, you use MockMvc's `andDo` method, passing it a `RestDocumentationResultHandler` that can be easily obtained from diff --git a/samples/rest-notes-spring-data-rest/src/test/java/com/example/notes/ApiDocumentation.java b/samples/rest-notes-spring-data-rest/src/test/java/com/example/notes/ApiDocumentation.java index 7e1f7933..80a762ad 100644 --- a/samples/rest-notes-spring-data-rest/src/test/java/com/example/notes/ApiDocumentation.java +++ b/samples/rest-notes-spring-data-rest/src/test/java/com/example/notes/ApiDocumentation.java @@ -18,6 +18,7 @@ package com.example.notes; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; +import static org.springframework.restdocs.RestDocumentation.documentationConfiguration; import static org.springframework.restdocs.RestDocumentation.document; import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; @@ -40,7 +41,6 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.hateoas.MediaTypes; -import org.springframework.restdocs.config.RestDocumentationConfigurer; import org.springframework.restdocs.payload.FieldType; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; @@ -72,7 +72,18 @@ public class ApiDocumentation { @Before public void setUp() { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) - .apply(new RestDocumentationConfigurer()).build(); + .apply(documentationConfiguration()).build(); + this.mockMvc = MockMvcBuilders + .webAppContextSetup(this.context) + .apply(documentationConfiguration() + .uris() + .withScheme("https") + .withHost("localhost") + .withPort(8443) + .and().snippets() + .withEncoding("ISO-8859-1")) + .build(); + } @Test diff --git a/samples/rest-notes-spring-data-rest/src/test/java/com/example/notes/GettingStartedDocumentation.java b/samples/rest-notes-spring-data-rest/src/test/java/com/example/notes/GettingStartedDocumentation.java index 1657af9a..aca7a580 100644 --- a/samples/rest-notes-spring-data-rest/src/test/java/com/example/notes/GettingStartedDocumentation.java +++ b/samples/rest-notes-spring-data-rest/src/test/java/com/example/notes/GettingStartedDocumentation.java @@ -20,6 +20,7 @@ import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.springframework.restdocs.RestDocumentation.document; +import static org.springframework.restdocs.RestDocumentation.documentationConfiguration; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; @@ -38,7 +39,6 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.hateoas.MediaTypes; -import org.springframework.restdocs.config.RestDocumentationConfigurer; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; @@ -66,7 +66,7 @@ public class GettingStartedDocumentation { @Before public void setUp() { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) - .apply(new RestDocumentationConfigurer()) + .apply(documentationConfiguration()) .alwaysDo(document("{method-name}/{step}/")) .build(); } diff --git a/samples/rest-notes-spring-hateoas/src/test/java/com/example/notes/ApiDocumentation.java b/samples/rest-notes-spring-hateoas/src/test/java/com/example/notes/ApiDocumentation.java index f8ce6fa1..4f397569 100644 --- a/samples/rest-notes-spring-hateoas/src/test/java/com/example/notes/ApiDocumentation.java +++ b/samples/rest-notes-spring-hateoas/src/test/java/com/example/notes/ApiDocumentation.java @@ -19,6 +19,7 @@ package com.example.notes; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.springframework.restdocs.RestDocumentation.document; +import static org.springframework.restdocs.RestDocumentation.documentationConfiguration; import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -40,7 +41,6 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.hateoas.MediaTypes; -import org.springframework.restdocs.config.RestDocumentationConfigurer; import org.springframework.restdocs.payload.FieldType; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; @@ -72,7 +72,7 @@ public class ApiDocumentation { @Before public void setUp() { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) - .apply(new RestDocumentationConfigurer()).build(); + .apply(documentationConfiguration()).build(); } @Test diff --git a/samples/rest-notes-spring-hateoas/src/test/java/com/example/notes/GettingStartedDocumentation.java b/samples/rest-notes-spring-hateoas/src/test/java/com/example/notes/GettingStartedDocumentation.java index 5b95a35a..2ca0d48b 100644 --- a/samples/rest-notes-spring-hateoas/src/test/java/com/example/notes/GettingStartedDocumentation.java +++ b/samples/rest-notes-spring-hateoas/src/test/java/com/example/notes/GettingStartedDocumentation.java @@ -20,6 +20,7 @@ import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.springframework.restdocs.RestDocumentation.document; +import static org.springframework.restdocs.RestDocumentation.documentationConfiguration; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; @@ -38,7 +39,6 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.hateoas.MediaTypes; -import org.springframework.restdocs.config.RestDocumentationConfigurer; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; @@ -66,7 +66,7 @@ public class GettingStartedDocumentation { @Before public void setUp() { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) - .apply(new RestDocumentationConfigurer()) + .apply(documentationConfiguration()) .alwaysDo(document("{method-name}/{step}/")) .build(); } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentation.java b/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentation.java index f0320e91..d3a5226d 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentation.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentation.java @@ -16,11 +16,14 @@ package org.springframework.restdocs; +import org.springframework.restdocs.config.RestDocumentationConfigurer; import org.springframework.restdocs.response.ResponsePostProcessor; import org.springframework.restdocs.response.ResponsePostProcessors; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder; +import org.springframework.test.web.servlet.setup.MockMvcConfigurer; /** * Static factory methods for documenting RESTful APIs using Spring MVC Test @@ -33,6 +36,16 @@ public abstract class RestDocumentation { } + /** + * Provides access to a {@link MockMvcConfigurer} that can be used to configure the + * REST documentation when building a {@link MockMvc} instance. + * @see ConfigurableMockMvcBuilder#apply(MockMvcConfigurer) + * @return the configurer + */ + public static RestDocumentationConfigurer documentationConfiguration() { + return new RestDocumentationConfigurer(); + } + /** * Documents the API call to the given {@code outputDir}. * diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/config/AbstractConfigurer.java b/spring-restdocs/src/main/java/org/springframework/restdocs/config/AbstractConfigurer.java new file mode 100644 index 00000000..a8120c52 --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/config/AbstractConfigurer.java @@ -0,0 +1,35 @@ +/* + * Copyright 2014-2015 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.config; + +import org.springframework.mock.web.MockHttpServletRequest; + +/** + * Abstract configurer that declares methods that are internal to the documentation + * configuration implementation. + * + * @author Andy Wilkinson + */ +abstract class AbstractConfigurer { + + /** + * Applies the configuration, possibly be modifying the given {@code request} + * @param request the request that may be modified + */ + abstract void apply(MockHttpServletRequest request); + +} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/config/AbstractNestedConfigurer.java b/spring-restdocs/src/main/java/org/springframework/restdocs/config/AbstractNestedConfigurer.java new file mode 100644 index 00000000..ce542926 --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/config/AbstractNestedConfigurer.java @@ -0,0 +1,55 @@ +/* + * Copyright 2014-2015 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.config; + +import org.springframework.test.web.servlet.request.RequestPostProcessor; +import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder; +import org.springframework.test.web.servlet.setup.MockMvcConfigurer; +import org.springframework.web.context.WebApplicationContext; + +/** + * Base class for {@link NestedConfigurer} implementations. + * + * @author Andy Wilkinson + * @param The type of the configurer's parent + */ +abstract class AbstractNestedConfigurer extends + AbstractConfigurer implements NestedConfigurer, MockMvcConfigurer { + + private final PARENT parent; + + protected AbstractNestedConfigurer(PARENT parent) { + this.parent = parent; + } + + @Override + public PARENT and() { + return this.parent; + } + + @Override + public void afterConfigurerAdded(ConfigurableMockMvcBuilder builder) { + this.parent.afterConfigurerAdded(builder); + } + + @Override + public RequestPostProcessor beforeMockMvcCreated( + ConfigurableMockMvcBuilder builder, WebApplicationContext context) { + return this.parent.beforeMockMvcCreated(builder, context); + } + +} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/config/NestedConfigurer.java b/spring-restdocs/src/main/java/org/springframework/restdocs/config/NestedConfigurer.java new file mode 100644 index 00000000..25c57767 --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/config/NestedConfigurer.java @@ -0,0 +1,35 @@ +/* + * Copyright 2014-2015 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.config; + +import org.springframework.test.web.servlet.setup.MockMvcConfigurer; + +/** + * A configurer that is nested and, therefore, has a parent. + * + * @author awilkinson + * @param The parent's type + */ +public interface NestedConfigurer { + + /** + * Returns the configurer's parent + * + * @return the parent + */ + PARENT and(); +} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/config/RestDocumentationConfigurer.java b/spring-restdocs/src/main/java/org/springframework/restdocs/config/RestDocumentationConfigurer.java index bdb56c93..8d578146 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/config/RestDocumentationConfigurer.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/config/RestDocumentationConfigurer.java @@ -16,7 +16,11 @@ package org.springframework.restdocs.config; +import java.util.Arrays; +import java.util.List; + import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.restdocs.RestDocumentation; import org.springframework.test.web.servlet.request.RequestPostProcessor; import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder; import org.springframework.test.web.servlet.setup.MockMvcConfigurer; @@ -30,120 +34,84 @@ import org.springframework.web.context.WebApplicationContext; * @author Andy Wilkinson * @author Dmitriy Mayboroda * @see ConfigurableMockMvcBuilder#apply(MockMvcConfigurer) + * @see RestDocumentation#documentationConfiguration() */ public class RestDocumentationConfigurer extends MockMvcConfigurerAdapter { - /** - * The default scheme for documented URIs - * @see #withScheme(String) - */ - public static final String DEFAULT_SCHEME = "http"; + private final UriConfigurer uriConfigurer = new UriConfigurer(this); + + private final SnippetConfigurer snippetConfigurer = new SnippetConfigurer(this); + + private final RequestPostProcessor requestPostProcessor; /** - * The defalt host for documented URIs - * @see #withHost(String) + * Creates a new {@link RestDocumentationConfigurer}. + * @see RestDocumentation#documentationConfiguration() */ - public static final String DEFAULT_HOST = "localhost"; - - /** - * The default port for documented URIs - * @see #withPort(int) - */ - public static final int DEFAULT_PORT = 8080; - - /** - * The default context path for documented URIs - * @see #withContextPath(String) - */ - public static final String DEFAULT_CONTEXT_PATH = ""; - - private String scheme = DEFAULT_SCHEME; - - private String host = DEFAULT_HOST; - - private int port = DEFAULT_PORT; - - private String contextPath = DEFAULT_CONTEXT_PATH; - - /** - * Configures any documented URIs to use the given {@code scheme}. The default is - * {@code http}. - * - * @param scheme The URI scheme - * @return {@code this} - */ - public RestDocumentationConfigurer withScheme(String scheme) { - this.scheme = scheme; - return this; + public RestDocumentationConfigurer() { + this.requestPostProcessor = new ConfigurerApplyingRequestPostProcessor( + Arrays. asList(this.uriConfigurer, + this.snippetConfigurer, new StepCountConfigurer(), + new ContentLengthHeaderConfigurer())); } - /** - * Configures any documented URIs to use the given {@code host}. The default is - * {@code localhost}. - * - * @param host The URI host - * @return {@code this} - */ - public RestDocumentationConfigurer withHost(String host) { - this.host = host; - return this; + public UriConfigurer uris() { + return this.uriConfigurer; } - /** - * Configures any documented URIs to use the given {@code port}. The default is - * {@code 8080}. - * - * @param port The URI port - * @return {@code this} - */ - public RestDocumentationConfigurer withPort(int port) { - this.port = port; - return this; - } - - /** - * Configures any documented URIs to use the given {@code contextPath}. The default is - * an empty string. - * - * @param contextPath The context path - * @return {@code this} - */ - public RestDocumentationConfigurer withContextPath(String contextPath) { - this.contextPath = (StringUtils.hasText(contextPath) && !contextPath - .startsWith("/")) ? "/" + contextPath : contextPath; - return this; + public SnippetConfigurer snippets() { + return this.snippetConfigurer; } @Override public RequestPostProcessor beforeMockMvcCreated( ConfigurableMockMvcBuilder builder, WebApplicationContext context) { - return new RequestPostProcessor() { + return this.requestPostProcessor; + } - @Override - public MockHttpServletRequest postProcessRequest( - MockHttpServletRequest request) { - RestDocumentationContext currentContext = RestDocumentationContext - .currentContext(); - if (currentContext != null) { - currentContext.getAndIncrementStepCount(); - } - request.setScheme(RestDocumentationConfigurer.this.scheme); - request.setServerPort(RestDocumentationConfigurer.this.port); - request.setServerName(RestDocumentationConfigurer.this.host); - request.setContextPath(RestDocumentationConfigurer.this.contextPath); - configureContentLengthHeaderIfAppropriate(request); - return request; + private static class StepCountConfigurer extends AbstractConfigurer { + + @Override + void apply(MockHttpServletRequest request) { + RestDocumentationContext currentContext = RestDocumentationContext + .currentContext(); + if (currentContext != null) { + currentContext.getAndIncrementStepCount(); } + } - private void configureContentLengthHeaderIfAppropriate( - MockHttpServletRequest request) { - long contentLength = request.getContentLengthLong(); - if (contentLength > 0 - && !StringUtils.hasText(request.getHeader("Content-Length"))) { - request.addHeader("Content-Length", request.getContentLengthLong()); - } + } + + private static class ContentLengthHeaderConfigurer extends AbstractConfigurer { + + @Override + void apply(MockHttpServletRequest request) { + long contentLength = request.getContentLengthLong(); + if (contentLength > 0 + && !StringUtils.hasText(request.getHeader("Content-Length"))) { + request.addHeader("Content-Length", request.getContentLengthLong()); } + } + + } + + private static class ConfigurerApplyingRequestPostProcessor implements + RequestPostProcessor { + + private final List configurers; + + private ConfigurerApplyingRequestPostProcessor( + List configurers) { + this.configurers = configurers; + } + + @Override + public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { + for (AbstractConfigurer configurer : this.configurers) { + configurer.apply(request); + } + return request; + } - }; } } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/config/RestDocumentationContext.java b/spring-restdocs/src/main/java/org/springframework/restdocs/config/RestDocumentationContext.java index 8d409952..cbeeca1e 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/config/RestDocumentationContext.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/config/RestDocumentationContext.java @@ -33,6 +33,8 @@ public class RestDocumentationContext { private final Method testMethod; + private String snippetEncoding; + private RestDocumentationContext() { this(null); } @@ -68,6 +70,19 @@ public class RestDocumentationContext { return this.stepCount.get(); } + void setSnippetEncoding(String snippetEncoding) { + this.snippetEncoding = snippetEncoding; + } + + /** + * Gets the encoding to be used when writing snippets + * + * @return The snippet encoding + */ + public String getSnippetEncoding() { + return this.snippetEncoding; + } + static void establishContext(Method testMethod) { CONTEXTS.set(new RestDocumentationContext(testMethod)); } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/config/SnippetConfigurer.java b/spring-restdocs/src/main/java/org/springframework/restdocs/config/SnippetConfigurer.java new file mode 100644 index 00000000..e94cb331 --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/config/SnippetConfigurer.java @@ -0,0 +1,61 @@ +/* + * Copyright 2014-2015 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.config; + +import org.springframework.mock.web.MockHttpServletRequest; + +/** + * A configurer that can be used to configure the generated documentation snippets. + * + * @author Andy Wilkinson + * + */ +public class SnippetConfigurer extends + AbstractNestedConfigurer { + + /** + * The default encoding for documentation snippets + * @see #withEncoding(String) + */ + public static final String DEFAULT_SNIPPET_ENCODING = "UTF-8"; + + private String snippetEncoding = DEFAULT_SNIPPET_ENCODING; + + SnippetConfigurer(RestDocumentationConfigurer parent) { + super(parent); + } + + /** + * Configures any documentation snippets to be written using the given + * {@code encoding}. The default is UTF-8. + * @param encoding The encoding + * @return {@code this} + */ + public SnippetConfigurer withEncoding(String encoding) { + this.snippetEncoding = encoding; + return this; + } + + @Override + void apply(MockHttpServletRequest request) { + RestDocumentationContext context = RestDocumentationContext.currentContext(); + if (context != null) { + context.setSnippetEncoding(this.snippetEncoding); + } + } + +} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/config/UriConfigurer.java b/spring-restdocs/src/main/java/org/springframework/restdocs/config/UriConfigurer.java new file mode 100644 index 00000000..c0fbc03e --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/config/UriConfigurer.java @@ -0,0 +1,122 @@ +/* + * Copyright 2014-2015 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.config; + +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.util.StringUtils; + +/** + * A configurer that can be used to configure the documented URIs + * + * @author Andy Wilkinson + */ +public class UriConfigurer extends AbstractNestedConfigurer { + + /** + * The default scheme for documented URIs + * @see #withScheme(String) + */ + public static final String DEFAULT_SCHEME = "http"; + + /** + * The defalt host for documented URIs + * @see #withHost(String) + */ + public static final String DEFAULT_HOST = "localhost"; + + /** + * The default port for documented URIs + * @see #withPort(int) + */ + public static final int DEFAULT_PORT = 8080; + + /** + * The default context path for documented URIs + * @see #withContextPath(String) + */ + public static final String DEFAULT_CONTEXT_PATH = ""; + + private String scheme = DEFAULT_SCHEME; + + private String host = DEFAULT_HOST; + + private int port = DEFAULT_PORT; + + private String contextPath = DEFAULT_CONTEXT_PATH; + + protected UriConfigurer(RestDocumentationConfigurer parent) { + super(parent); + } + + /** + * Configures any documented URIs to use the given {@code scheme}. The default is + * {@code http}. + * + * @param scheme The URI scheme + * @return {@code this} + */ + public UriConfigurer withScheme(String scheme) { + this.scheme = scheme; + return this; + } + + /** + * Configures any documented URIs to use the given {@code host}. The default is + * {@code localhost}. + * + * @param host The URI host + * @return {@code this} + */ + public UriConfigurer withHost(String host) { + this.host = host; + return this; + } + + /** + * Configures any documented URIs to use the given {@code port}. The default is + * {@code 8080}. + * + * @param port The URI port + * @return {@code this} + */ + public UriConfigurer withPort(int port) { + this.port = port; + return this; + } + + /** + * Configures any documented URIs to use the given {@code contextPath}. The default is + * an empty string. + * + * @param contextPath The context path + * @return {@code this} + */ + public UriConfigurer withContextPath(String contextPath) { + this.contextPath = (StringUtils.hasText(contextPath) && !contextPath + .startsWith("/")) ? "/" + contextPath : contextPath; + return this; + } + + @Override + void apply(MockHttpServletRequest request) { + request.setScheme(this.scheme); + request.setServerPort(this.port); + request.setServerName(this.host); + request.setContextPath(this.contextPath); + } + +} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/SnippetWritingResultHandler.java b/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/SnippetWritingResultHandler.java index 819ec6c4..d77635e6 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/SnippetWritingResultHandler.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/SnippetWritingResultHandler.java @@ -17,11 +17,13 @@ package org.springframework.restdocs.snippet; import java.io.File; +import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; +import org.springframework.restdocs.config.RestDocumentationContext; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultHandler; @@ -65,7 +67,12 @@ public abstract class SnippetWritingResultHandler implements ResultHandler { throw new IllegalStateException("Failed to create directory '" + parent + "'"); } - return new FileWriter(outputFile); + RestDocumentationContext context = RestDocumentationContext.currentContext(); + if (context == null || context.getSnippetEncoding() == null) { + return new FileWriter(outputFile); + } + return new OutputStreamWriter(new FileOutputStream(outputFile), + context.getSnippetEncoding()); } else { return new OutputStreamWriter(System.out); diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/config/RestDocumentationConfigurerTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/config/RestDocumentationConfigurerTests.java index 1ec54d30..35e95c1d 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/config/RestDocumentationConfigurerTests.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/config/RestDocumentationConfigurerTests.java @@ -52,7 +52,7 @@ public class RestDocumentationConfigurerTests { @Test public void customScheme() { - RequestPostProcessor postProcessor = new RestDocumentationConfigurer() + RequestPostProcessor postProcessor = new RestDocumentationConfigurer().uris() .withScheme("https").beforeMockMvcCreated(null, null); postProcessor.postProcessRequest(this.request); @@ -61,8 +61,8 @@ public class RestDocumentationConfigurerTests { @Test public void customHost() { - RequestPostProcessor postProcessor = new RestDocumentationConfigurer().withHost( - "api.example.com").beforeMockMvcCreated(null, null); + RequestPostProcessor postProcessor = new RestDocumentationConfigurer().uris() + .withHost("api.example.com").beforeMockMvcCreated(null, null); postProcessor.postProcessRequest(this.request); assertUriConfiguration("http", "api.example.com", 8080); @@ -70,8 +70,8 @@ public class RestDocumentationConfigurerTests { @Test public void customPort() { - RequestPostProcessor postProcessor = new RestDocumentationConfigurer().withPort( - 8081).beforeMockMvcCreated(null, null); + RequestPostProcessor postProcessor = new RestDocumentationConfigurer().uris() + .withPort(8081).beforeMockMvcCreated(null, null); postProcessor.postProcessRequest(this.request); assertUriConfiguration("http", "localhost", 8081); @@ -80,7 +80,7 @@ public class RestDocumentationConfigurerTests { @Test public void customContextPathWithoutSlash() { String contextPath = "context-path"; - RequestPostProcessor postProcessor = new RestDocumentationConfigurer() + RequestPostProcessor postProcessor = new RestDocumentationConfigurer().uris() .withContextPath(contextPath).beforeMockMvcCreated(null, null); postProcessor.postProcessRequest(this.request); @@ -91,7 +91,7 @@ public class RestDocumentationConfigurerTests { @Test public void customContextPathWithSlash() { String contextPath = "/context-path"; - RequestPostProcessor postProcessor = new RestDocumentationConfigurer() + RequestPostProcessor postProcessor = new RestDocumentationConfigurer().uris() .withContextPath(contextPath).beforeMockMvcCreated(null, null); postProcessor.postProcessRequest(this.request); @@ -101,16 +101,16 @@ public class RestDocumentationConfigurerTests { @Test public void noContentLengthHeaderWhenRequestHasNotContent() { - RequestPostProcessor postProcessor = new RestDocumentationConfigurer().withPort( - 8081).beforeMockMvcCreated(null, null); + RequestPostProcessor postProcessor = new RestDocumentationConfigurer().uris() + .withPort(8081).beforeMockMvcCreated(null, null); postProcessor.postProcessRequest(this.request); assertThat(this.request.getHeader("Content-Length"), is(nullValue())); } @Test public void contentLengthHeaderIsSetWhenRequestHasContent() { - RequestPostProcessor postProcessor = new RestDocumentationConfigurer().withPort( - 8081).beforeMockMvcCreated(null, null); + RequestPostProcessor postProcessor = new RestDocumentationConfigurer() + .beforeMockMvcCreated(null, null); byte[] content = "Hello, world".getBytes(); this.request.setContent(content); postProcessor.postProcessRequest(this.request); @@ -118,6 +118,38 @@ public class RestDocumentationConfigurerTests { is(equalTo(Integer.toString(content.length)))); } + @Test + public void defaultSnippetEncodingIsAppliedToTheContext() { + RestDocumentationContext.establishContext(null); + try { + assertThat(RestDocumentationContext.currentContext().getSnippetEncoding(), + is(nullValue())); + new RestDocumentationConfigurer().beforeMockMvcCreated(null, null) + .postProcessRequest(this.request); + assertThat(RestDocumentationContext.currentContext().getSnippetEncoding(), + is(equalTo("UTF-8"))); + } + finally { + RestDocumentationContext.clearContext(); + } + } + + @Test + public void customSnippetEncodingIsAppliedToTheContext() { + RestDocumentationContext.establishContext(null); + try { + assertThat(RestDocumentationContext.currentContext().getSnippetEncoding(), + is(nullValue())); + new RestDocumentationConfigurer().snippets().withEncoding("foo") + .beforeMockMvcCreated(null, null).postProcessRequest(this.request); + assertThat(RestDocumentationContext.currentContext().getSnippetEncoding(), + is(equalTo("foo"))); + } + finally { + RestDocumentationContext.clearContext(); + } + } + private void assertUriConfiguration(String scheme, String host, int port) { assertEquals(scheme, this.request.getScheme()); assertEquals(host, this.request.getServerName());