Skip to content

Commit 2d91d71

Browse files
authored
Merge pull request #618 from Backbase/validationAnnotationsOnCollectionItems
Validation annotations on collection items
2 parents 514da65 + 8bced58 commit 2d91d71

File tree

12 files changed

+414
-107
lines changed

12 files changed

+414
-107
lines changed

README.md

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

19+
## 0.17.13
20+
* boat-spring
21+
* Fix: generate validation constraints on primitive collection items types (updates in pojo.mustache and new collectionDataType.mustache)
1922
## 0.17.12
2023
* BoatJavaCodeGen, BoatSpringCodeGen
2124
* Fix: Always generate collection initializer when array is required in the schema (even if containerDefaultToNull=true)

boat-scaffold/pom.xml

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,13 +152,29 @@
152152
<artifactId>maven-resolver-transport-http</artifactId>
153153
<version>${maven.resolver.version}</version>
154154
</dependency>
155-
156155
<dependency>
157156
<groupId>com.github.javaparser</groupId>
158157
<artifactId>javaparser-core</artifactId>
159158
<version>3.25.4</version>
160159
<scope>test</scope>
161160
</dependency>
161+
<dependency>
162+
<groupId>org.hibernate.validator</groupId>
163+
<artifactId>hibernate-validator</artifactId>
164+
<version>8.0.1.Final</version>
165+
<scope>test</scope>
166+
</dependency>
167+
<dependency>
168+
<groupId>org.glassfish</groupId>
169+
<artifactId>jakarta.el</artifactId>
170+
<version>5.0.0-M1</version>
171+
<scope>test</scope>
172+
</dependency>
173+
<dependency>
174+
<groupId>org.openapitools</groupId>
175+
<artifactId>jackson-databind-nullable</artifactId>
176+
<scope>test</scope>
177+
</dependency>
162178
</dependencies>
163179

164180
<build>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{{#openApiNullable}}{{#isNullable}}JsonNullable<{{/isNullable}}{{/openApiNullable}}{{{baseType}}}<{{#items}}{{#isPrimitiveType}}{{>beanValidationCore}}{{/isPrimitiveType}}{{^isPrimitiveType}}{{#useBeanValidation}}@Valid {{/useBeanValidation}}{{/isPrimitiveType}}{{/items}}{{{items.datatypeWithEnum}}}>{{#openApiNullable}}{{#isNullable}}>{{/isNullable}}{{/openApiNullable}}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ import javax.validation.constraints.*;
3535
{{/useBeanValidation}}
3636
{{^useBeanValidation}}
3737
{{#useJakartaEe}}
38-
import jakarta.validation.constraints.NotNull;
38+
import jakarta.validation.constraints.*;
3939
{{/useJakartaEe}}
4040
{{^useJakartaEe}}
41-
import javax.validation.constraints.NotNull;
41+
import javax.validation.constraints.*;
4242
{{/useJakartaEe}}
4343
{{/useBeanValidation}}
4444
{{#performBeanValidation}}

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

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,24 @@ public class {{classname}}{{#parent}} extends {{{parent}}}{{/parent}}{{^parent}}
6868
{{{vendorExtensions.x-field-extra-annotation}}}
6969
{{/vendorExtensions.x-field-extra-annotation}}
7070
{{#isContainer}}
71-
{{#useBeanValidation}}@Valid{{/useBeanValidation}}
72-
{{#openApiNullable}}
73-
private {{>nullableDataType}} {{name}}{{#isNullable}} = JsonNullable.undefined(){{/isNullable}}{{^isNullable}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{/isNullable}};
74-
{{/openApiNullable}}
75-
{{^openApiNullable}}
76-
private {{>nullableDataType}} {{name}}{{#defaultValue}} = {{{.}}}{{/defaultValue}};
77-
{{/openApiNullable}}
71+
{{#isArray}}
72+
{{#useBeanValidation}}@Valid{{/useBeanValidation}}
73+
{{#openApiNullable}}
74+
private {{>collectionDataType}} {{name}}{{#isNullable}} = JsonNullable.undefined(){{/isNullable}}{{^isNullable}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{/isNullable}};
75+
{{/openApiNullable}}
76+
{{^openApiNullable}}
77+
private {{>collectionDataType}} {{name}}{{#defaultValue}} = {{{.}}}{{/defaultValue}};
78+
{{/openApiNullable}}
79+
{{/isArray}}
80+
{{^isArray}}
81+
{{#useBeanValidation}}@Valid{{/useBeanValidation}}
82+
{{#openApiNullable}}
83+
private {{>nullableDataType}} {{name}}{{#isNullable}} = JsonNullable.undefined(){{/isNullable}}{{^isNullable}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{/isNullable}};
84+
{{/openApiNullable}}
85+
{{^openApiNullable}}
86+
private {{>nullableDataType}} {{name}}{{#defaultValue}} = {{{.}}}{{/defaultValue}};
87+
{{/openApiNullable}}
88+
{{/isArray}}
7889
{{/isContainer}}
7990
{{^isContainer}}
8091
{{#isDate}}

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

Lines changed: 138 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,48 @@
11
package com.backbase.oss.codegen.java;
22

3+
import static com.backbase.oss.codegen.java.BoatSpringCodeGen.USE_PROTECTED_FIELDS;
4+
import static java.util.stream.Collectors.groupingBy;
5+
import static org.hamcrest.MatcherAssert.assertThat;
6+
import static org.hamcrest.Matchers.equalTo;
7+
import static org.hamcrest.Matchers.hasEntry;
8+
import static org.junit.jupiter.api.Assertions.assertEquals;
9+
import static org.junit.jupiter.api.Assertions.assertTrue;
10+
import static org.mockito.Mockito.mock;
11+
import static org.mockito.Mockito.when;
12+
313
import com.backbase.oss.codegen.java.BoatSpringCodeGen.NewLineIndent;
14+
import com.backbase.oss.codegen.java.VerificationRunner.Verification;
415
import com.github.javaparser.StaticJavaParser;
516
import com.github.javaparser.ast.body.MethodDeclaration;
617
import com.github.javaparser.ast.body.Parameter;
718
import com.samskivert.mustache.Template.Fragment;
819
import io.swagger.parser.OpenAPIParser;
920
import io.swagger.v3.oas.models.Operation;
1021
import io.swagger.v3.parser.core.models.ParseOptions;
11-
import org.apache.commons.io.FileUtils;
12-
import org.junit.jupiter.api.BeforeAll;
13-
import org.junit.jupiter.api.Test;
14-
import org.openapitools.codegen.*;
15-
22+
import jakarta.validation.ConstraintViolation;
23+
import jakarta.validation.Validation;
24+
import jakarta.validation.Validator;
1625
import java.io.File;
1726
import java.io.IOException;
1827
import java.io.StringWriter;
1928
import java.nio.file.Files;
2029
import java.nio.file.Paths;
2130
import java.util.Arrays;
31+
import java.util.Collection;
2232
import java.util.List;
2333
import java.util.Map;
24-
25-
import static com.backbase.oss.codegen.java.BoatSpringCodeGen.USE_PROTECTED_FIELDS;
26-
import static java.util.stream.Collectors.groupingBy;
27-
import static org.hamcrest.MatcherAssert.assertThat;
28-
import static org.hamcrest.Matchers.equalTo;
29-
import static org.hamcrest.Matchers.hasEntry;
30-
import static org.junit.jupiter.api.Assertions.assertEquals;
31-
import static org.junit.jupiter.api.Assertions.assertTrue;
32-
import static org.mockito.Mockito.mock;
33-
import static org.mockito.Mockito.when;
34+
import java.util.Set;
35+
import org.apache.commons.io.FileUtils;
36+
import org.apache.commons.lang.UnhandledException;
37+
import org.hamcrest.Matchers;
38+
import org.junit.jupiter.api.BeforeAll;
39+
import org.junit.jupiter.api.Test;
40+
import org.openapitools.codegen.CliOption;
41+
import org.openapitools.codegen.ClientOptInput;
42+
import org.openapitools.codegen.CodegenOperation;
43+
import org.openapitools.codegen.CodegenProperty;
44+
import org.openapitools.codegen.DefaultGenerator;
45+
import org.openapitools.codegen.languages.SpringCodegen;
3446

3547
class BoatSpringCodeGenTests {
3648

@@ -133,4 +145,115 @@ void testReplaceBeanValidationCollectionType() {
133145
codegenProperty,"Set<@Valid com.backbase.dbs.arrangement.commons.model.TranslationItemDto>");
134146
assertEquals("Set<com.backbase.dbs.arrangement.commons.model.@Valid TranslationItemDto>", result);
135147
}
148+
@Test
149+
@SuppressWarnings("unchecked")
150+
void shouldGenerateValidations() throws InterruptedException {
151+
152+
var modelPackage = "com.backbase.model";
153+
var input = new File("src/test/resources/boat-spring/openapi.yaml");
154+
var output = TEST_OUTPUT + "/shouldGenerateValidations";
155+
156+
// generate project
157+
var codegen = new BoatSpringCodeGen();
158+
codegen.setLibrary("spring-boot");
159+
codegen.setInterfaceOnly(true);
160+
codegen.setOutputDir(output);
161+
codegen.setInputSpec(input.getAbsolutePath());
162+
codegen.additionalProperties().put(SpringCodegen.USE_SPRING_BOOT3, Boolean.TRUE.toString());
163+
codegen.additionalProperties().put(BoatSpringCodeGen.USE_CLASS_LEVEL_BEAN_VALIDATION, Boolean.TRUE.toString());
164+
codegen.setModelPackage(modelPackage);
165+
166+
var openApiInput = new OpenAPIParser()
167+
.readLocation(input.getAbsolutePath(), null, new ParseOptions())
168+
.getOpenAPI();
169+
var clientOptInput = new ClientOptInput();
170+
clientOptInput.config(codegen);
171+
clientOptInput.openAPI(openApiInput);
172+
173+
List<File> files = new DefaultGenerator().opts(clientOptInput).generate();
174+
175+
// compile generated project
176+
var compiler = new MavenProjectCompiler(BoatSpringCodeGenTests.class.getClassLoader());
177+
var projectDir = new File(output);
178+
int compilationStatus = compiler.compile(projectDir);
179+
assertEquals(0, compilationStatus);
180+
181+
// verify
182+
ClassLoader projectClassLoader = compiler.getProjectClassLoader(projectDir);
183+
var verificationRunner = new VerificationRunner(projectClassLoader);
184+
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
185+
186+
Runnable verifyDefaultValues = () -> {
187+
try {
188+
var className = modelPackage + ".MultiLinePaymentRequest";
189+
Class<?> requestClass = projectClassLoader.loadClass(className);
190+
Object requestObject = requestClass.getConstructor().newInstance();
191+
Set<ConstraintViolation<Object>> violations = validator.validate(requestObject);
192+
assertThat(violations, Matchers.hasSize(1));
193+
assertThat(violations.stream().findFirst().get().getPropertyPath().toString(), Matchers.equalTo("name"));
194+
assertThat(
195+
violations.stream().findFirst().get().getMessageTemplate(),
196+
Matchers.equalTo("{jakarta.validation.constraints.NotNull.message}")
197+
);
198+
} catch (Exception e) {
199+
throw new UnhandledException(e);
200+
}
201+
};
202+
verificationRunner.runVerification(
203+
Verification.builder().runnable(verifyDefaultValues).displayName("validations").build()
204+
);
205+
206+
Runnable verifyCollectionItems = () -> {
207+
try {
208+
var className = modelPackage + ".MultiLinePaymentRequest";
209+
Class<?> requestClass = projectClassLoader.loadClass(className);
210+
Object requestObject = requestClass.getConstructor().newInstance();
211+
212+
// set name on MultiLinePaymentRequest
213+
requestObject.getClass()
214+
.getDeclaredMethod("setName", String.class)
215+
.invoke(requestObject, "someName");
216+
217+
// set arrangement ids
218+
Collection<String> arrangementIds = (Collection<String>) requestObject.getClass()
219+
.getDeclaredMethod("getArrangementIds")
220+
.invoke(requestObject);
221+
arrangementIds.add("1");
222+
arrangementIds.add("");
223+
224+
// add PaymentRequestLine to lines
225+
Collection<Object> lines = (Collection<Object>) requestObject.getClass()
226+
.getDeclaredMethod("getLines")
227+
.invoke(requestObject);
228+
Class<?> lineObjectClass = projectClassLoader.loadClass(modelPackage + ".PaymentRequestLine");
229+
Object lineObject = lineObjectClass.getConstructor().newInstance();
230+
lineObject.getClass()
231+
.getDeclaredMethod("setAccountId", String.class)
232+
.invoke(lineObject, "invalidId");
233+
lines.add(lineObject);
234+
235+
// validate
236+
Set<ConstraintViolation<Object>> violations = validator.validate(requestObject);
237+
assertThat(violations, Matchers.hasSize(3));
238+
239+
assertViolationsCount(violations, "{jakarta.validation.constraints.Pattern.message}", 1);
240+
assertViolationsCount(violations, "{jakarta.validation.constraints.Size.message}", 2);
241+
242+
} catch (Exception e) {
243+
throw new UnhandledException(e);
244+
}
245+
};
246+
verificationRunner.runVerification(
247+
Verification.builder().runnable(verifyCollectionItems).displayName("validations").build()
248+
);
249+
}
250+
251+
private static void assertViolationsCount(Set<ConstraintViolation<Object>> violations, String messageTemplate, int count) {
252+
long actualCount = violations.stream()
253+
.map(ConstraintViolation::getMessageTemplate)
254+
.filter(messageTemplate::equals)
255+
.count();
256+
assertEquals(count, actualCount,
257+
String.format("Number of violations '%s', count mismatch", messageTemplate));
258+
}
136259
}

0 commit comments

Comments
 (0)