Skip to content

Commit d306a25

Browse files
authored
Merge pull request #1237 from Backbase/chore/0.18.1-snapshot-exampleobject-fix
chore: bump to 0.18.1-SNAPSHOT and fix spring example escaping
2 parents fb065ce + 09506ad commit d306a25

13 files changed

Lines changed: 100 additions & 11 deletions

File tree

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ It currently consists of
1616
# Release Notes
1717
BOAT is still under development and subject to change.
1818

19+
## 0.18.2
20+
* Spring generator: fixed `@ExampleObject` rendering in `api.mustache` by unwrapping escaped quotes in example payloads.
21+
* Added `unwrapEscapedQuotes` lambda to `boat-spring` generator templates to prevent malformed annotation values (for example `value = "\"{...}"`).
22+
* Added a regression test to verify generated Spring API interfaces include valid `@ExampleObject` annotation values and remain parseable Java code.
23+
1924
## 0.18.1
2025
* Use Jackson3 imports in `BigDecimalCustomSerializer.class` when using `useJackson3` set to `true`
2126

boat-engine/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<parent>
66
<groupId>com.backbase.oss</groupId>
77
<artifactId>backbase-openapi-tools</artifactId>
8-
<version>0.18.1-SNAPSHOT</version>
8+
<version>0.18.2-SNAPSHOT</version>
99
</parent>
1010
<artifactId>boat-engine</artifactId>
1111
<packaging>jar</packaging>

boat-maven-plugin/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<parent>
66
<groupId>com.backbase.oss</groupId>
77
<artifactId>backbase-openapi-tools</artifactId>
8-
<version>0.18.1-SNAPSHOT</version>
8+
<version>0.18.2-SNAPSHOT</version>
99
</parent>
1010
<artifactId>boat-maven-plugin</artifactId>
1111

boat-quay/boat-quay-lint/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<parent>
66
<groupId>com.backbase.oss</groupId>
77
<artifactId>boat-quay</artifactId>
8-
<version>0.18.1-SNAPSHOT</version>
8+
<version>0.18.2-SNAPSHOT</version>
99
</parent>
1010

1111
<artifactId>boat-quay-lint</artifactId>

boat-quay/boat-quay-rules/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<parent>
66
<groupId>com.backbase.oss</groupId>
77
<artifactId>boat-quay</artifactId>
8-
<version>0.18.1-SNAPSHOT</version>
8+
<version>0.18.2-SNAPSHOT</version>
99
</parent>
1010

1111
<artifactId>boat-quay-rules</artifactId>

boat-quay/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<parent>
66
<groupId>com.backbase.oss</groupId>
77
<artifactId>backbase-openapi-tools</artifactId>
8-
<version>0.18.1-SNAPSHOT</version>
8+
<version>0.18.2-SNAPSHOT</version>
99
</parent>
1010

1111

boat-scaffold/pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<parent>
66
<groupId>com.backbase.oss</groupId>
77
<artifactId>backbase-openapi-tools</artifactId>
8-
<version>0.18.1-SNAPSHOT</version>
8+
<version>0.18.2-SNAPSHOT</version>
99
</parent>
1010

1111
<artifactId>boat-scaffold</artifactId>
@@ -102,7 +102,7 @@
102102
<dependency>
103103
<groupId>com.backbase.oss</groupId>
104104
<artifactId>boat-trail-resources</artifactId>
105-
<version>0.18.1-SNAPSHOT</version>
105+
<version>0.18.2-SNAPSHOT</version>
106106
<scope>test</scope>
107107
</dependency>
108108
<dependency>

boat-scaffold/src/main/java/com/backbase/oss/codegen/java/BoatSpringCodeGen.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public class BoatSpringCodeGen extends SpringCodegen {
4848

4949
public static final String ADD_SERVLET_REQUEST = "addServletRequest";
5050
public static final String ADD_BINDING_RESULT = "addBindingResult";
51+
public static final String UNWRAP_ESCAPED_QUOTES = "unwrapEscapedQuotes";
5152

5253
private static final String VENDOR_EXTENSION_NOT_NULL = "x-not-null";
5354
private static final String JSON_SERIALIZE = "JsonSerialize";
@@ -148,6 +149,23 @@ protected String postProcessLine(String line) {
148149
}
149150
}
150151

152+
static class UnwrapEscapedQuotes implements Mustache.Lambda {
153+
154+
@Override
155+
public void execute(Fragment frag, Writer out) throws IOException {
156+
String text = frag.execute();
157+
if (text == null) {
158+
return;
159+
}
160+
String normalized = text.replace("\\\\\"", "\\\"");
161+
if (normalized.length() >= 4 && normalized.startsWith("\\\"") && normalized.endsWith("\\\"")) {
162+
out.write(normalized.substring(2, normalized.length() - 2));
163+
return;
164+
}
165+
out.write(normalized);
166+
}
167+
}
168+
151169
/**
152170
* Adds a HttpServletRequest object to the API definition method.
153171
*/
@@ -338,6 +356,7 @@ public void processOpts() {
338356
this.additionalProperties.put("newLine8", new NewLineIndent(8, " "));
339357
this.additionalProperties.put("toOneLine", new FormatToOneLine());
340358
this.additionalProperties.put("trimAndIndent4", new TrimAndIndent(4, " "));
359+
this.additionalProperties.put(UNWRAP_ESCAPED_QUOTES, new UnwrapEscapedQuotes());
341360
}
342361

343362
@Override

boat-scaffold/src/main/templates/boat-spring/api.mustache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ public interface {{classname}} {
187187
{{#examples}}
188188
@ExampleObject(
189189
name = "{{{exampleName}}}",
190-
value = "{{{exampleValue}}}"
190+
value = "{{#unwrapEscapedQuotes}}{{{exampleValue}}}{{/unwrapEscapedQuotes}}"
191191
){{^-last}},{{/-last}}
192192
{{/examples}}
193193
{{#-last}}

boat-scaffold/src/test/java/com/backbase/oss/codegen/java/BoatSpringCodeGenTests.java

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import static org.hamcrest.Matchers.is;
77
import static org.hamcrest.Matchers.isA;
88
import static org.junit.jupiter.api.Assertions.assertEquals;
9+
import static org.junit.jupiter.api.Assertions.assertFalse;
910
import static org.junit.jupiter.api.Assertions.assertTrue;
1011
import static org.mockito.Mockito.mock;
1112
import static org.mockito.Mockito.when;
@@ -48,14 +49,17 @@
4849
import java.util.Set;
4950
import java.util.UUID;
5051
import java.util.stream.Collectors;
52+
import java.util.stream.Stream;
5153
import org.apache.commons.io.FileUtils;
5254
import org.apache.commons.lang.UnhandledException;
5355
import org.hamcrest.Matchers;
5456
import org.jetbrains.annotations.NotNull;
5557
import org.junit.jupiter.api.BeforeAll;
5658
import org.junit.jupiter.api.Test;
5759
import org.junit.jupiter.params.ParameterizedTest;
60+
import org.junit.jupiter.params.provider.Arguments;
5861
import org.junit.jupiter.params.provider.CsvSource;
62+
import org.junit.jupiter.params.provider.MethodSource;
5963
import org.openapitools.codegen.CliOption;
6064
import org.openapitools.codegen.ClientOptInput;
6165
import org.openapitools.codegen.CodegenOperation;
@@ -97,6 +101,20 @@ void newLineIndent() throws IOException {
97101
assertThat(output.toString(), equalTo(String.format("__%n__Good%n__ morning,%n__ Dave%n")));
98102
}
99103

104+
@ParameterizedTest
105+
@MethodSource("unwrapEscapedQuotesCases")
106+
void unwrapEscapedQuotes_execute_shouldHandleAllScenarios(String input, String expectedOutput) throws IOException {
107+
final BoatSpringCodeGen.UnwrapEscapedQuotes lambda = new BoatSpringCodeGen.UnwrapEscapedQuotes();
108+
final StringWriter output = new StringWriter();
109+
final Fragment frag = mock(Fragment.class);
110+
111+
when(frag.execute()).thenReturn(input);
112+
113+
lambda.execute(frag, output);
114+
115+
assertThat(output.toString(), equalTo(expectedOutput));
116+
}
117+
100118
@Test
101119
void addServletRequestTestFromOperation(){
102120
final BoatSpringCodeGen gen = new BoatSpringCodeGen();
@@ -139,6 +157,45 @@ void multipartWithFileAndObject() throws IOException {
139157
assertThat(filesParam.getTypeAsString(), equalTo("List<MultipartFile>"));
140158
}
141159

160+
@Test
161+
void shouldGenerateValidExampleObjectAnnotation() throws IOException {
162+
var codegen = new BoatSpringCodeGen();
163+
var input = new File("src/test/resources/openapi-with-examples/openapi-with-multiple-permissions.yaml");
164+
codegen.setLibrary("spring-boot");
165+
codegen.setInterfaceOnly(true);
166+
codegen.setSkipDefaultInterface(true);
167+
codegen.setOutputDir(TEST_OUTPUT + "/example-object");
168+
codegen.setInputSpec(input.getAbsolutePath());
169+
codegen.additionalProperties().put(SpringCodegen.USE_SPRING_BOOT3, Boolean.TRUE.toString());
170+
171+
var openApiInput = new OpenAPIParser().readLocation(input.getAbsolutePath(), null, new ParseOptions())
172+
.getOpenAPI();
173+
var clientOptInput = new ClientOptInput();
174+
clientOptInput.config(codegen);
175+
clientOptInput.openAPI(openApiInput);
176+
177+
List<File> files = new DefaultGenerator().opts(clientOptInput).generate();
178+
179+
File apiFile = files.stream()
180+
.filter(file -> file.getName().endsWith("Api.java"))
181+
.filter(file -> {
182+
try {
183+
return Files.readString(file.toPath()).contains("@ExampleObject(");
184+
} catch (IOException e) {
185+
throw new UnhandledException(e);
186+
}
187+
})
188+
.findFirst()
189+
.orElseThrow();
190+
191+
String apiContent = Files.readString(apiFile.toPath());
192+
assertTrue(apiContent.contains("@ExampleObject("));
193+
assertTrue(apiContent.contains("Value Exceeded. Must be between {min} and {max}."));
194+
assertTrue(apiContent.contains("Bad Request"));
195+
assertFalse(apiContent.contains("value = \"\\\"{"));
196+
StaticJavaParser.parse(apiFile);
197+
}
198+
142199
@Test
143200
void testReplaceBeanValidationCollectionType() {
144201
var codegen = new BoatSpringCodeGen();
@@ -552,4 +609,12 @@ private static void assertMethodCollectionReturnType(MethodDeclaration method, S
552609
.getTypeArguments().get().getFirst().get();
553610
assertEquals(itemType, collectionItemType.getName().toString());
554611
}
612+
613+
static Stream<Arguments> unwrapEscapedQuotesCases() {
614+
return Stream.of(
615+
Arguments.of((String) null, ""),
616+
Arguments.of("\\\"{\\\"message\\\":\\\"Bad Request\\\"}\\\"", "{\\\"message\\\":\\\"Bad Request\\\"}"),
617+
Arguments.of("prefix\\\\\"quoted\\\\\"suffix", "prefix\\\"quoted\\\"suffix"),
618+
Arguments.of("\\\"", "\\\""));
619+
}
555620
}

0 commit comments

Comments
 (0)