@@ -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