Skip to content

Commit 6c6c91b

Browse files
committed
[java][client] Add @Email bean validation for format: email strings
The java client generator recognised format: email (isEmail was already set in DefaultCodegen) but its beanValidationCore.mustache never consumed the flag, so email properties and parameters were generated with no @Email constraint, unlike the Spring generator. Emit @Email from Java/beanValidationCore.mustache when isEmail is set, matching the leading-space convention of the sibling @Pattern/@SiZe annotations. No new import is needed: model.mustache and the microprofile model template already declare the wildcard import {javaxPackage}.validation.constraints.* / {rootJavaEEPackage}.validation.constraints.*, so the correct javax/jakarta package is selected automatically. The annotation is only reached under useBeanValidation. Expanded the issue-17485 bean-validation fixture with a User model carrying a required format: email property, added unit tests covering javax, jakarta and the disabled case, and regenerated the resttemplate-list-schema-validation sample so the generated @Email output is committed and verified by CI. The sample compiles.
1 parent 36bc084 commit 6c6c91b

11 files changed

Lines changed: 335 additions & 14 deletions

File tree

modules/openapi-generator/src/main/resources/Java/beanValidationCore.mustache

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
{{#pattern}}{{^isByteArray}} @Pattern(regexp="{{{pattern}}}"){{/isByteArray}}{{/pattern}}{{!
1+
{{#isEmail}} @Email{{/isEmail}}{{!
2+
}}{{#pattern}}{{^isByteArray}} @Pattern(regexp="{{{pattern}}}"){{/isByteArray}}{{/pattern}}{{!
23
minLength && maxLength set
34
}}{{#minLength}}{{#maxLength}} @Size(min={{minLength}},max={{maxLength}}){{/maxLength}}{{/minLength}}{{!
45
minLength set, maxLength not

modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,64 @@ public void testJdkHttpClientWithUseBeanValidationEnabled() {
619619
.contains("@Pattern", "import jakarta.validation.constraints.*");
620620
}
621621

622+
@Test
623+
public void testUseBeanValidationGeneratesEmailAnnotation() {
624+
final Path output = newTempFolder();
625+
final CodegenConfigurator configurator = new CodegenConfigurator()
626+
.setGeneratorName(JAVA_GENERATOR)
627+
.setLibrary(JavaClientCodegen.NATIVE)
628+
.addAdditionalProperty(CodegenConstants.MODEL_PACKAGE, "xyz.abcdef.model")
629+
.addAdditionalProperty(JavaClientCodegen.USE_BEANVALIDATION, true)
630+
.addAdditionalProperty(JavaClientCodegen.USE_JAKARTA_EE, true)
631+
.setInputSpec("src/test/resources/3_1/issue-17485.yaml")
632+
.setOutputDir(output.toString().replace("\\", "/"));
633+
634+
List<File> files = new DefaultGenerator().opts(configurator.toClientOptInput()).generate();
635+
636+
validateJavaSourceFiles(files);
637+
// format: email is validated with @Email (required, so @NotNull too); the constraint
638+
// package comes from the wildcard "import jakarta.validation.constraints.*"
639+
assertThat(output.resolve("src/main/java/xyz/abcdef/model/User.java")).content()
640+
.contains("@Email", "@NotNull", "import jakarta.validation.constraints.*");
641+
}
642+
643+
@Test
644+
public void testUseBeanValidationGeneratesEmailAnnotationWithJavax() {
645+
final Path output = newTempFolder();
646+
final CodegenConfigurator configurator = new CodegenConfigurator()
647+
.setGeneratorName(JAVA_GENERATOR)
648+
.setLibrary(JavaClientCodegen.NATIVE)
649+
.addAdditionalProperty(CodegenConstants.MODEL_PACKAGE, "xyz.abcdef.model")
650+
.addAdditionalProperty(JavaClientCodegen.USE_BEANVALIDATION, true)
651+
.addAdditionalProperty(JavaClientCodegen.USE_JAKARTA_EE, false)
652+
.setInputSpec("src/test/resources/3_1/issue-17485.yaml")
653+
.setOutputDir(output.toString().replace("\\", "/"));
654+
655+
List<File> files = new DefaultGenerator().opts(configurator.toClientOptInput()).generate();
656+
657+
validateJavaSourceFiles(files);
658+
assertThat(output.resolve("src/main/java/xyz/abcdef/model/User.java")).content()
659+
.contains("@Email", "import javax.validation.constraints.*");
660+
}
661+
662+
@Test
663+
public void testWithoutBeanValidationDoesNotGenerateEmailAnnotation() {
664+
final Path output = newTempFolder();
665+
final CodegenConfigurator configurator = new CodegenConfigurator()
666+
.setGeneratorName(JAVA_GENERATOR)
667+
.setLibrary(JavaClientCodegen.NATIVE)
668+
.addAdditionalProperty(CodegenConstants.MODEL_PACKAGE, "xyz.abcdef.model")
669+
.addAdditionalProperty(JavaClientCodegen.USE_BEANVALIDATION, false)
670+
.setInputSpec("src/test/resources/3_1/issue-17485.yaml")
671+
.setOutputDir(output.toString().replace("\\", "/"));
672+
673+
List<File> files = new DefaultGenerator().opts(configurator.toClientOptInput()).generate();
674+
675+
validateJavaSourceFiles(files);
676+
assertThat(output.resolve("src/main/java/xyz/abcdef/model/User.java")).content()
677+
.doesNotContain("@Email");
678+
}
679+
622680
@Test
623681
public void testJdkHttpClientWithAndWithoutDiscriminator() {
624682
final Path output = newTempFolder();

modules/openapi-generator/src/test/resources/3_1/issue-17485.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,20 @@ paths:
2323
responses:
2424
"200":
2525
description: OK
26+
content:
27+
application/json:
28+
schema:
29+
$ref: "#/components/schemas/User"
30+
components:
31+
schemas:
32+
User:
33+
type: object
34+
required:
35+
- email
36+
properties:
37+
username:
38+
type: string
39+
email:
40+
type: string
41+
format: email
42+
description: The user's contact email address

samples/client/others/java/resttemplate-list-schema-validation/.openapi-generator/FILES

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ README.md
55
api/openapi.yaml
66
build.gradle
77
build.sbt
8+
docs/User.md
89
docs/UserApi.md
910
git_push.sh
1011
gradle.properties
@@ -28,3 +29,4 @@ src/main/java/org/openapitools/client/auth/ApiKeyAuth.java
2829
src/main/java/org/openapitools/client/auth/Authentication.java
2930
src/main/java/org/openapitools/client/auth/HttpBasicAuth.java
3031
src/main/java/org/openapitools/client/auth/HttpBearerAuth.java
32+
src/main/java/org/openapitools/client/model/User.java

samples/client/others/java/resttemplate-list-schema-validation/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ public class UserApiExample {
9595
UserApi apiInstance = new UserApi(defaultClient);
9696
List<@Pattern(regexp = "^[a-zA-Z0-9]$")String> username = Arrays.asList(); // List<@Pattern(regexp = "^[a-zA-Z0-9]$")String> | The name of the user
9797
try {
98-
apiInstance.userGet(username);
98+
User result = apiInstance.userGet(username);
99+
System.out.println(result);
99100
} catch (ApiException e) {
100101
System.err.println("Exception when calling UserApi#userGet");
101102
System.err.println("Status code: " + e.getCode());
@@ -119,6 +120,7 @@ Class | Method | HTTP request | Description
119120

120121
## Documentation for Models
121122

123+
- [User](docs/User.md)
122124

123125

124126
<a id="documentation-for-authorization"></a>

samples/client/others/java/resttemplate-list-schema-validation/api/openapi.yaml

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,28 @@ paths:
2222
style: simple
2323
responses:
2424
"200":
25+
content:
26+
application/json:
27+
schema:
28+
$ref: "#/components/schemas/User"
2529
description: OK
2630
tags:
2731
- user
2832
x-accepts:
2933
- application/json
3034
components:
31-
schemas: {}
35+
schemas:
36+
User:
37+
example:
38+
username: username
39+
email: email
40+
properties:
41+
username:
42+
type: string
43+
email:
44+
description: The user's contact email address
45+
format: email
46+
type: string
47+
required:
48+
- email
3249

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
3+
# User
4+
5+
6+
## Properties
7+
8+
| Name | Type | Description | Notes |
9+
|------------ | ------------- | ------------- | -------------|
10+
|**username** | **String** | | [optional] |
11+
|**email** | **String** | The user&#39;s contact email address | |
12+
13+
14+

samples/client/others/java/resttemplate-list-schema-validation/docs/UserApi.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ All URIs are relative to *http://api.example.xyz/v1*
1010

1111
## userGet
1212

13-
> userGet(username)
13+
> User userGet(username)
1414
1515

1616

@@ -32,7 +32,8 @@ public class Example {
3232
UserApi apiInstance = new UserApi(defaultClient);
3333
List<@Pattern(regexp = "^[a-zA-Z0-9]$")String> username = Arrays.asList(); // List<@Pattern(regexp = "^[a-zA-Z0-9]$")String> | The name of the user
3434
try {
35-
apiInstance.userGet(username);
35+
User result = apiInstance.userGet(username);
36+
System.out.println(result);
3637
} catch (ApiException e) {
3738
System.err.println("Exception when calling UserApi#userGet");
3839
System.err.println("Status code: " + e.getCode());
@@ -53,7 +54,7 @@ public class Example {
5354

5455
### Return type
5556

56-
null (empty response body)
57+
[**User**](User.md)
5758

5859
### Authorization
5960

@@ -62,7 +63,7 @@ No authorization required
6263
### HTTP request headers
6364

6465
- **Content-Type**: Not defined
65-
- **Accept**: Not defined
66+
- **Accept**: application/json
6667

6768

6869
### HTTP response details

samples/client/others/java/resttemplate-list-schema-validation/src/main/java/org/openapitools/client/api/UserApi.java

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import org.openapitools.client.ApiClient;
44
import org.openapitools.client.BaseApi;
55

6+
import org.openapitools.client.model.User;
67

78
import java.util.Collections;
89
import java.util.HashMap;
@@ -44,21 +45,22 @@ public UserApi(ApiClient apiClient) {
4445
*
4546
* <p><b>200</b> - OK
4647
* @param username The name of the user (required)
48+
* @return User
4749
* @throws RestClientException if an error occurs while attempting to invoke the API
4850
*/
49-
public void userGet(List<@Pattern(regexp = "^[a-zA-Z0-9]$")String> username) throws RestClientException {
50-
userGetWithHttpInfo(username);
51+
public User userGet(List<@Pattern(regexp = "^[a-zA-Z0-9]$")String> username) throws RestClientException {
52+
return userGetWithHttpInfo(username).getBody();
5153
}
5254

5355
/**
5456
*
5557
*
5658
* <p><b>200</b> - OK
5759
* @param username The name of the user (required)
58-
* @return ResponseEntity&lt;Void&gt;
60+
* @return ResponseEntity&lt;User&gt;
5961
* @throws RestClientException if an error occurs while attempting to invoke the API
6062
*/
61-
public ResponseEntity<Void> userGetWithHttpInfo(List<@Pattern(regexp = "^[a-zA-Z0-9]$")String> username) throws RestClientException {
63+
public ResponseEntity<User> userGetWithHttpInfo(List<@Pattern(regexp = "^[a-zA-Z0-9]$")String> username) throws RestClientException {
6264
Object localVarPostBody = null;
6365

6466
// verify the required parameter 'username' is set
@@ -75,14 +77,16 @@ public ResponseEntity<Void> userGetWithHttpInfo(List<@Pattern(regexp = "^[a-zA-Z
7577
final MultiValueMap<String, String> localVarCookieParams = new LinkedMultiValueMap<String, String>();
7678
final MultiValueMap<String, Object> localVarFormParams = new LinkedMultiValueMap<String, Object>();
7779

78-
final String[] localVarAccepts = { };
80+
final String[] localVarAccepts = {
81+
"application/json"
82+
};
7983
final List<MediaType> localVarAccept = apiClient.selectHeaderAccept(localVarAccepts);
8084
final String[] localVarContentTypes = { };
8185
final MediaType localVarContentType = apiClient.selectHeaderContentType(localVarContentTypes);
8286

8387
String[] localVarAuthNames = new String[] { };
8488

85-
ParameterizedTypeReference<Void> localReturnType = new ParameterizedTypeReference<Void>() {};
89+
ParameterizedTypeReference<User> localReturnType = new ParameterizedTypeReference<User>() {};
8690
return apiClient.invokeAPI("/user", HttpMethod.GET, uriVariables, localVarQueryParams, localVarPostBody, localVarHeaderParams, localVarCookieParams, localVarFormParams, localVarAccept, localVarContentType, localVarAuthNames, localReturnType);
8791
}
8892

@@ -97,7 +101,9 @@ public <T> ResponseEntity<T> invokeAPI(String url, HttpMethod method, Object req
97101
final MultiValueMap<String, String> localVarCookieParams = new LinkedMultiValueMap<String, String>();
98102
final MultiValueMap<String, Object> localVarFormParams = new LinkedMultiValueMap<String, Object>();
99103

100-
final String[] localVarAccepts = { };
104+
final String[] localVarAccepts = {
105+
"application/json"
106+
};
101107
final List<MediaType> localVarAccept = apiClient.selectHeaderAccept(localVarAccepts);
102108
final String[] localVarContentTypes = { };
103109
final MediaType localVarContentType = apiClient.selectHeaderContentType(localVarContentTypes);

0 commit comments

Comments
 (0)