Skip to content

Commit cd7fe34

Browse files
authored
Fix validation constraints for parameters in request body of form request are not generated (at least with Spring Boot generator) (#21749)
* fix(Spring Boot): adds validation to body params of forms requests * fix(Spring Boot): adds test for validation of body params of forms requests * fix(Spring Boot): adds samples
1 parent 66c2a28 commit cd7fe34

File tree

22 files changed

+216
-148
lines changed
  • modules/openapi-generator/src
  • samples
    • openapi3
      • client/petstore/spring-cloud-oas3-fakeapi/src/main/java/org/openapitools/api
      • server/petstore
        • springboot-delegate/src/main/java/org/openapitools/api
        • springboot-implicitHeaders/src/main/java/org/openapitools/api
    • server/petstore
      • spring-boot-defaultInterface-unhandledException/src/main/java/org/openapitools/api
      • springboot-beanvalidation-no-nullable/src/main/java/org/openapitools/api
      • springboot-beanvalidation/src/main/java/org/openapitools/api
      • springboot-builtin-validation/src/main/java/org/openapitools/api
      • springboot-delegate-j8/src/main/java/org/openapitools/api
      • springboot-delegate/src/main/java/org/openapitools/api
      • springboot-implicitHeaders/src/main/java/org/openapitools/api
      • springboot-reactive-noResponseEntity/src/main/java/org/openapitools/api
      • springboot-reactive/src/main/java/org/openapitools/api
      • springboot-spring-pageable-delegatePattern-without-j8/src/main/java/org/openapitools/api
      • springboot-spring-pageable-delegatePattern/src/main/java/org/openapitools/api
      • springboot-spring-pageable-without-j8/src/main/java/org/openapitools/api
      • springboot-spring-pageable/src/main/java/org/openapitools/api
      • springboot-useoptional/src/main/java/org/openapitools/api
      • springboot-virtualan/src/main/java/org/openapitools/virtualan/api
      • springboot/src/main/java/org/openapitools/api

22 files changed

+216
-148
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{{#isFormParam}}{{^isFile}}{{>paramDoc}}{{#useBeanValidation}} @Valid{{/useBeanValidation}} {{#isModel}}@RequestPart{{/isModel}}{{^isModel}}{{#isArray}}@RequestPart{{/isArray}}{{^isArray}}{{#reactive}}@RequestPart{{/reactive}}{{^reactive}}@RequestParam{{/reactive}}{{/isArray}}{{/isModel}}(value = "{{baseName}}"{{#required}}, required = true{{/required}}{{^required}}, required = false{{/required}}){{>dateTimeParam}} {{^required}}{{#useOptional}}Optional<{{/useOptional}}{{/required}}{{{dataType}}}{{^required}}{{#useOptional}}>{{/useOptional}}{{/required}} {{paramName}}{{/isFile}}{{#isFile}}{{>paramDoc}} @RequestPart(value = "{{baseName}}"{{#required}}, required = true{{/required}}{{^required}}, required = false{{/required}}) {{#reactive}}{{#isArray}}Flux<{{/isArray}}Part{{#isArray}}>{{/isArray}}{{/reactive}}{{^reactive}}{{#isArray}}List<{{/isArray}}MultipartFile{{#isArray}}>{{/isArray}}{{/reactive}} {{paramName}}{{/isFile}}{{/isFormParam}}
1+
{{#isFormParam}}{{^isFile}}{{>paramDoc}}{{#useBeanValidation}} {{>beanValidationBodyParams}}@Valid{{/useBeanValidation}} {{#isModel}}@RequestPart{{/isModel}}{{^isModel}}{{#isArray}}@RequestPart{{/isArray}}{{^isArray}}{{#reactive}}@RequestPart{{/reactive}}{{^reactive}}@RequestParam{{/reactive}}{{/isArray}}{{/isModel}}(value = "{{baseName}}"{{#required}}, required = true{{/required}}{{^required}}, required = false{{/required}}){{>dateTimeParam}} {{^required}}{{#useOptional}}Optional<{{/useOptional}}{{/required}}{{{dataType}}}{{^required}}{{#useOptional}}>{{/useOptional}}{{/required}} {{paramName}}{{/isFile}}{{#isFile}}{{>paramDoc}} @RequestPart(value = "{{baseName}}"{{#required}}, required = true{{/required}}{{^required}}, required = false{{/required}}) {{#reactive}}{{#isArray}}Flux<{{/isArray}}Part{{#isArray}}>{{/isArray}}{{/reactive}}{{^reactive}}{{#isArray}}List<{{/isArray}}MultipartFile{{#isArray}}>{{/isArray}}{{/reactive}} {{paramName}}{{/isFile}}{{/isFormParam}}

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2743,6 +2743,42 @@ public void useBeanValidationGenerateAnnotationsForRequestBody_issue13932() thro
27432743
.containsWithNameAndAttributes("Min", ImmutableMap.of("value", "2"));
27442744
}
27452745

2746+
@Test
2747+
public void useBeanValidationGenerateAnnotationsForFormsRequestBody() throws IOException {
2748+
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
2749+
output.deleteOnExit();
2750+
2751+
OpenAPI openAPI = new OpenAPIParser()
2752+
.readLocation("src/test/resources/3_0/spring/form-requestbody-params-with-constraints.yaml", null, new ParseOptions()).getOpenAPI();
2753+
SpringCodegen codegen = new SpringCodegen();
2754+
codegen.setLibrary(SPRING_BOOT);
2755+
codegen.setOutputDir(output.getAbsolutePath());
2756+
codegen.additionalProperties().put(SpringCodegen.INTERFACE_ONLY, "true");
2757+
codegen.additionalProperties().put(SpringCodegen.USE_BEANVALIDATION, "true");
2758+
codegen.additionalProperties().put(CodegenConstants.MODEL_PACKAGE, "xyz.model");
2759+
codegen.additionalProperties().put(CodegenConstants.API_PACKAGE, "xyz.controller");
2760+
2761+
ClientOptInput input = new ClientOptInput()
2762+
.openAPI(openAPI)
2763+
.config(codegen);
2764+
2765+
DefaultGenerator generator = new DefaultGenerator();
2766+
generator.setGenerateMetadata(false);
2767+
Map<String, File> files = generator.opts(input).generate().stream()
2768+
.collect(Collectors.toMap(File::getName, Function.identity()));
2769+
2770+
JavaFileAssert.assertThat(files.get("AddApi.java"))
2771+
.assertMethod("addPost")
2772+
.assertParameter("name")
2773+
.assertParameterAnnotations()
2774+
.containsWithNameAndAttributes("Pattern", ImmutableMap.of("regexp", "\"^[[:print:]]+$\""))
2775+
.toParameter()
2776+
.toMethod()
2777+
.assertParameter("quantity")
2778+
.assertParameterAnnotations()
2779+
.containsWithNameAndAttributes("Min", ImmutableMap.of("value", "1"));
2780+
}
2781+
27462782
@Test
27472783
public void shouldHandleSeparatelyInterfaceAndModelAdditionalAnnotations() throws IOException {
27482784
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
openapi: 3.0.1
2+
info:
3+
title: Test form
4+
version: 1.0.0
5+
servers:
6+
- url: https://where.am.i
7+
paths:
8+
/add:
9+
post:
10+
requestBody:
11+
required: true
12+
content:
13+
application/x-www-form-urlencoded:
14+
schema:
15+
type: object
16+
required:
17+
- id
18+
- quantity
19+
properties:
20+
name:
21+
type: string
22+
pattern: '^[[:print:]]+$'
23+
quantity:
24+
type: integer
25+
minimum: 1
26+
responses:
27+
'200':
28+
description: OK
29+
content:
30+
application/json:
31+
schema:
32+
type: boolean

samples/openapi3/client/petstore/spring-cloud-oas3-fakeapi/src/main/java/org/openapitools/api/FakeApi.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -324,19 +324,19 @@ ResponseEntity<Client> testClientModel(
324324
)
325325

326326
ResponseEntity<Void> testEndpointParameters(
327-
@Parameter(name = "number", description = "None", required = true) @Valid @RequestParam(value = "number", required = true) BigDecimal number,
328-
@Parameter(name = "double", description = "None", required = true) @Valid @RequestParam(value = "double", required = true) Double _double,
329-
@Parameter(name = "pattern_without_delimiter", description = "None", required = true) @Valid @RequestParam(value = "pattern_without_delimiter", required = true) String patternWithoutDelimiter,
327+
@Parameter(name = "number", description = "None", required = true) @DecimalMin("32.1") @DecimalMax("543.2") @Valid @RequestParam(value = "number", required = true) BigDecimal number,
328+
@Parameter(name = "double", description = "None", required = true) @DecimalMin("67.8") @DecimalMax("123.4") @Valid @RequestParam(value = "double", required = true) Double _double,
329+
@Parameter(name = "pattern_without_delimiter", description = "None", required = true) @Pattern(regexp = "^[A-Z].*") @Valid @RequestParam(value = "pattern_without_delimiter", required = true) String patternWithoutDelimiter,
330330
@Parameter(name = "byte", description = "None", required = true) @Valid @RequestParam(value = "byte", required = true) byte[] _byte,
331-
@Parameter(name = "integer", description = "None") @Valid @RequestParam(value = "integer", required = false) Integer integer,
332-
@Parameter(name = "int32", description = "None") @Valid @RequestParam(value = "int32", required = false) Integer int32,
331+
@Parameter(name = "integer", description = "None") @Min(10) @Max(100) @Valid @RequestParam(value = "integer", required = false) Integer integer,
332+
@Parameter(name = "int32", description = "None") @Min(20) @Max(200) @Valid @RequestParam(value = "int32", required = false) Integer int32,
333333
@Parameter(name = "int64", description = "None") @Valid @RequestParam(value = "int64", required = false) Long int64,
334-
@Parameter(name = "float", description = "None") @Valid @RequestParam(value = "float", required = false) Float _float,
335-
@Parameter(name = "string", description = "None") @Valid @RequestParam(value = "string", required = false) String string,
334+
@Parameter(name = "float", description = "None") @DecimalMax("987.6") @Valid @RequestParam(value = "float", required = false) Float _float,
335+
@Parameter(name = "string", description = "None") @Pattern(regexp = "/[a-z]/i") @Valid @RequestParam(value = "string", required = false) String string,
336336
@Parameter(name = "binary", description = "None") @RequestPart(value = "binary", required = false) MultipartFile binary,
337337
@Parameter(name = "date", description = "None") @Valid @RequestParam(value = "date", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date,
338338
@Parameter(name = "dateTime", description = "None") @Valid @RequestParam(value = "dateTime", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime dateTime,
339-
@Parameter(name = "password", description = "None") @Valid @RequestParam(value = "password", required = false) String password,
339+
@Parameter(name = "password", description = "None") @Size(min = 10, max = 64) @Valid @RequestParam(value = "password", required = false) String password,
340340
@Parameter(name = "callback", description = "None") @Valid @RequestParam(value = "callback", required = false) String paramCallback
341341
);
342342

samples/openapi3/server/petstore/springboot-delegate/src/main/java/org/openapitools/api/FakeApi.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -371,19 +371,19 @@ default ResponseEntity<Client> testClientModel(
371371
)
372372

373373
default ResponseEntity<Void> testEndpointParameters(
374-
@Parameter(name = "number", description = "None", required = true) @Valid @RequestParam(value = "number", required = true) BigDecimal number,
375-
@Parameter(name = "double", description = "None", required = true) @Valid @RequestParam(value = "double", required = true) Double _double,
376-
@Parameter(name = "pattern_without_delimiter", description = "None", required = true) @Valid @RequestParam(value = "pattern_without_delimiter", required = true) String patternWithoutDelimiter,
374+
@Parameter(name = "number", description = "None", required = true) @DecimalMin("32.1") @DecimalMax("543.2") @Valid @RequestParam(value = "number", required = true) BigDecimal number,
375+
@Parameter(name = "double", description = "None", required = true) @DecimalMin("67.8") @DecimalMax("123.4") @Valid @RequestParam(value = "double", required = true) Double _double,
376+
@Parameter(name = "pattern_without_delimiter", description = "None", required = true) @Pattern(regexp = "^[A-Z].*") @Valid @RequestParam(value = "pattern_without_delimiter", required = true) String patternWithoutDelimiter,
377377
@Parameter(name = "byte", description = "None", required = true) @Valid @RequestParam(value = "byte", required = true) byte[] _byte,
378-
@Parameter(name = "integer", description = "None") @Valid @RequestParam(value = "integer", required = false) Integer integer,
379-
@Parameter(name = "int32", description = "None") @Valid @RequestParam(value = "int32", required = false) Integer int32,
378+
@Parameter(name = "integer", description = "None") @Min(10) @Max(100) @Valid @RequestParam(value = "integer", required = false) Integer integer,
379+
@Parameter(name = "int32", description = "None") @Min(20) @Max(200) @Valid @RequestParam(value = "int32", required = false) Integer int32,
380380
@Parameter(name = "int64", description = "None") @Valid @RequestParam(value = "int64", required = false) Long int64,
381-
@Parameter(name = "float", description = "None") @Valid @RequestParam(value = "float", required = false) Float _float,
382-
@Parameter(name = "string", description = "None") @Valid @RequestParam(value = "string", required = false) String string,
381+
@Parameter(name = "float", description = "None") @DecimalMax("987.6") @Valid @RequestParam(value = "float", required = false) Float _float,
382+
@Parameter(name = "string", description = "None") @Pattern(regexp = "/[a-z]/i") @Valid @RequestParam(value = "string", required = false) String string,
383383
@Parameter(name = "binary", description = "None") @RequestPart(value = "binary", required = false) MultipartFile binary,
384384
@Parameter(name = "date", description = "None") @Valid @RequestParam(value = "date", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date,
385385
@Parameter(name = "dateTime", description = "None") @Valid @RequestParam(value = "dateTime", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime dateTime,
386-
@Parameter(name = "password", description = "None") @Valid @RequestParam(value = "password", required = false) String password,
386+
@Parameter(name = "password", description = "None") @Size(min = 10, max = 64) @Valid @RequestParam(value = "password", required = false) String password,
387387
@Parameter(name = "callback", description = "None") @Valid @RequestParam(value = "callback", required = false) String paramCallback
388388
) {
389389
return getDelegate().testEndpointParameters(number, _double, patternWithoutDelimiter, _byte, integer, int32, int64, _float, string, binary, date, dateTime, password, paramCallback);

samples/openapi3/server/petstore/springboot-implicitHeaders/src/main/java/org/openapitools/api/FakeApi.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -411,19 +411,19 @@ default ResponseEntity<Client> testClientModel(
411411
)
412412

413413
default ResponseEntity<Void> testEndpointParameters(
414-
@Parameter(name = "number", description = "None", required = true) @Valid @RequestParam(value = "number", required = true) BigDecimal number,
415-
@Parameter(name = "double", description = "None", required = true) @Valid @RequestParam(value = "double", required = true) Double _double,
416-
@Parameter(name = "pattern_without_delimiter", description = "None", required = true) @Valid @RequestParam(value = "pattern_without_delimiter", required = true) String patternWithoutDelimiter,
414+
@Parameter(name = "number", description = "None", required = true) @DecimalMin("32.1") @DecimalMax("543.2") @Valid @RequestParam(value = "number", required = true) BigDecimal number,
415+
@Parameter(name = "double", description = "None", required = true) @DecimalMin("67.8") @DecimalMax("123.4") @Valid @RequestParam(value = "double", required = true) Double _double,
416+
@Parameter(name = "pattern_without_delimiter", description = "None", required = true) @Pattern(regexp = "^[A-Z].*") @Valid @RequestParam(value = "pattern_without_delimiter", required = true) String patternWithoutDelimiter,
417417
@Parameter(name = "byte", description = "None", required = true) @Valid @RequestParam(value = "byte", required = true) byte[] _byte,
418-
@Parameter(name = "integer", description = "None") @Valid @RequestParam(value = "integer", required = false) Integer integer,
419-
@Parameter(name = "int32", description = "None") @Valid @RequestParam(value = "int32", required = false) Integer int32,
418+
@Parameter(name = "integer", description = "None") @Min(10) @Max(100) @Valid @RequestParam(value = "integer", required = false) Integer integer,
419+
@Parameter(name = "int32", description = "None") @Min(20) @Max(200) @Valid @RequestParam(value = "int32", required = false) Integer int32,
420420
@Parameter(name = "int64", description = "None") @Valid @RequestParam(value = "int64", required = false) Long int64,
421-
@Parameter(name = "float", description = "None") @Valid @RequestParam(value = "float", required = false) Float _float,
422-
@Parameter(name = "string", description = "None") @Valid @RequestParam(value = "string", required = false) String string,
421+
@Parameter(name = "float", description = "None") @DecimalMax("987.6") @Valid @RequestParam(value = "float", required = false) Float _float,
422+
@Parameter(name = "string", description = "None") @Pattern(regexp = "/[a-z]/i") @Valid @RequestParam(value = "string", required = false) String string,
423423
@Parameter(name = "binary", description = "None") @RequestPart(value = "binary", required = false) MultipartFile binary,
424424
@Parameter(name = "date", description = "None") @Valid @RequestParam(value = "date", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date,
425425
@Parameter(name = "dateTime", description = "None") @Valid @RequestParam(value = "dateTime", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime dateTime,
426-
@Parameter(name = "password", description = "None") @Valid @RequestParam(value = "password", required = false) String password,
426+
@Parameter(name = "password", description = "None") @Size(min = 10, max = 64) @Valid @RequestParam(value = "password", required = false) String password,
427427
@Parameter(name = "callback", description = "None") @Valid @RequestParam(value = "callback", required = false) String paramCallback
428428
) {
429429
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);

samples/server/petstore/spring-boot-defaultInterface-unhandledException/src/main/java/org/openapitools/api/FakeApi.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -353,19 +353,19 @@ ResponseEntity<Client> testClientModel(
353353
)
354354

355355
ResponseEntity<Void> testEndpointParameters(
356-
@Parameter(name = "number", description = "None", required = true) @Valid @RequestParam(value = "number", required = true) BigDecimal number,
357-
@Parameter(name = "double", description = "None", required = true) @Valid @RequestParam(value = "double", required = true) Double _double,
358-
@Parameter(name = "pattern_without_delimiter", description = "None", required = true) @Valid @RequestParam(value = "pattern_without_delimiter", required = true) String patternWithoutDelimiter,
356+
@Parameter(name = "number", description = "None", required = true) @DecimalMin("32.1") @DecimalMax("543.2") @Valid @RequestParam(value = "number", required = true) BigDecimal number,
357+
@Parameter(name = "double", description = "None", required = true) @DecimalMin("67.8") @DecimalMax("123.4") @Valid @RequestParam(value = "double", required = true) Double _double,
358+
@Parameter(name = "pattern_without_delimiter", description = "None", required = true) @Pattern(regexp = "^[A-Z].*") @Valid @RequestParam(value = "pattern_without_delimiter", required = true) String patternWithoutDelimiter,
359359
@Parameter(name = "byte", description = "None", required = true) @Valid @RequestParam(value = "byte", required = true) byte[] _byte,
360-
@Parameter(name = "integer", description = "None") @Valid @RequestParam(value = "integer", required = false) Integer integer,
361-
@Parameter(name = "int32", description = "None") @Valid @RequestParam(value = "int32", required = false) Integer int32,
360+
@Parameter(name = "integer", description = "None") @Min(10) @Max(100) @Valid @RequestParam(value = "integer", required = false) Integer integer,
361+
@Parameter(name = "int32", description = "None") @Min(20) @Max(200) @Valid @RequestParam(value = "int32", required = false) Integer int32,
362362
@Parameter(name = "int64", description = "None") @Valid @RequestParam(value = "int64", required = false) Long int64,
363-
@Parameter(name = "float", description = "None") @Valid @RequestParam(value = "float", required = false) Float _float,
364-
@Parameter(name = "string", description = "None") @Valid @RequestParam(value = "string", required = false) String string,
363+
@Parameter(name = "float", description = "None") @DecimalMax("987.6") @Valid @RequestParam(value = "float", required = false) Float _float,
364+
@Parameter(name = "string", description = "None") @Pattern(regexp = "/[a-z]/i") @Valid @RequestParam(value = "string", required = false) String string,
365365
@Parameter(name = "binary", description = "None") @RequestPart(value = "binary", required = false) MultipartFile binary,
366366
@Parameter(name = "date", description = "None") @Valid @RequestParam(value = "date", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date,
367367
@Parameter(name = "dateTime", description = "None") @Valid @RequestParam(value = "dateTime", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime dateTime,
368-
@Parameter(name = "password", description = "None") @Valid @RequestParam(value = "password", required = false) String password,
368+
@Parameter(name = "password", description = "None") @Size(min = 10, max = 64) @Valid @RequestParam(value = "password", required = false) String password,
369369
@Parameter(name = "callback", description = "None") @Valid @RequestParam(value = "callback", required = false) String paramCallback
370370
) throws Exception;
371371

0 commit comments

Comments
 (0)