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:
@@ -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'
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|Relation|Description
|
||||
|
||||
{{#links}}
|
||||
|`{{rel}}`
|
||||
|{{description}}
|
||||
|{{#tableCellContent}}`{{rel}}`{{/tableCellContent}}
|
||||
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
|
||||
|
||||
{{/links}}
|
||||
|===
|
||||
@@ -3,8 +3,8 @@
|
||||
|Parameter|Description
|
||||
|
||||
{{#parameters}}
|
||||
|`{{name}}`
|
||||
|{{description}}
|
||||
|{{#tableCellContent}}`{{name}}`{{/tableCellContent}}
|
||||
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
|
||||
|
||||
{{/parameters}}
|
||||
|===
|
||||
@@ -2,9 +2,9 @@
|
||||
|Path|Type|Description
|
||||
|
||||
{{#fields}}
|
||||
|`{{path}}`
|
||||
|`{{type}}`
|
||||
|{{description}}
|
||||
|{{#tableCellContent}}`{{path}}`{{/tableCellContent}}
|
||||
|{{#tableCellContent}}`{{type}}`{{/tableCellContent}}
|
||||
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
|
||||
|
||||
{{/fields}}
|
||||
|===
|
||||
@@ -2,8 +2,8 @@
|
||||
|Name|Description
|
||||
|
||||
{{#headers}}
|
||||
|`{{name}}`
|
||||
|{{description}}
|
||||
|{{#tableCellContent}}`{{name}}`{{/tableCellContent}}
|
||||
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
|
||||
|
||||
{{/headers}}
|
||||
|===
|
||||
@@ -2,8 +2,8 @@
|
||||
|Parameter|Description
|
||||
|
||||
{{#parameters}}
|
||||
|`{{name}}`
|
||||
|{{description}}
|
||||
|{{#tableCellContent}}`{{name}}`{{/tableCellContent}}
|
||||
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
|
||||
|
||||
{{/parameters}}
|
||||
|===
|
||||
@@ -2,8 +2,8 @@
|
||||
|Part|Description
|
||||
|
||||
{{#requestParts}}
|
||||
|`{{name}}`
|
||||
|{{description}}
|
||||
|{{#tableCellContent}}`{{name}}`{{/tableCellContent}}
|
||||
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
|
||||
|
||||
{{/requestParts}}
|
||||
|===
|
||||
@@ -2,9 +2,9 @@
|
||||
|Path|Type|Description
|
||||
|
||||
{{#fields}}
|
||||
|`{{path}}`
|
||||
|`{{type}}`
|
||||
|{{description}}
|
||||
|{{#tableCellContent}}`{{path}}`{{/tableCellContent}}
|
||||
|{{#tableCellContent}}`{{type}}`{{/tableCellContent}}
|
||||
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
|
||||
|
||||
{{/fields}}
|
||||
|===
|
||||
@@ -2,8 +2,8 @@
|
||||
|Name|Description
|
||||
|
||||
{{#headers}}
|
||||
|`{{name}}`
|
||||
|{{description}}
|
||||
|{{#tableCellContent}}`{{name}}`{{/tableCellContent}}
|
||||
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
|
||||
|
||||
{{/headers}}
|
||||
|===
|
||||
@@ -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");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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("|", "\\|");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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("|", "\\|");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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("|", "\\|");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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("|", "\\|");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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("|", "\\|");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 + "`";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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("|", "\\|");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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("|", "\\|");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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\\|")));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user