Skip to content

Commit 52270e9

Browse files
oliverkuntzeGoopher Maijenburg
authored andcommitted
Fix validation constraints for parameters in request body of form request are not generated (at least with Spring Boot generator) (OpenAPITools#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 2e30dbd commit 52270e9

22 files changed

Lines changed: 216 additions & 148 deletions

File tree

  • 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
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)