1717package org .openapitools .codegen .languages ;
1818
1919import com .google .common .collect .ImmutableMap ;
20+ import com .fasterxml .jackson .databind .node .ArrayNode ;
2021import com .samskivert .mustache .Mustache ;
2122import com .samskivert .mustache .Mustache .Lambda ;
2223import com .samskivert .mustache .Template ;
@@ -188,6 +189,9 @@ public String getDescription() {
188189 // Map from operationId to allowed sort values for @ValidSort annotation generation
189190 private Map <String , List <String >> sortValidationEnums = new HashMap <>();
190191
192+ // Map from operationId to pageable defaults for @PageableDefault/@SortDefault annotation generation
193+ private Map <String , PageableDefaultsData > pageableDefaultsRegistry = new HashMap <>();
194+
191195 public KotlinSpringServerCodegen () {
192196 super ();
193197
@@ -1071,6 +1075,33 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation
10711075
10721076 codegenOperation .imports .add ("ValidSort" );
10731077 }
1078+
1079+ // Generate @PageableDefault / @SortDefault.SortDefaults annotations if defaults are present
1080+ if (pageableDefaultsRegistry .containsKey (codegenOperation .operationId )) {
1081+ PageableDefaultsData defaults = pageableDefaultsRegistry .get (codegenOperation .operationId );
1082+ List <String > pageableAnnotations = new ArrayList <>();
1083+
1084+ if (defaults .page != null || defaults .size != null ) {
1085+ List <String > attrs = new ArrayList <>();
1086+ if (defaults .page != null ) attrs .add ("page = " + defaults .page );
1087+ if (defaults .size != null ) attrs .add ("size = " + defaults .size );
1088+ pageableAnnotations .add ("@PageableDefault(" + String .join (", " , attrs ) + ")" );
1089+ codegenOperation .imports .add ("PageableDefault" );
1090+ }
1091+
1092+ if (!defaults .sortDefaults .isEmpty ()) {
1093+ List <String > sortEntries = defaults .sortDefaults .stream ()
1094+ .map (sf -> "SortDefault(sort = [\" " + sf .field + "\" ], direction = Sort.Direction." + sf .direction + ")" )
1095+ .collect (Collectors .toList ());
1096+ pageableAnnotations .add ("@SortDefault.SortDefaults(" + String .join (", " , sortEntries ) + ")" );
1097+ codegenOperation .imports .add ("SortDefault" );
1098+ codegenOperation .imports .add ("Sort" );
1099+ }
1100+
1101+ if (!pageableAnnotations .isEmpty ()) {
1102+ codegenOperation .vendorExtensions .put ("x-pageable-extra-annotation" , pageableAnnotations );
1103+ }
1104+ }
10741105 codegenOperation .queryParams .removeIf (param -> defaultPageableQueryParams .contains (param .baseName ));
10751106 codegenOperation .allParams .removeIf (param -> param .isQueryParam && defaultPageableQueryParams .contains (param .baseName ));
10761107 }
@@ -1091,6 +1122,10 @@ public void preprocessOpenAPI(OpenAPI openAPI) {
10911122 scanSortValidationEnums (openAPI );
10921123 }
10931124
1125+ if (SPRING_BOOT .equals (library )) {
1126+ scanPageableDefaults (openAPI );
1127+ }
1128+
10941129 if (!additionalProperties .containsKey (TITLE )) {
10951130 // The purpose of the title is for:
10961131 // - README documentation
@@ -1156,6 +1191,115 @@ public void preprocessOpenAPI(OpenAPI openAPI) {
11561191 // TODO: Handle tags
11571192 }
11581193
1194+ /**
1195+ * Scans the OpenAPI spec for pageable operations whose page/size/sort parameters have default values,
1196+ * builds the {@link #pageableDefaultsRegistry}, and registers required import mappings.
1197+ * Called from {@link #preprocessOpenAPI} for all spring-boot generations.
1198+ */
1199+ private void scanPageableDefaults (OpenAPI openAPI ) {
1200+ if (openAPI .getPaths () == null ) {
1201+ return ;
1202+ }
1203+ for (Map .Entry <String , PathItem > pathEntry : openAPI .getPaths ().entrySet ()) {
1204+ for (Operation operation : pathEntry .getValue ().readOperations ()) {
1205+ String operationId = operation .getOperationId ();
1206+ if (operationId == null || !willBePageable (operation )) {
1207+ continue ;
1208+ }
1209+ if (operation .getParameters () == null ) {
1210+ continue ;
1211+ }
1212+ Integer pageDefault = null ;
1213+ Integer sizeDefault = null ;
1214+ List <SortFieldDefault > sortDefaults = new ArrayList <>();
1215+
1216+ for (Parameter param : operation .getParameters ()) {
1217+ Schema <?> schema = param .getSchema ();
1218+ if (schema == null ) {
1219+ continue ;
1220+ }
1221+ if (schema .get$ref () != null ) {
1222+ schema = ModelUtils .getReferencedSchema (openAPI , schema );
1223+ }
1224+ if (schema == null || schema .getDefault () == null ) {
1225+ continue ;
1226+ }
1227+ Object defaultValue = schema .getDefault ();
1228+ switch (param .getName ()) {
1229+ case "page" :
1230+ if (defaultValue instanceof Number ) {
1231+ pageDefault = ((Number ) defaultValue ).intValue ();
1232+ }
1233+ break ;
1234+ case "size" :
1235+ if (defaultValue instanceof Number ) {
1236+ sizeDefault = ((Number ) defaultValue ).intValue ();
1237+ }
1238+ break ;
1239+ case "sort" :
1240+ List <String > sortValues = new ArrayList <>();
1241+ if (defaultValue instanceof String ) {
1242+ sortValues .add ((String ) defaultValue );
1243+ } else if (defaultValue instanceof ArrayNode ) {
1244+ ((ArrayNode ) defaultValue ).forEach (node -> sortValues .add (node .asText ()));
1245+ } else if (defaultValue instanceof List ) {
1246+ for (Object item : (List <?>) defaultValue ) {
1247+ sortValues .add (item .toString ());
1248+ }
1249+ }
1250+ for (String sortStr : sortValues ) {
1251+ String [] parts = sortStr .split ("," , 2 );
1252+ String field = parts [0 ].trim ();
1253+ String direction = parts .length > 1 ? parts [1 ].trim ().toUpperCase (Locale .ROOT ) : "ASC" ;
1254+ sortDefaults .add (new SortFieldDefault (field , direction ));
1255+ }
1256+ break ;
1257+ default :
1258+ break ;
1259+ }
1260+ }
1261+
1262+ PageableDefaultsData data = new PageableDefaultsData (pageDefault , sizeDefault , sortDefaults );
1263+ if (data .hasAny ()) {
1264+ pageableDefaultsRegistry .put (operationId , data );
1265+ }
1266+ }
1267+ }
1268+ if (!pageableDefaultsRegistry .isEmpty ()) {
1269+ importMapping .putIfAbsent ("PageableDefault" , "org.springframework.data.web.PageableDefault" );
1270+ importMapping .putIfAbsent ("SortDefault" , "org.springframework.data.web.SortDefault" );
1271+ importMapping .putIfAbsent ("Sort" , "org.springframework.data.domain.Sort" );
1272+ }
1273+ }
1274+
1275+ /** Carries a parsed sort field and its direction (always "ASC" or "DESC") from the spec default. */
1276+ private static final class SortFieldDefault {
1277+ final String field ;
1278+ final String direction ;
1279+
1280+ SortFieldDefault (String field , String direction ) {
1281+ this .field = field ;
1282+ this .direction = direction ;
1283+ }
1284+ }
1285+
1286+ /** Carries parsed default values for page, size, and sort fields from a pageable operation. */
1287+ private static final class PageableDefaultsData {
1288+ final Integer page ;
1289+ final Integer size ;
1290+ final List <SortFieldDefault > sortDefaults ;
1291+
1292+ PageableDefaultsData (Integer page , Integer size , List <SortFieldDefault > sortDefaults ) {
1293+ this .page = page ;
1294+ this .size = size ;
1295+ this .sortDefaults = sortDefaults ;
1296+ }
1297+
1298+ boolean hasAny () {
1299+ return page != null || size != null || !sortDefaults .isEmpty ();
1300+ }
1301+ }
1302+
11591303 /**
11601304 * Scans the OpenAPI spec for paginated operations whose 'sort' parameter has enum values,
11611305 * builds the {@link #sortValidationEnums} registry, and registers the ValidSort.kt supporting file.
0 commit comments