Skip to content

Commit 5e85d53

Browse files
committed
pageable-scan-resolve-allof-constraints refactor: streamline pageable handling and enhance annotation application logic
1 parent edd4a5f commit 5e85d53

7 files changed

Lines changed: 782 additions & 253 deletions

File tree

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinSpringServerCodegen.java

Lines changed: 24 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -197,20 +197,14 @@ public String getDescription() {
197197
private Map<String, String> sealedInterfaceToOperationId = new HashMap<>();
198198
private boolean sealedInterfacesFileWritten = false;
199199

200-
// Map from operationId to allowed sort values for @ValidSort annotation generation
201-
private Map<String, List<String>> sortValidationEnums = new HashMap<>();
202-
203-
// Map from operationId to pageable defaults for @PageableDefault/@SortDefault annotation generation
204-
private Map<String, SpringPageableScanUtils.PageableDefaultsData> pageableDefaultsRegistry = new HashMap<>();
205-
206-
// Map from operationId to pageable constraints for @ValidPageable annotation generation
207-
private Map<String, SpringPageableScanUtils.PageableConstraintsData> pageableConstraintsRegistry = new HashMap<>();
208-
209200
// Map from schema name to detected paged-model info (populated when substituteGenericPagedModel=true)
210201
private Map<String, PagedModelScanUtils.DetectedPagedModel> pagedModelRegistry = new HashMap<>();
211202
// Simple class name of the PagedModel substitute (derived from importMapping; defaults to "PagedModel")
212203
private String pagedModelClassName = "PagedModel";
213204

205+
// Holds scan results for Spring Pageable features (populated during preprocessOpenAPI)
206+
private final SpringPageableScanUtils pageableUtils = new SpringPageableScanUtils();
207+
214208
public KotlinSpringServerCodegen() {
215209
super();
216210

@@ -1038,34 +1032,16 @@ public void addOperationToGroup(String tag, String resourcePath, Operation opera
10381032
*/
10391033
@Override
10401034
public CodegenOperation fromOperation(String path, String httpMethod, Operation operation, List<io.swagger.v3.oas.models.servers.Server> servers) {
1041-
// #8315 Spring Data Web default query params recognized by Pageable
1042-
List<String> defaultPageableQueryParams = Arrays.asList("page", "size", "sort");
1035+
// Auto-detect pagination parameters and set x-spring-paginated if autoXSpringPaginated is enabled.
1036+
// Must be done BEFORE super.fromOperation() so that the base codegen populates
1037+
// codegenOperation.vendorExtensions from the extension we just set on 'operation'.
1038+
// Only for spring-boot library; respect manual x-spring-paginated: false override.
1039+
if (SPRING_BOOT.equals(library)) {
1040+
SpringPageableScanUtils.applyAutoXSpringPaginatedIfNeeded(operation, autoXSpringPaginated);
1041+
}
10431042

10441043
CodegenOperation codegenOperation = super.fromOperation(path, httpMethod, operation, servers);
10451044

1046-
// Check if operation has all three pagination query parameters (case-sensitive)
1047-
boolean hasParamsForPageable = codegenOperation.queryParams.stream()
1048-
.map(p -> p.baseName)
1049-
.collect(Collectors.toSet())
1050-
.containsAll(defaultPageableQueryParams);
1051-
// Auto-detect pagination parameters and add x-spring-paginated if autoXSpringPaginated is enabled
1052-
// Only for spring-boot library, respect manual x-spring-paginated: false setting
1053-
if (SPRING_BOOT.equals(library) && autoXSpringPaginated) {
1054-
// Check if x-spring-paginated is not explicitly set to false
1055-
if (operation.getExtensions() == null || !Boolean.FALSE.equals(operation.getExtensions().get("x-spring-paginated"))) {
1056-
1057-
1058-
if (hasParamsForPageable) {
1059-
// Automatically add x-spring-paginated to the operation
1060-
if (operation.getExtensions() == null) {
1061-
operation.setExtensions(new HashMap<>());
1062-
}
1063-
operation.getExtensions().put("x-spring-paginated", Boolean.TRUE);
1064-
codegenOperation.vendorExtensions.put("x-spring-paginated", Boolean.TRUE);
1065-
}
1066-
}
1067-
}
1068-
10691045
// Only process x-spring-paginated for server-side libraries (spring-boot)
10701046
// Client libraries (spring-cloud, spring-declarative-http-interface) need actual query parameters for HTTP requests
10711047
if (SPRING_BOOT.equals(library)) {
@@ -1078,75 +1054,16 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation
10781054
// add org.springframework.data.domain.Pageable import when needed (server libraries only)
10791055
if (operation.getExtensions() != null && Boolean.TRUE.equals(operation.getExtensions().get("x-spring-paginated"))) {
10801056
codegenOperation.imports.add("Pageable");
1081-
if (DocumentationProvider.SPRINGDOC.equals(getDocumentationProvider())) {
1082-
codegenOperation.imports.add("PageableAsQueryParam");
1083-
// Prepend @PageableAsQueryParam to existing x-operation-extra-annotation if present
1084-
// Use getObjectAsStringList to properly handle both list and string formats:
1085-
// - YAML list: ['@Ann1', '@Ann2'] -> List of annotations
1086-
// - Single string: '@Ann1 @Ann2' -> Single-element list
1087-
// - Nothing/null -> Empty list
1088-
Object existingAnnotation = codegenOperation.vendorExtensions.get("x-operation-extra-annotation");
1089-
List<String> annotations = DefaultCodegen.getObjectAsStringList(existingAnnotation);
1090-
1091-
// Prepend @PageableAsQueryParam to the beginning of the list
1092-
List<String> updatedAnnotations = new ArrayList<>();
1093-
updatedAnnotations.add("@PageableAsQueryParam");
1094-
updatedAnnotations.addAll(annotations);
1095-
1096-
codegenOperation.vendorExtensions.put("x-operation-extra-annotation", updatedAnnotations);
1097-
}
1057+
SpringPageableScanUtils.applySpringDocPageableAnnotation(codegenOperation,
1058+
SpringPageableScanUtils.AnnotationSyntax.KOTLIN,
1059+
DocumentationProvider.SPRINGDOC.equals(getDocumentationProvider()));
10981060

10991061
// #8315 Remove matching Spring Data Web default query params if 'x-spring-paginated' with Pageable is used
1100-
// Build pageable parameter annotations (@ValidPageable, @ValidSort, @PageableDefault, @SortDefault.SortDefaults)
1101-
List<String> pageableAnnotations = new ArrayList<>();
1102-
1103-
if (generatePageableConstraintValidation && useBeanValidation && pageableConstraintsRegistry.containsKey(codegenOperation.operationId)) {
1104-
SpringPageableScanUtils.PageableConstraintsData constraints = pageableConstraintsRegistry.get(codegenOperation.operationId);
1105-
List<String> attrs = new ArrayList<>();
1106-
if (constraints.maxSize >= 0) attrs.add("maxSize = " + constraints.maxSize);
1107-
if (constraints.maxPage >= 0) attrs.add("maxPage = " + constraints.maxPage);
1108-
if (constraints.minSize >= 0) attrs.add("minSize = " + constraints.minSize);
1109-
if (constraints.minPage >= 0) attrs.add("minPage = " + constraints.minPage);
1110-
pageableAnnotations.add("@ValidPageable(" + String.join(", ", attrs) + ")");
1111-
codegenOperation.imports.add("ValidPageable");
1112-
}
1113-
1114-
if (generateSortValidation && useBeanValidation && sortValidationEnums.containsKey(codegenOperation.operationId)) {
1115-
List<String> allowedSortValues = sortValidationEnums.get(codegenOperation.operationId);
1116-
String allowedValuesStr = allowedSortValues.stream()
1117-
.map(v -> "\"" + v.replace("\\", "\\\\").replace("\"", "\\\"") + "\"")
1118-
.collect(Collectors.joining(", "));
1119-
pageableAnnotations.add("@ValidSort(allowedValues = [" + allowedValuesStr + "])");
1120-
codegenOperation.imports.add("ValidSort");
1121-
}
1122-
1123-
// Generate @PageableDefault / @SortDefault.SortDefaults annotations if defaults are present
1124-
if (pageableDefaultsRegistry.containsKey(codegenOperation.operationId)) {
1125-
SpringPageableScanUtils.PageableDefaultsData defaults = pageableDefaultsRegistry.get(codegenOperation.operationId);
1126-
1127-
if (defaults.page != null || defaults.size != null) {
1128-
List<String> attrs = new ArrayList<>();
1129-
if (defaults.page != null) attrs.add("page = " + defaults.page);
1130-
if (defaults.size != null) attrs.add("size = " + defaults.size);
1131-
pageableAnnotations.add("@PageableDefault(" + String.join(", ", attrs) + ")");
1132-
codegenOperation.imports.add("PageableDefault");
1133-
}
1134-
1135-
if (!defaults.sortDefaults.isEmpty()) {
1136-
List<String> sortEntries = defaults.sortDefaults.stream()
1137-
.map(sf -> "SortDefault(sort = [\"" + sf.field + "\"], direction = Sort.Direction." + sf.direction + ")")
1138-
.collect(Collectors.toList());
1139-
pageableAnnotations.add("@SortDefault.SortDefaults(" + String.join(", ", sortEntries) + ")");
1140-
codegenOperation.imports.add("SortDefault");
1141-
codegenOperation.imports.add("Sort");
1142-
}
1143-
}
1144-
1145-
if (!pageableAnnotations.isEmpty()) {
1146-
codegenOperation.vendorExtensions.put("x-pageable-extra-annotation", pageableAnnotations);
1147-
}
1148-
codegenOperation.queryParams.removeIf(param -> defaultPageableQueryParams.contains(param.baseName));
1149-
codegenOperation.allParams.removeIf(param -> param.isQueryParam && defaultPageableQueryParams.contains(param.baseName));
1062+
// Build and attach pageable parameter annotations
1063+
SpringPageableScanUtils.removePageableQueryParams(codegenOperation);
1064+
pageableUtils.applyPageableAnnotations(codegenOperation,
1065+
generatePageableConstraintValidation, useBeanValidation,
1066+
generateSortValidation, SpringPageableScanUtils.AnnotationSyntax.KOTLIN);
11501067
}
11511068
}
11521069

@@ -1191,27 +1108,22 @@ public void preprocessOpenAPI(OpenAPI openAPI) {
11911108
(sourceFolder + File.separator + configPackage).replace(".", java.io.File.separator), "EnumConverterConfiguration.kt"));
11921109
}
11931110

1194-
if (SPRING_BOOT.equals(library) && generateSortValidation && useBeanValidation) {
1195-
sortValidationEnums = SpringPageableScanUtils.scanSortValidationEnums(openAPI, autoXSpringPaginated);
1196-
if (!sortValidationEnums.isEmpty()) {
1111+
if (SPRING_BOOT.equals(library)) {
1112+
pageableUtils.scanAll(openAPI, autoXSpringPaginated);
1113+
1114+
if (generateSortValidation && useBeanValidation && !pageableUtils.sortValidationEnums.isEmpty()) {
11971115
importMapping.putIfAbsent("ValidSort", configPackage + ".ValidSort");
11981116
supportingFiles.add(new SupportingFile("validSort.mustache",
11991117
(sourceFolder + File.separator + configPackage).replace(".", File.separator), "ValidSort.kt"));
12001118
}
1201-
}
12021119

1203-
if (SPRING_BOOT.equals(library)) {
1204-
pageableDefaultsRegistry = SpringPageableScanUtils.scanPageableDefaults(openAPI, autoXSpringPaginated);
1205-
if (!pageableDefaultsRegistry.isEmpty()) {
1120+
if (!pageableUtils.pageableDefaultsRegistry.isEmpty()) {
12061121
importMapping.putIfAbsent("PageableDefault", "org.springframework.data.web.PageableDefault");
12071122
importMapping.putIfAbsent("SortDefault", "org.springframework.data.web.SortDefault");
12081123
importMapping.putIfAbsent("Sort", "org.springframework.data.domain.Sort");
12091124
}
1210-
}
12111125

1212-
if (SPRING_BOOT.equals(library) && generatePageableConstraintValidation && useBeanValidation) {
1213-
pageableConstraintsRegistry = SpringPageableScanUtils.scanPageableConstraints(openAPI, autoXSpringPaginated);
1214-
if (!pageableConstraintsRegistry.isEmpty()) {
1126+
if (generatePageableConstraintValidation && useBeanValidation && !pageableUtils.pageableConstraintsRegistry.isEmpty()) {
12151127
importMapping.putIfAbsent("ValidPageable", configPackage + ".ValidPageable");
12161128
supportingFiles.add(new SupportingFile("validPageable.mustache",
12171129
(sourceFolder + File.separator + configPackage).replace(".", File.separator), "ValidPageable.kt"));

0 commit comments

Comments
 (0)