Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid generating uncompilable response body in Spring's API template #8691

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import io.swagger.codegen.languages.features.BeanValidationFeatures;
import io.swagger.codegen.languages.features.NotNullAnnotationFeatures;
import io.swagger.codegen.languages.features.OptionalFeatures;
import io.swagger.codegen.mustache.SplitStringLambda;
import io.swagger.codegen.mustache.TrimWhitespaceLambda;
import io.swagger.models.Operation;
import io.swagger.models.Path;
import io.swagger.models.Swagger;
Expand Down Expand Up @@ -361,6 +363,10 @@ public void execute(Template.Fragment fragment, Writer writer) throws IOExceptio
writer.write(fragment.execute().replaceAll("\\r|\\n", ""));
}
});

additionalProperties.put("lambdaTrimWhitespace", new TrimWhitespaceLambda());

additionalProperties.put("lambdaSplitString", new SplitStringLambda());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package io.swagger.codegen.mustache;

import java.io.IOException;
import java.io.Writer;

import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Template.Fragment;

/**
* Splits long fragments into smaller strings and uses a StringBuilder to merge
* them back.
*
* Register:
*
* <pre>
* additionalProperties.put("lambdaSplitString", new SplitStringLambda());
* </pre>
*
* Use:
*
* <pre>
* {{#lambdaSplitString}}{{summary}}{{/lambdaSplitString}}
* </pre>
*/
public class SplitStringLambda implements Mustache.Lambda {
private static final int DEFAULT_MAX_LENGTH = 65535;

private static final String SPLIT_INIT = "new StringBuilder(%d)";

private static final String SPLIT_PART = ".append(\"%s\")";

private static final String SPLIT_SUFFIX = ".toString()";

private final int maxLength;

public SplitStringLambda() {
this(DEFAULT_MAX_LENGTH);
}

public SplitStringLambda(int maxLength) {
this.maxLength = maxLength;
}

@Override
public void execute(Fragment fragment, Writer writer) throws IOException {
String input = fragment.execute();
int inputLength = input.length();

StringBuilder builder = new StringBuilder();
if (inputLength > maxLength) {

// Initialize a StringBuilder
builder.append(String.format(SPLIT_INIT, inputLength));

int currentPosition = 0;
int currentStringLength = 0;
char currentLastChar = '\\';

// Split input into parts of at most maxLength and not ending with an escape character
// Append each part to the StringBuilder
while (currentPosition + maxLength < input.length()) {
currentStringLength = maxLength;
currentLastChar = input.charAt(currentPosition + currentStringLength - 1);
if (currentLastChar == '\\') {
--currentStringLength;
}

builder.append(String.format(SPLIT_PART, input.substring(currentPosition, currentPosition + currentStringLength)));
currentPosition += currentStringLength;
}

// Append last part if necessary
if (currentPosition < input.length()) {
builder.append(String.format(SPLIT_PART, input.substring(currentPosition)));
}

// Close the builder and merge everything back to a string
builder.append(SPLIT_SUFFIX);
} else {
builder.append(String.format("\"%s\"", input));
}

writer.write(builder.toString());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package io.swagger.codegen.mustache;

import java.io.IOException;
import java.io.Writer;

import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Template.Fragment;

/**
* Replaces duplicate whitespace characters in a fragment with single space.
*
* Register:
*
* <pre>
* additionalProperties.put("lambdaTrimWhitespace", new TrimWhitespaceLambda());
* </pre>
*
* Use:
*
* <pre>
* {{#lambdaTrimWhitespace}}{{summary}}{{/lambdaTrimWhitespace}}
* </pre>
*/
public class TrimWhitespaceLambda implements Mustache.Lambda {
private static final String SINGLE_SPACE = " ";

private static final String WHITESPACE_REGEX = "\\s+";

@Override
public void execute(Fragment fragment, Writer writer) throws IOException {
writer.write(fragment.execute().replaceAll(WHITESPACE_REGEX, SINGLE_SPACE));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ public interface {{classname}} {
{{#examples}}
if (getAcceptHeader().get().contains("{{{contentType}}}")) {
try {
return {{#async}}CompletableFuture.completedFuture({{/async}}new ResponseEntity<>(getObjectMapper().get().readValue("{{#lambdaRemoveLineBreak}}{{#lambdaEscapeDoubleQuote}}{{{example}}}{{/lambdaEscapeDoubleQuote}}{{/lambdaRemoveLineBreak}}", {{>exampleReturnTypes}}.class), HttpStatus.NOT_IMPLEMENTED){{#async}}){{/async}};
String exampleString = {{#lambdaSplitString}}{{#lambdaRemoveLineBreak}}{{#lambdaEscapeDoubleQuote}}{{#lambdaTrimWhitespace}}{{{example}}}{{/lambdaTrimWhitespace}}{{/lambdaEscapeDoubleQuote}}{{/lambdaRemoveLineBreak}}{{/lambdaSplitString}};
return {{#async}}CompletableFuture.completedFuture({{/async}}new ResponseEntity<>(getObjectMapper().get().readValue(exampleString, {{>exampleReturnTypes}}.class), HttpStatus.NOT_IMPLEMENTED){{#async}}){{/async}};
} catch (IOException e) {
log.error("Couldn't serialize response for content type {{{contentType}}}", e);
return {{#async}}CompletableFuture.completedFuture({{/async}}new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR){{#async}}){{/async}};
Expand All @@ -136,4 +137,4 @@ public interface {{classname}} {

{{/operation}}
}
{{/operations}}
{{/operations}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package io.swagger.codegen.mustache;

import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;

import java.io.IOException;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;

import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import com.samskivert.mustache.Template.Fragment;

public class SplitStringLambdaTest {
private static final String INPUT_STRING = "1112223334";

private static final Map<Integer, String> EXPECTED_OUTPUTS;
static {
EXPECTED_OUTPUTS = new HashMap<>();
EXPECTED_OUTPUTS.put(2,
String.format(
"new StringBuilder(%d).append(\"11\").append(\"12\").append(\"22\").append(\"33\").append(\"34\").toString()",
INPUT_STRING.length()));
EXPECTED_OUTPUTS.put(3,
String.format(
"new StringBuilder(%d).append(\"111\").append(\"222\").append(\"333\").append(\"4\").toString()",
INPUT_STRING.length()));
}

private static final String INPUT_QUOTED_STRING = "1\\\"11\\\"2223\\\"334";
private static final String INPUT_QUOTED_OUTPUT = String.format(
"new StringBuilder(%d).append(\"1\\\"\").append(\"11\").append(\"\\\"2\").append(\"223\").append(\"\\\"3\").append(\"34\").toString()",
INPUT_QUOTED_STRING.length());

@Mock
private Fragment fragment;

@BeforeMethod
public void init() {
MockitoAnnotations.initMocks(this);
}

@AfterMethod
public void reset() {
Mockito.reset(fragment);
}

private void testString(String input, int maxLength, String expected) throws IOException {
when(fragment.execute()).thenReturn(input);

StringWriter output = new StringWriter();
new SplitStringLambda(maxLength).execute(fragment, output);
assertEquals(output.toString(), expected);
}

@Test
public void testSplitGroupsOf2() throws IOException {
int maxLength = 2;
testString(INPUT_STRING, maxLength, EXPECTED_OUTPUTS.get(maxLength));
}

@Test
public void testSplitGroupsOf3() throws IOException {
int maxLength = 3;
testString(INPUT_STRING, maxLength, EXPECTED_OUTPUTS.get(maxLength));
}

@Test
public void testSplitQuotedString() throws IOException {
int maxLength = 3;
testString(INPUT_QUOTED_STRING, maxLength, INPUT_QUOTED_OUTPUT);
}

@Test
public void testShortString() throws IOException {
testString(INPUT_STRING, INPUT_STRING.length(), String.format("\"%s\"", INPUT_STRING));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package io.swagger.codegen.mustache;

import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;

import java.io.IOException;
import java.io.StringWriter;

import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import com.samskivert.mustache.Template.Fragment;

public class TrimWhitespaceLambdaTest {

@Mock
private Fragment fragment;

@BeforeMethod
public void init() {
MockitoAnnotations.initMocks(this);
}

@AfterMethod
public void reset() {
Mockito.reset(fragment);
}

@Test
public void testTrimWhitespace() throws IOException {
when(fragment.execute()).thenReturn("\t a b\t\tc \t");

StringWriter output = new StringWriter();
new TrimWhitespaceLambda().execute(fragment, output);
assertEquals(output.toString(), " a b c ");
}

}