diff --git a/README.md b/README.md
index d466e37ac..d3eced9a5 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,11 @@ It currently consists of
# Release Notes
BOAT is still under development and subject to change.
+## 0.18.1
+* Spring generator: fixed `@ExampleObject` rendering in `api.mustache` by unwrapping escaped quotes in example payloads.
+* Added `unwrapEscapedQuotes` lambda to `boat-spring` generator templates to prevent malformed annotation values (for example `value = "\"{...}"`).
+* Added a regression test to verify generated Spring API interfaces include valid `@ExampleObject` annotation values and remain parseable Java code.
+
## 0.18.0
* openapi-generator `7.20.0` baseline (Spring Boot 4, Jackson 3)
* moved Java and JavaSpring templates to `7.20.0` adding remaing custom features
diff --git a/boat-engine/pom.xml b/boat-engine/pom.xml
index 776bed88a..89746680a 100644
--- a/boat-engine/pom.xml
+++ b/boat-engine/pom.xml
@@ -5,7 +5,7 @@
com.backbase.oss
backbase-openapi-tools
- 0.18.0-SNAPSHOT
+ 0.18.1-SNAPSHOT
boat-engine
jar
diff --git a/boat-maven-plugin/pom.xml b/boat-maven-plugin/pom.xml
index 3c8e8aae0..00069a4aa 100644
--- a/boat-maven-plugin/pom.xml
+++ b/boat-maven-plugin/pom.xml
@@ -5,7 +5,7 @@
com.backbase.oss
backbase-openapi-tools
- 0.18.0-SNAPSHOT
+ 0.18.1-SNAPSHOT
boat-maven-plugin
diff --git a/boat-quay/boat-quay-lint/pom.xml b/boat-quay/boat-quay-lint/pom.xml
index 14df40cd7..db9b4ebf6 100644
--- a/boat-quay/boat-quay-lint/pom.xml
+++ b/boat-quay/boat-quay-lint/pom.xml
@@ -5,7 +5,7 @@
com.backbase.oss
boat-quay
- 0.18.0-SNAPSHOT
+ 0.18.1-SNAPSHOT
boat-quay-lint
diff --git a/boat-quay/boat-quay-rules/pom.xml b/boat-quay/boat-quay-rules/pom.xml
index 0f48c13c5..609a524fd 100644
--- a/boat-quay/boat-quay-rules/pom.xml
+++ b/boat-quay/boat-quay-rules/pom.xml
@@ -5,7 +5,7 @@
com.backbase.oss
boat-quay
- 0.18.0-SNAPSHOT
+ 0.18.1-SNAPSHOT
boat-quay-rules
diff --git a/boat-quay/pom.xml b/boat-quay/pom.xml
index 7a2d7f695..277df7c25 100644
--- a/boat-quay/pom.xml
+++ b/boat-quay/pom.xml
@@ -5,7 +5,7 @@
com.backbase.oss
backbase-openapi-tools
- 0.18.0-SNAPSHOT
+ 0.18.1-SNAPSHOT
diff --git a/boat-scaffold/pom.xml b/boat-scaffold/pom.xml
index 479e70c16..6186671b7 100644
--- a/boat-scaffold/pom.xml
+++ b/boat-scaffold/pom.xml
@@ -5,7 +5,7 @@
com.backbase.oss
backbase-openapi-tools
- 0.18.0-SNAPSHOT
+ 0.18.1-SNAPSHOT
boat-scaffold
@@ -102,7 +102,7 @@
com.backbase.oss
boat-trail-resources
- 0.18.0-SNAPSHOT
+ 0.18.1-SNAPSHOT
test
diff --git a/boat-scaffold/src/main/java/com/backbase/oss/codegen/java/BoatSpringCodeGen.java b/boat-scaffold/src/main/java/com/backbase/oss/codegen/java/BoatSpringCodeGen.java
index 633651516..bd03dc4b1 100644
--- a/boat-scaffold/src/main/java/com/backbase/oss/codegen/java/BoatSpringCodeGen.java
+++ b/boat-scaffold/src/main/java/com/backbase/oss/codegen/java/BoatSpringCodeGen.java
@@ -48,6 +48,7 @@ public class BoatSpringCodeGen extends SpringCodegen {
public static final String ADD_SERVLET_REQUEST = "addServletRequest";
public static final String ADD_BINDING_RESULT = "addBindingResult";
+ public static final String UNWRAP_ESCAPED_QUOTES = "unwrapEscapedQuotes";
private static final String VENDOR_EXTENSION_NOT_NULL = "x-not-null";
@@ -147,6 +148,23 @@ protected String postProcessLine(String line) {
}
}
+ static class UnwrapEscapedQuotes implements Mustache.Lambda {
+
+ @Override
+ public void execute(Fragment frag, Writer out) throws IOException {
+ String text = frag.execute();
+ if (text == null) {
+ return;
+ }
+ String normalized = text.replace("\\\\\"", "\\\"");
+ if (normalized.length() >= 4 && normalized.startsWith("\\\"") && normalized.endsWith("\\\"")) {
+ out.write(normalized.substring(2, normalized.length() - 2));
+ return;
+ }
+ out.write(normalized);
+ }
+ }
+
/**
* Adds a HttpServletRequest object to the API definition method.
*/
@@ -332,6 +350,7 @@ public void processOpts() {
this.additionalProperties.put("newLine8", new NewLineIndent(8, " "));
this.additionalProperties.put("toOneLine", new FormatToOneLine());
this.additionalProperties.put("trimAndIndent4", new TrimAndIndent(4, " "));
+ this.additionalProperties.put(UNWRAP_ESCAPED_QUOTES, new UnwrapEscapedQuotes());
}
@Override
diff --git a/boat-scaffold/src/main/templates/boat-spring/api.mustache b/boat-scaffold/src/main/templates/boat-spring/api.mustache
index 9f3ac9721..41500ef63 100644
--- a/boat-scaffold/src/main/templates/boat-spring/api.mustache
+++ b/boat-scaffold/src/main/templates/boat-spring/api.mustache
@@ -187,7 +187,7 @@ public interface {{classname}} {
{{#examples}}
@ExampleObject(
name = "{{{exampleName}}}",
- value = "{{{exampleValue}}}"
+ value = "{{#unwrapEscapedQuotes}}{{{exampleValue}}}{{/unwrapEscapedQuotes}}"
){{^-last}},{{/-last}}
{{/examples}}
{{#-last}}
diff --git a/boat-scaffold/src/test/java/com/backbase/oss/codegen/java/BoatSpringCodeGenTests.java b/boat-scaffold/src/test/java/com/backbase/oss/codegen/java/BoatSpringCodeGenTests.java
index c0c52fb20..1d24004c4 100644
--- a/boat-scaffold/src/test/java/com/backbase/oss/codegen/java/BoatSpringCodeGenTests.java
+++ b/boat-scaffold/src/test/java/com/backbase/oss/codegen/java/BoatSpringCodeGenTests.java
@@ -6,6 +6,7 @@
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.isA;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -48,6 +49,7 @@
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.UnhandledException;
import org.hamcrest.Matchers;
@@ -55,7 +57,9 @@
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.MethodSource;
import org.openapitools.codegen.CliOption;
import org.openapitools.codegen.ClientOptInput;
import org.openapitools.codegen.CodegenOperation;
@@ -97,6 +101,20 @@ void newLineIndent() throws IOException {
assertThat(output.toString(), equalTo(String.format("__%n__Good%n__ morning,%n__ Dave%n")));
}
+ @ParameterizedTest
+ @MethodSource("unwrapEscapedQuotesCases")
+ void unwrapEscapedQuotes_execute_shouldHandleAllScenarios(String input, String expectedOutput) throws IOException {
+ final BoatSpringCodeGen.UnwrapEscapedQuotes lambda = new BoatSpringCodeGen.UnwrapEscapedQuotes();
+ final StringWriter output = new StringWriter();
+ final Fragment frag = mock(Fragment.class);
+
+ when(frag.execute()).thenReturn(input);
+
+ lambda.execute(frag, output);
+
+ assertThat(output.toString(), equalTo(expectedOutput));
+ }
+
@Test
void addServletRequestTestFromOperation(){
final BoatSpringCodeGen gen = new BoatSpringCodeGen();
@@ -139,6 +157,45 @@ void multipartWithFileAndObject() throws IOException {
assertThat(filesParam.getTypeAsString(), equalTo("List"));
}
+ @Test
+ void shouldGenerateValidExampleObjectAnnotation() throws IOException {
+ var codegen = new BoatSpringCodeGen();
+ var input = new File("src/test/resources/openapi-with-examples/openapi-with-multiple-permissions.yaml");
+ codegen.setLibrary("spring-boot");
+ codegen.setInterfaceOnly(true);
+ codegen.setSkipDefaultInterface(true);
+ codegen.setOutputDir(TEST_OUTPUT + "/example-object");
+ codegen.setInputSpec(input.getAbsolutePath());
+ codegen.additionalProperties().put(SpringCodegen.USE_SPRING_BOOT3, Boolean.TRUE.toString());
+
+ var openApiInput = new OpenAPIParser().readLocation(input.getAbsolutePath(), null, new ParseOptions())
+ .getOpenAPI();
+ var clientOptInput = new ClientOptInput();
+ clientOptInput.config(codegen);
+ clientOptInput.openAPI(openApiInput);
+
+ List files = new DefaultGenerator().opts(clientOptInput).generate();
+
+ File apiFile = files.stream()
+ .filter(file -> file.getName().endsWith("Api.java"))
+ .filter(file -> {
+ try {
+ return Files.readString(file.toPath()).contains("@ExampleObject(");
+ } catch (IOException e) {
+ throw new UnhandledException(e);
+ }
+ })
+ .findFirst()
+ .orElseThrow();
+
+ String apiContent = Files.readString(apiFile.toPath());
+ assertTrue(apiContent.contains("@ExampleObject("));
+ assertTrue(apiContent.contains("Value Exceeded. Must be between {min} and {max}."));
+ assertTrue(apiContent.contains("Bad Request"));
+ assertFalse(apiContent.contains("value = \"\\\"{"));
+ StaticJavaParser.parse(apiFile);
+ }
+
@Test
void testReplaceBeanValidationCollectionType() {
var codegen = new BoatSpringCodeGen();
@@ -552,4 +609,12 @@ private static void assertMethodCollectionReturnType(MethodDeclaration method, S
.getTypeArguments().get().getFirst().get();
assertEquals(itemType, collectionItemType.getName().toString());
}
+
+ static Stream unwrapEscapedQuotesCases() {
+ return Stream.of(
+ Arguments.of((String) null, ""),
+ Arguments.of("\\\"{\\\"message\\\":\\\"Bad Request\\\"}\\\"", "{\\\"message\\\":\\\"Bad Request\\\"}"),
+ Arguments.of("prefix\\\\\"quoted\\\\\"suffix", "prefix\\\"quoted\\\"suffix"),
+ Arguments.of("\\\"", "\\\""));
+ }
}
diff --git a/boat-trail-resources/pom.xml b/boat-trail-resources/pom.xml
index b3e0c7f8f..082a6947e 100644
--- a/boat-trail-resources/pom.xml
+++ b/boat-trail-resources/pom.xml
@@ -5,7 +5,7 @@
com.backbase.oss
backbase-openapi-tools
- 0.18.0-SNAPSHOT
+ 0.18.1-SNAPSHOT
boat-trail-resources
diff --git a/pom.xml b/pom.xml
index bce873435..b140018c7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
com.backbase.oss
backbase-openapi-tools
- 0.18.0-SNAPSHOT
+ 0.18.1-SNAPSHOT
pom
Backbase Open Api Tools is a collection of tools to work with Open API
diff --git a/tests/pom.xml b/tests/pom.xml
index 90eee5d9c..116bc34b9 100644
--- a/tests/pom.xml
+++ b/tests/pom.xml
@@ -5,7 +5,7 @@
com.backbase.oss
backbase-openapi-tools
- 0.18.0-SNAPSHOT
+ 0.18.1-SNAPSHOT
tests