Skip to content

Commit 42bd4e1

Browse files
committed
fix(spring): generate supporting PagedModel files in config package (can be replaced with custom one via importMapping)
1 parent f7c827a commit 42bd4e1

6 files changed

Lines changed: 180 additions & 5 deletions

File tree

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1207,7 +1207,13 @@ public void preprocessOpenAPI(OpenAPI openAPI) {
12071207
if (SPRING_BOOT.equals(library) && substituteGenericPagedModel) {
12081208
pagedModelRegistry = PagedModelScanUtils.scanPagedModels(openAPI);
12091209
if (!pagedModelRegistry.isEmpty()) {
1210-
importMapping.putIfAbsent("PagedModel", "org.springframework.data.web.PagedModel");
1210+
boolean customMapping = importMapping.containsKey("PagedModel");
1211+
importMapping.putIfAbsent("PagedModel", configPackage + ".PagedModel");
1212+
if (!customMapping) {
1213+
// No custom class provided — generate the simple PagedModel into the config package.
1214+
supportingFiles.add(new SupportingFile("pagedModel.mustache",
1215+
(sourceFolder + File.separator + configPackage).replace(".", File.separator), "PagedModel.kt"));
1216+
}
12111217
// Derive the actual simple class name from the FQN in importMapping so that a
12121218
// custom mapping (e.g. "PagedModel" → "com.example.MyPagedModel") is respected.
12131219
// The simple name of the FQN becomes the token used in generated code, and is

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -872,7 +872,13 @@ public void preprocessOpenAPI(OpenAPI openAPI) {
872872
if (SPRING_BOOT.equals(library) && substituteGenericPagedModel) {
873873
pagedModelRegistry = PagedModelScanUtils.scanPagedModels(openAPI);
874874
if (!pagedModelRegistry.isEmpty()) {
875-
importMapping.putIfAbsent("PagedModel", "org.springframework.data.web.PagedModel");
875+
boolean customMapping = importMapping.containsKey("PagedModel");
876+
importMapping.putIfAbsent("PagedModel", configPackage + ".PagedModel");
877+
if (!customMapping) {
878+
// No custom class provided — generate the simple PagedModel into the config package.
879+
supportingFiles.add(new SupportingFile("pagedModel.mustache",
880+
(sourceFolder + File.separator + configPackage).replace(".", java.io.File.separator), "PagedModel.java"));
881+
}
876882
// Derive the actual simple class name from the FQN in importMapping so that a
877883
// custom mapping (e.g. "PagedModel" → "com.example.MyPagedModel") is respected.
878884
// The simple name of the FQN becomes the token used in generated code, and is
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package {{configPackage}};
2+
3+
import java.util.List;
4+
5+
/**
6+
* Simple generic paged response wrapper generated by openapi-generator.
7+
*
8+
* <p>Holds a page of content items and pagination metadata. Jackson deserializes this from the
9+
* standard Spring {@code Page} JSON shape:
10+
* <pre>
11+
* {
12+
* "content": [...],
13+
* "page": { "size": 20, "totalElements": 100, "totalPages": 5, "number": 0 }
14+
* }
15+
* </pre>
16+
*
17+
* <p>To use your own class instead, set {@code importMappings.PagedModel} in the generator config.
18+
*/
19+
public class PagedModel<T> {
20+
21+
private List<T> content;
22+
private PageMetadata page;
23+
24+
public PagedModel() {}
25+
26+
public PagedModel(List<T> content, PageMetadata page) {
27+
this.content = content;
28+
this.page = page;
29+
}
30+
31+
public List<T> getContent() {
32+
return content;
33+
}
34+
35+
public void setContent(List<T> content) {
36+
this.content = content;
37+
}
38+
39+
public PageMetadata getPage() {
40+
return page;
41+
}
42+
43+
public void setPage(PageMetadata page) {
44+
this.page = page;
45+
}
46+
47+
public static class PageMetadata {
48+
49+
private long size;
50+
private long totalElements;
51+
private long totalPages;
52+
private long number;
53+
54+
public PageMetadata() {}
55+
56+
public PageMetadata(long size, long totalElements, long totalPages, long number) {
57+
this.size = size;
58+
this.totalElements = totalElements;
59+
this.totalPages = totalPages;
60+
this.number = number;
61+
}
62+
63+
public long getSize() {
64+
return size;
65+
}
66+
67+
public void setSize(long size) {
68+
this.size = size;
69+
}
70+
71+
public long getTotalElements() {
72+
return totalElements;
73+
}
74+
75+
public void setTotalElements(long totalElements) {
76+
this.totalElements = totalElements;
77+
}
78+
79+
public long getTotalPages() {
80+
return totalPages;
81+
}
82+
83+
public void setTotalPages(long totalPages) {
84+
this.totalPages = totalPages;
85+
}
86+
87+
public long getNumber() {
88+
return number;
89+
}
90+
91+
public void setNumber(long number) {
92+
this.number = number;
93+
}
94+
}
95+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package {{configPackage}}
2+
3+
/**
4+
* Simple generic paged response wrapper generated by openapi-generator.
5+
*
6+
* Holds a page of content items and pagination metadata. Jackson deserializes this from the
7+
* standard Spring `Page` JSON shape:
8+
* ```json
9+
* {
10+
* "content": [...],
11+
* "page": { "size": 20, "totalElements": 100, "totalPages": 5, "number": 0 }
12+
* }
13+
* ```
14+
*
15+
* To use your own class instead, set `importMappings.PagedModel` in the generator config.
16+
*/
17+
data class PagedModel<T>(
18+
val content: List<T>,
19+
val page: PageMetadata,
20+
) {
21+
data class PageMetadata(
22+
val size: Long,
23+
val totalElements: Long,
24+
val totalPages: Long,
25+
val number: Long,
26+
)
27+
}

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

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7242,9 +7242,9 @@ public void substituteGenericPagedModel_importsPagedModelAndItemTypeInApiFile()
72427242
Map<String, File> files = generateFromContract(
72437243
"src/test/resources/3_0/spring/petstore-paged-model.yaml", SPRING_BOOT, props);
72447244

7245-
// The api file must import both PagedModel and the item type
7245+
// The api file must import both the generated PagedModel and the item type
72467246
JavaFileAssert.assertThat(files.get("UserApi.java"))
7247-
.fileContains("import org.springframework.data.web.PagedModel")
7247+
.fileContains("import org.openapitools.configuration.PagedModel")
72487248
.fileContains("import org.openapitools.model.User");
72497249
}
72507250

@@ -7336,6 +7336,26 @@ public void substituteGenericPagedModel_respectsSchemaMappingWithImportMappingFo
73367336
.hasReturnType("ResponseEntity<PagedModel<ExternalUser>>");
73377337
}
73387338

7339+
@Test
7340+
public void substituteGenericPagedModel_generatesPagedModelSupportingFile() throws IOException {
7341+
Map<String, File> files = generateFromContract(
7342+
"src/test/resources/3_0/spring/petstore-paged-model.yaml", SPRING_BOOT, commonPagedModelProps());
7343+
7344+
assertThat(files).containsKey("PagedModel.java");
7345+
}
7346+
7347+
@Test
7348+
public void substituteGenericPagedModel_doesNotGeneratePagedModelFileWhenCustomMapping() throws IOException {
7349+
Map<String, Object> props = commonPagedModelProps();
7350+
7351+
Map<String, File> files = generateFromContract(
7352+
"src/test/resources/3_0/spring/petstore-paged-model.yaml", SPRING_BOOT, props,
7353+
configurator -> configurator
7354+
.addImportMapping("PagedModel", "com.example.custom.MyPagedModel"));
7355+
7356+
assertThat(files).doesNotContainKey("PagedModel.java");
7357+
}
7358+
73397359
@Test
73407360
public void substituteGenericPagedModel_respectsCustomImportMappingClassName() throws IOException {
73417361
// When the user remaps "PagedModel" to a FQN with a different simple class name,

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

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5604,7 +5604,8 @@ public void substituteGenericPagedModel_importsPagedModelAndItemTypeInApiFile()
56045604
File userApi = files.get("UserApi.kt");
56055605
assertThat(userApi).isNotNull();
56065606
String content = Files.readString(userApi.toPath());
5607-
assertThat(content).contains("import org.springframework.data.web.PagedModel");
5607+
// The api file must import the generated PagedModel and the item type
5608+
assertThat(content).contains("import org.openapitools.configuration.PagedModel");
56085609
assertThat(content).contains("import org.openapitools.model.User");
56095610
}
56105611

@@ -5682,6 +5683,26 @@ public void substituteGenericPagedModel_respectsSchemaMappingForItemType() throw
56825683
assertThat(content).contains("import com.example.external.ExternalUser");
56835684
}
56845685

5686+
@Test
5687+
public void substituteGenericPagedModel_generatesPagedModelSupportingFile() throws IOException {
5688+
Map<String, File> files = generateFromContract(
5689+
"src/test/resources/3_0/spring/petstore-paged-model.yaml", commonKotlinPagedModelProps());
5690+
5691+
assertThat(files).containsKey("PagedModel.kt");
5692+
}
5693+
5694+
@Test
5695+
public void substituteGenericPagedModel_doesNotGeneratePagedModelFileWhenCustomMapping() throws IOException {
5696+
Map<String, File> files = generateFromContract(
5697+
"src/test/resources/3_0/spring/petstore-paged-model.yaml",
5698+
commonKotlinPagedModelProps(),
5699+
new HashMap<>(),
5700+
configurator -> configurator
5701+
.addImportMapping("PagedModel", "com.example.custom.MyPagedModel"));
5702+
5703+
assertThat(files).doesNotContainKey("PagedModel.kt");
5704+
}
5705+
56855706
@Test
56865707
public void substituteGenericPagedModel_respectsCustomImportMappingClassName() throws IOException {
56875708
// When the user remaps "PagedModel" to a FQN with a different simple class name,

0 commit comments

Comments
 (0)