Avoid problems caused by | characters in table cell content

Previously, a | character in a cell of an Asciidoctor table would
break that table's formatting as it would be interpretted as a
delimiter for the cell. The problem also affects Markdown tables where
the | character is not within backticks.

This commit addresses the problem with Asciidoctor tables by
introducing a JMustache Lambda that escapes | characters by prefixing
them with a backslash.

Unfortunately, a complete solution to the problem when using Markdown
is not as straightforward. Markdown only requires a | character to be
espaced when it is not within backticks. Determing this isn't
straightforward as the text would have to be parsed, taking into
account the possibility of backticks themselves being escaped.
Instead, this commit attempts to avoid the problem in a different way.

The most likely source of a | character is when documenting a
`@Pattern` constraint that uses a | character in its regular
expression. To improve the readability of the regular expression this
commit wraps it in backticks, thereby formatting it in a monospaced
font in both Asciidoctor and Markdown and also avoiding any escaping
problems with the latter.

Closes gh-232
See gh-230
This commit is contained in:
Andy Wilkinson
2016-05-24 10:50:32 +01:00
parent 65e55ac15c
commit 404b99b0a4
27 changed files with 393 additions and 29 deletions

View File

@@ -38,6 +38,7 @@ dependencies {
testCompile 'org.hamcrest:hamcrest-core'
testCompile 'org.hamcrest:hamcrest-library'
testCompile 'org.hibernate:hibernate-validator'
testCompile 'org.springframework:spring-test'
testRuntime 'org.glassfish:javax.el:3.0.0'
}

View File

@@ -17,15 +17,19 @@
package org.springframework.restdocs.config;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.restdocs.RestDocumentationContext;
import org.springframework.restdocs.mustache.Mustache;
import org.springframework.restdocs.snippet.RestDocumentationContextPlaceholderResolver;
import org.springframework.restdocs.snippet.StandardWriterResolver;
import org.springframework.restdocs.snippet.WriterResolver;
import org.springframework.restdocs.templates.StandardTemplateResourceResolver;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.templates.mustache.AsciidoctorTableCellContentLambda;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
/**
@@ -102,9 +106,16 @@ public abstract class RestDocumentationConfigurer<S extends AbstractConfigurer,
if (engineToUse == null) {
SnippetConfiguration snippetConfiguration = (SnippetConfiguration) configuration
.get(SnippetConfiguration.class.getName());
Map<String, Object> templateContext = new HashMap<>();
if (snippetConfiguration.getTemplateFormat().getId()
.equals(TemplateFormats.asciidoctor().getId())) {
templateContext.put("tableCellContent",
new AsciidoctorTableCellContentLambda());
}
engineToUse = new MustacheTemplateEngine(
new StandardTemplateResourceResolver(
snippetConfiguration.getTemplateFormat()));
snippetConfiguration.getTemplateFormat()),
Mustache.compiler().escapeHTML(false), templateContext);
}
configuration.put(TemplateEngine.class.getName(), engineToUse);
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2014-2016 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.templates.mustache;
import java.io.IOException;
import java.io.Writer;
import org.springframework.restdocs.mustache.Mustache.Lambda;
import org.springframework.restdocs.mustache.Template.Fragment;
/**
* A {@link Lambda} that escapes {@code |} characters so that the do not break the table's
* formatting.
*
* @author Andy Wilkinson
*/
public final class AsciidoctorTableCellContentLambda implements Lambda {
@Override
public void execute(Fragment fragment, Writer writer) throws IOException {
String output = fragment.execute();
for (int i = 0; i < output.length(); i++) {
char current = output.charAt(i);
if (current == '|' && (i == 0 || output.charAt(i - 1) != '\\')) {
writer.append('\\');
}
writer.append(current);
}
}
}

View File

@@ -16,6 +16,8 @@
package org.springframework.restdocs.templates.mustache;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.springframework.restdocs.templates.Template;
@@ -30,18 +32,37 @@ public class MustacheTemplate implements Template {
private final org.springframework.restdocs.mustache.Template delegate;
private final Map<String, Object> context;
/**
* Creates a new {@code MustacheTemplate} that adapts the given {@code delegate}.
*
* @param delegate The delegate to adapt
*/
public MustacheTemplate(org.springframework.restdocs.mustache.Template delegate) {
this(delegate, Collections.<String, Object>emptyMap());
}
/**
* Creates a new {@code MustacheTemplate} that adapts the given {@code delegate}.
* During rendering, the given {@code context} and the context passed into
* {@link #render(Map)} will be combined and then passed to the delegate when it is
* {@link org.springframework.restdocs.mustache.Template#execute executed}.
*
* @param delegate The delegate to adapt
* @param context The context
*/
public MustacheTemplate(org.springframework.restdocs.mustache.Template delegate,
Map<String, Object> context) {
this.delegate = delegate;
this.context = context;
}
@Override
public String render(Map<String, Object> context) {
return this.delegate.execute(context);
Map<String, Object> combinedContext = new HashMap<>(this.context);
combinedContext.putAll(context);
return this.delegate.execute(combinedContext);
}
}

View File

@@ -18,6 +18,8 @@ package org.springframework.restdocs.templates.mustache;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Collections;
import java.util.Map;
import org.springframework.core.io.Resource;
import org.springframework.restdocs.mustache.Mustache;
@@ -36,9 +38,11 @@ import org.springframework.restdocs.templates.TemplateResourceResolver;
*/
public class MustacheTemplateEngine implements TemplateEngine {
private final TemplateResourceResolver templateResourceResolver;
private final Compiler compiler;
private final TemplateResourceResolver templateResourceResolver;
private final Map<String, Object> context;
/**
* Creates a new {@code MustacheTemplateEngine} that will use the given
@@ -60,16 +64,36 @@ public class MustacheTemplateEngine implements TemplateEngine {
*/
public MustacheTemplateEngine(TemplateResourceResolver templateResourceResolver,
Compiler compiler) {
this(templateResourceResolver, compiler, Collections.<String, Object>emptyMap());
}
/**
* Creates a new {@code MustacheTemplateEngine} that will use the given
* {@code templateResourceResolver} to resolve templates and the given
* {@code compiler} to compile them. Compiled templates will be created with the given
* {@code context}.
*
* @param templateResourceResolver the resolver to use
* @param compiler the compiler to use
* @param context the context to pass to compiled templates
* @see MustacheTemplate#MustacheTemplate(org.springframework.restdocs.mustache.Template,
* Map)
*/
public MustacheTemplateEngine(TemplateResourceResolver templateResourceResolver,
Compiler compiler, Map<String, Object> context) {
this.templateResourceResolver = templateResourceResolver;
this.compiler = compiler;
this.context = context;
}
@Override
public Template compileTemplate(String name) throws IOException {
Resource templateResource = this.templateResourceResolver
.resolveTemplateResource(name);
return new MustacheTemplate(this.compiler
.compile(new InputStreamReader(templateResource.getInputStream())));
return new MustacheTemplate(
this.compiler.compile(
new InputStreamReader(templateResource.getInputStream())),
this.context);
}
/**

View File

@@ -9,7 +9,7 @@ javax.validation.constraints.Min.description=Must be at least ${value}
javax.validation.constraints.NotNull.description=Must not be null
javax.validation.constraints.Null.description=Must be null
javax.validation.constraints.Past.description=Must be in the past
javax.validation.constraints.Pattern.description=Must match the regular expression '${regexp}'
javax.validation.constraints.Pattern.description=Must match the regular expression `${regexp}`
javax.validation.constraints.Size.description=Size must be between ${min} and ${max} inclusive
org.hibernate.validator.constraints.CreditCardNumber.description=Must be a well-formed credit card number
org.hibernate.validator.constraints.EAN.description=Must be a well-formed ${type} number

View File

@@ -2,8 +2,8 @@
|Relation|Description
{{#links}}
|`{{rel}}`
|{{description}}
|{{#tableCellContent}}`{{rel}}`{{/tableCellContent}}
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
{{/links}}
|===

View File

@@ -3,8 +3,8 @@
|Parameter|Description
{{#parameters}}
|`{{name}}`
|{{description}}
|{{#tableCellContent}}`{{name}}`{{/tableCellContent}}
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
{{/parameters}}
|===

View File

@@ -2,9 +2,9 @@
|Path|Type|Description
{{#fields}}
|`{{path}}`
|`{{type}}`
|{{description}}
|{{#tableCellContent}}`{{path}}`{{/tableCellContent}}
|{{#tableCellContent}}`{{type}}`{{/tableCellContent}}
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
{{/fields}}
|===

View File

@@ -2,8 +2,8 @@
|Name|Description
{{#headers}}
|`{{name}}`
|{{description}}
|{{#tableCellContent}}`{{name}}`{{/tableCellContent}}
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
{{/headers}}
|===

View File

@@ -2,8 +2,8 @@
|Parameter|Description
{{#parameters}}
|`{{name}}`
|{{description}}
|{{#tableCellContent}}`{{name}}`{{/tableCellContent}}
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
{{/parameters}}
|===

View File

@@ -2,8 +2,8 @@
|Part|Description
{{#requestParts}}
|`{{name}}`
|{{description}}
|{{#tableCellContent}}`{{name}}`{{/tableCellContent}}
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
{{/requestParts}}
|===

View File

@@ -2,9 +2,9 @@
|Path|Type|Description
{{#fields}}
|`{{path}}`
|`{{type}}`
|{{description}}
|{{#tableCellContent}}`{{path}}`{{/tableCellContent}}
|{{#tableCellContent}}`{{type}}`{{/tableCellContent}}
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
{{/fields}}
|===

View File

@@ -2,8 +2,8 @@
|Name|Description
{{#headers}}
|`{{name}}`
|{{description}}
|{{#tableCellContent}}`{{name}}`{{/tableCellContent}}
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
{{/headers}}
|===

View File

@@ -36,7 +36,9 @@ import org.springframework.restdocs.snippet.StandardWriterResolver;
import org.springframework.restdocs.snippet.WriterResolver;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.templates.mustache.AsciidoctorTableCellContentLambda;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import org.springframework.test.util.ReflectionTestUtils;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
@@ -164,6 +166,34 @@ public class RestDocumentationConfigurerTests {
is(equalTo(TemplateFormats.markdown())));
}
@SuppressWarnings("unchecked")
@Test
public void asciidoctorTableCellContentLambaIsInstalledWhenUsingAsciidoctorTemplateFormat() {
Map<String, Object> configuration = new HashMap<>();
this.configurer.apply(configuration, createContext());
TemplateEngine templateEngine = (TemplateEngine) configuration
.get(TemplateEngine.class.getName());
MustacheTemplateEngine mustacheTemplateEngine = (MustacheTemplateEngine) templateEngine;
Map<String, Object> templateContext = (Map<String, Object>) ReflectionTestUtils
.getField(mustacheTemplateEngine, "context");
assertThat(templateContext, hasEntry(equalTo("tableCellContent"),
instanceOf(AsciidoctorTableCellContentLambda.class)));
}
@SuppressWarnings("unchecked")
@Test
public void asciidoctorTableCellContentLambaIsNotInstalledWhenUsingNonAsciidoctorTemplateFormat() {
Map<String, Object> configuration = new HashMap<>();
this.configurer.snippetConfigurer.withTemplateFormat(TemplateFormats.markdown());
this.configurer.apply(configuration, createContext());
TemplateEngine templateEngine = (TemplateEngine) configuration
.get(TemplateEngine.class.getName());
MustacheTemplateEngine mustacheTemplateEngine = (MustacheTemplateEngine) templateEngine;
Map<String, Object> templateContext = (Map<String, Object>) ReflectionTestUtils
.getField(mustacheTemplateEngine, "context");
assertThat(templateContext.size(), equalTo(0));
}
private RestDocumentationContext createContext() {
ManualRestDocumentation manualRestDocumentation = new ManualRestDocumentation(
"build");

View File

@@ -118,7 +118,7 @@ public class ConstraintDescriptionsTests {
@Test
public void pattern() {
assertThat(this.constraintDescriptions.descriptionsForProperty("pattern"),
contains("Must match the regular expression '[A-Z][a-z]+'"));
contains("Must match the regular expression `[A-Z][a-z]+`"));
}
@Test

View File

@@ -137,7 +137,7 @@ public class ResourceBundleConstraintDescriptionResolverTests {
@Test
public void defaultMessagePattern() {
assertThat(constraintDescriptionForField("pattern"),
is(equalTo("Must match the regular expression '[A-Z][a-z]+'")));
is(equalTo("Must match the regular expression `[A-Z][a-z]+`")));
}
@Test

View File

@@ -24,6 +24,7 @@ import org.junit.Test;
import org.springframework.restdocs.AbstractSnippetTests;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import org.springframework.restdocs.test.OperationBuilder;
@@ -165,4 +166,23 @@ public class RequestHeadersSnippetTests extends AbstractSnippetTests {
.header("Connection", "keep-alive").build());
}
@Test
public void tableCellContentIsEscapedWhenNecessary() throws IOException {
this.snippet.expectRequestHeaders("request-with-escaped-headers").withContents(
tableWithHeader("Name", "Description").row(escapeIfNecessary("`Foo|Bar`"),
escapeIfNecessary("one|two")));
new RequestHeadersSnippet(
Arrays.asList(headerWithName("Foo|Bar").description("one|two")))
.document(operationBuilder("request-with-escaped-headers")
.request("http://localhost").header("Foo|Bar", "baz")
.build());
}
private String escapeIfNecessary(String input) {
if (this.templateFormat.equals(TemplateFormats.markdown())) {
return input;
}
return input.replace("|", "\\|");
}
}

View File

@@ -24,6 +24,7 @@ import org.junit.Test;
import org.springframework.restdocs.AbstractSnippetTests;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
import org.springframework.restdocs.test.OperationBuilder;
@@ -155,4 +156,22 @@ public class ResponseHeadersSnippetTests extends AbstractSnippetTests {
.build());
}
@Test
public void tableCellContentIsEscapedWhenNecessary() throws IOException {
this.snippet.expectResponseHeaders("response-with-escaped-headers").withContents(
tableWithHeader("Name", "Description").row(escapeIfNecessary("`Foo|Bar`"),
escapeIfNecessary("one|two")));
new ResponseHeadersSnippet(
Arrays.asList(headerWithName("Foo|Bar").description("one|two")))
.document(operationBuilder("response-with-escaped-headers")
.response().header("Foo|Bar", "baz").build());
}
private String escapeIfNecessary(String input) {
if (this.templateFormat.equals(TemplateFormats.markdown())) {
return input;
}
return input.replace("|", "\\|");
}
}

View File

@@ -24,6 +24,7 @@ import org.junit.Test;
import org.springframework.restdocs.AbstractSnippetTests;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
@@ -171,4 +172,22 @@ public class LinksSnippetTests extends AbstractSnippetTests {
.and(new LinkDescriptor("b").description("two"))
.document(operationBuilder("additional-descriptors").build());
}
@Test
public void tableCellContentIsEscapedWhenNecessary() throws IOException {
this.snippet.expectLinks("links-with-escaped-content")
.withContents(tableWithHeader("Relation", "Description").row(
escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("one|two")));
new LinksSnippet(new StubLinkExtractor().withLinks(new Link("Foo|Bar", "foo")),
Arrays.asList(new LinkDescriptor("Foo|Bar").description("one|two")))
.document(operationBuilder("links-with-escaped-content").build());
}
private String escapeIfNecessary(String input) {
if (this.templateFormat.equals(TemplateFormats.markdown())) {
return input;
}
return input.replace("|", "\\|");
}
}

View File

@@ -26,6 +26,7 @@ import org.springframework.http.MediaType;
import org.springframework.restdocs.AbstractSnippetTests;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
@@ -227,4 +228,25 @@ public class RequestFieldsSnippetTests extends AbstractSnippetTests {
.content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}").build());
}
@Test
public void requestWithFieldsWithEscapedContent() throws IOException {
this.snippet.expectRequestFields("request-fields-with-escaped-content")
.withContents(tableWithHeader("Path", "Type", "Description").row(
escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("`one|two`"),
escapeIfNecessary("three|four")));
new RequestFieldsSnippet(Arrays.asList(
fieldWithPath("Foo|Bar").type("one|two").description("three|four")))
.document(operationBuilder("request-fields-with-escaped-content")
.request("http://localhost").content("{\"Foo|Bar\": 5}")
.build());
}
private String escapeIfNecessary(String input) {
if (this.templateFormat.equals(TemplateFormats.markdown())) {
return input;
}
return input.replace("|", "\\|");
}
}

View File

@@ -26,6 +26,7 @@ import org.springframework.http.MediaType;
import org.springframework.restdocs.AbstractSnippetTests;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
@@ -283,4 +284,24 @@ public class ResponseFieldsSnippetTests extends AbstractSnippetTests {
.content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}").build());
}
@Test
public void responseWithFieldsWithEscapedContent() throws IOException {
this.snippet.expectResponseFields("response-fields-with-escaped-content")
.withContents(tableWithHeader("Path", "Type", "Description").row(
escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("`one|two`"),
escapeIfNecessary("three|four")));
new ResponseFieldsSnippet(Arrays.asList(
fieldWithPath("Foo|Bar").type("one|two").description("three|four")))
.document(operationBuilder("response-fields-with-escaped-content")
.response().content("{\"Foo|Bar\": 5}").build());
}
private String escapeIfNecessary(String input) {
if (this.templateFormat.equals(TemplateFormats.markdown())) {
return input;
}
return input.replace("|", "\\|");
}
}

View File

@@ -192,9 +192,37 @@ public class PathParametersSnippetTests extends AbstractSnippetTests {
.build());
}
@Test
public void pathParametersWithEscapedContent() throws IOException {
this.snippet.expectPathParameters("path-parameters-with-escaped-content")
.withContents(tableWithTitleAndHeader(getTitle("{Foo|Bar}"), "Parameter",
"Description").row(escapeIfNecessary("`Foo|Bar`"),
escapeIfNecessary("one|two")));
RequestDocumentation
.pathParameters(parameterWithName("Foo|Bar").description("one|two"))
.document(operationBuilder("path-parameters-with-escaped-content")
.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE,
"{Foo|Bar}")
.build());
}
private String escapeIfNecessary(String input) {
if (this.templateFormat.equals(TemplateFormats.markdown())) {
return input;
}
return input.replace("|", "\\|");
}
private String getTitle() {
return this.templateFormat == TemplateFormats.asciidoctor() ? "/{a}/{b}"
: "`/{a}/{b}`";
return getTitle("/{a}/{b}");
}
private String getTitle(String title) {
if (this.templateFormat.equals(TemplateFormats.asciidoctor())) {
return title;
}
return "`" + title + "`";
}
}

View File

@@ -24,6 +24,7 @@ import org.junit.Test;
import org.springframework.restdocs.AbstractSnippetTests;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
@@ -194,4 +195,23 @@ public class RequestParametersSnippetTests extends AbstractSnippetTests {
.param("b", "bravo").build());
}
@Test
public void requestParametersWithEscapedContent() throws IOException {
this.snippet.expectRequestParameters("request-parameters-with-escaped-content")
.withContents(tableWithHeader("Parameter", "Description").row(
escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("one|two")));
RequestDocumentation
.requestParameters(parameterWithName("Foo|Bar").description("one|two"))
.document(operationBuilder("request-parameters-with-escaped-content")
.request("http://localhost").param("Foo|Bar", "baz").build());
}
private String escapeIfNecessary(String input) {
if (this.templateFormat.equals(TemplateFormats.markdown())) {
return input;
}
return input.replace("|", "\\|");
}
}

View File

@@ -24,6 +24,7 @@ import org.junit.Test;
import org.springframework.restdocs.AbstractSnippetTests;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.templates.TemplateResourceResolver;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
@@ -180,4 +181,23 @@ public class RequestPartsSnippetTests extends AbstractSnippetTests {
.part("b", "bravo".getBytes()).build());
}
@Test
public void requestPartsWithEscapedContent() throws IOException {
this.snippet.expectRequestParts("request-parts-with-escaped-content")
.withContents(tableWithHeader("Part", "Description").row(
escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("one|two")));
RequestDocumentation.requestParts(partWithName("Foo|Bar").description("one|two"))
.document(operationBuilder("request-parts-with-escaped-content")
.request("http://localhost").part("Foo|Bar", "baz".getBytes())
.build());
}
private String escapeIfNecessary(String input) {
if (this.templateFormat.equals(TemplateFormats.markdown())) {
return input;
}
return input.replace("|", "\\|");
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2014-2016 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.templates.mustache;
import java.io.IOException;
import java.io.StringWriter;
import org.junit.Test;
import org.springframework.restdocs.mustache.Template.Fragment;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link AsciidoctorTableCellContentLambda}.
*
* @author Andy Wilkinson
*/
public class AsciidoctorTableCellContentLambdaTests {
@Test
public void verticalBarCharactersAreEscaped() throws IOException {
Fragment fragment = mock(Fragment.class);
given(fragment.execute()).willReturn("|foo|bar|baz|");
StringWriter writer = new StringWriter();
new AsciidoctorTableCellContentLambda().execute(fragment, writer);
assertThat(writer.toString(), is(equalTo("\\|foo\\|bar\\|baz\\|")));
}
@Test
public void escapedVerticalBarCharactersAreNotEscapedAgain() throws IOException {
Fragment fragment = mock(Fragment.class);
given(fragment.execute()).willReturn("\\|foo|bar\\|baz|");
StringWriter writer = new StringWriter();
new AsciidoctorTableCellContentLambda().execute(fragment, writer);
assertThat(writer.toString(), is(equalTo("\\|foo\\|bar\\|baz\\|")));
}
}

View File

@@ -29,6 +29,7 @@ import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.restdocs.ManualRestDocumentation;
import org.springframework.restdocs.RestDocumentationContext;
import org.springframework.restdocs.mustache.Mustache;
import org.springframework.restdocs.operation.Operation;
import org.springframework.restdocs.operation.OperationRequest;
import org.springframework.restdocs.operation.OperationRequestFactory;
@@ -45,6 +46,7 @@ import org.springframework.restdocs.templates.StandardTemplateResourceResolver;
import org.springframework.restdocs.templates.TemplateEngine;
import org.springframework.restdocs.templates.TemplateFormat;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.restdocs.templates.mustache.AsciidoctorTableCellContentLambda;
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
/**
@@ -93,9 +95,13 @@ public class OperationBuilder {
public Operation build() {
if (this.attributes.get(TemplateEngine.class.getName()) == null) {
Map<String, Object> templateContext = new HashMap<>();
templateContext.put("tableCellContent",
new AsciidoctorTableCellContentLambda());
this.attributes.put(TemplateEngine.class.getName(),
new MustacheTemplateEngine(
new StandardTemplateResourceResolver(this.templateFormat)));
new StandardTemplateResourceResolver(this.templateFormat),
Mustache.compiler().escapeHTML(false), templateContext));
}
RestDocumentationContext context = createContext();
this.attributes.put(RestDocumentationContext.class.getName(), context);