From a4b1a26c579bcc381cef1a0e7358f32a9d3e63e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Masset?= Date: Thu, 6 Sep 2018 22:00:55 +0200 Subject: [PATCH 1/4] Moved example string to a dedicated variable in Spring"s API template --- .../swagger-codegen/src/main/resources/JavaSpring/api.mustache | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/swagger-codegen/src/main/resources/JavaSpring/api.mustache b/modules/swagger-codegen/src/main/resources/JavaSpring/api.mustache index 0fa5c0056c6..67fee7bac75 100644 --- a/modules/swagger-codegen/src/main/resources/JavaSpring/api.mustache +++ b/modules/swagger-codegen/src/main/resources/JavaSpring/api.mustache @@ -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 = "{{#lambdaRemoveLineBreak}}{{#lambdaEscapeDoubleQuote}}{{{example}}}{{/lambdaEscapeDoubleQuote}}{{/lambdaRemoveLineBreak}}"; + 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}}; From 7534b1a9795bdaabafc83da77b64cede882b6092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Masset?= Date: Thu, 6 Sep 2018 23:16:00 +0200 Subject: [PATCH 2/4] Added a new mustache lambda to trim whitespace in fragments --- .../mustache/TrimWhitespaceLambda.java | 34 +++++++++++++++ .../mustache/TrimWhitespaceLambdaTest.java | 42 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 modules/swagger-codegen/src/main/java/io/swagger/codegen/mustache/TrimWhitespaceLambda.java create mode 100644 modules/swagger-codegen/src/test/java/io/swagger/codegen/mustache/TrimWhitespaceLambdaTest.java diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/mustache/TrimWhitespaceLambda.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/mustache/TrimWhitespaceLambda.java new file mode 100644 index 00000000000..c88490322ee --- /dev/null +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/mustache/TrimWhitespaceLambda.java @@ -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: + * + *
+ * additionalProperties.put("lambdaTrimWhitespace", new TrimWhitespaceLambda());
+ * 
+ * + * Use: + * + *
+ * {{#lambdaTrimWhitespace}}{{summary}}{{/lambdaTrimWhitespace}}
+ * 
+ */ +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)); + } + +} diff --git a/modules/swagger-codegen/src/test/java/io/swagger/codegen/mustache/TrimWhitespaceLambdaTest.java b/modules/swagger-codegen/src/test/java/io/swagger/codegen/mustache/TrimWhitespaceLambdaTest.java new file mode 100644 index 00000000000..beb14223b56 --- /dev/null +++ b/modules/swagger-codegen/src/test/java/io/swagger/codegen/mustache/TrimWhitespaceLambdaTest.java @@ -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 "); + } + +} From 339b2c39c5ee1ef83888dcaa71236f58657e28a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Masset?= Date: Fri, 7 Sep 2018 00:28:23 +0200 Subject: [PATCH 3/4] Added a new lambda to split long fragments into compilable strings --- .../codegen/mustache/SplitStringLambda.java | 86 +++++++++++++++++++ .../mustache/SplitStringLambdaTest.java | 85 ++++++++++++++++++ 2 files changed, 171 insertions(+) create mode 100644 modules/swagger-codegen/src/main/java/io/swagger/codegen/mustache/SplitStringLambda.java create mode 100644 modules/swagger-codegen/src/test/java/io/swagger/codegen/mustache/SplitStringLambdaTest.java diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/mustache/SplitStringLambda.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/mustache/SplitStringLambda.java new file mode 100644 index 00000000000..06c81332a9d --- /dev/null +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/mustache/SplitStringLambda.java @@ -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: + * + *
+ * additionalProperties.put("lambdaSplitString", new SplitStringLambda());
+ * 
+ * + * Use: + * + *
+ * {{#lambdaSplitString}}{{summary}}{{/lambdaSplitString}}
+ * 
+ */ +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()); + } + +} diff --git a/modules/swagger-codegen/src/test/java/io/swagger/codegen/mustache/SplitStringLambdaTest.java b/modules/swagger-codegen/src/test/java/io/swagger/codegen/mustache/SplitStringLambdaTest.java new file mode 100644 index 00000000000..9d9eeed8f16 --- /dev/null +++ b/modules/swagger-codegen/src/test/java/io/swagger/codegen/mustache/SplitStringLambdaTest.java @@ -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 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)); + } + +} From b32c46a14f3b575c88507165b1221fad4d414683 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Masset?= Date: Tue, 11 Sep 2018 01:26:08 +0200 Subject: [PATCH 4/4] Use newly introduced lambdas in Spring's API template to avoid generating uncompilable example code --- .../java/io/swagger/codegen/languages/SpringCodegen.java | 6 ++++++ .../src/main/resources/JavaSpring/api.mustache | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/SpringCodegen.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/SpringCodegen.java index 86cf8f8cf41..d4f64d1485a 100644 --- a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/SpringCodegen.java +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/SpringCodegen.java @@ -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; @@ -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 diff --git a/modules/swagger-codegen/src/main/resources/JavaSpring/api.mustache b/modules/swagger-codegen/src/main/resources/JavaSpring/api.mustache index 67fee7bac75..3f33a34bcdb 100644 --- a/modules/swagger-codegen/src/main/resources/JavaSpring/api.mustache +++ b/modules/swagger-codegen/src/main/resources/JavaSpring/api.mustache @@ -117,7 +117,7 @@ public interface {{classname}} { {{#examples}} if (getAcceptHeader().get().contains("{{{contentType}}}")) { try { - String exampleString = "{{#lambdaRemoveLineBreak}}{{#lambdaEscapeDoubleQuote}}{{{example}}}{{/lambdaEscapeDoubleQuote}}{{/lambdaRemoveLineBreak}}"; + 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); @@ -137,4 +137,4 @@ public interface {{classname}} { {{/operation}} } -{{/operations}} \ No newline at end of file +{{/operations}}