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());