Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/generators/java-camel.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true|
|sourceFolder|source folder for generated code| |src/main/java|
|springApiVersion|Value for 'version' attribute in @RequestMapping (for Spring 7 and above).| |null|
|substituteGenericPagedModel|Detect schemas that represent paginated responses (an object with a 'content' array property and a pagination-metadata property) and replace their generated references with org.springframework.data.web.PagedModel<T>. The detected page schemas and the pagination metadata schema are suppressed from code generation. Only applies when library=spring-boot.| |false|
|substituteGenericPagedModel|Detect schemas that represent paginated responses (an object with a 'content' array property and a 'page' pagination-metadata property) and replace their generated references with PagedModel<T> generated into config package (default 'org.openapitools.configuration'). The detected page schemas and the pagination metadata schema are suppressed from code generation. Only applies when library=spring-boot.| |false|
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
|testOutput|Set output folder for models and APIs tests| |${project.build.directory}/generated-test-sources/openapi|
|title|server title name or client service name| |OpenAPI Spring|
|unhandledException|Declare operation methods to throw a generic exception and allow unhandled exceptions (useful for Spring `@ControllerAdvice` directives).| |false|
Expand Down
2 changes: 1 addition & 1 deletion docs/generators/kotlin-spring.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|sortModelPropertiesByRequiredFlag|Sort model properties to place required parameters before optional parameters.| |null|
|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |null|
|sourceFolder|source folder for generated code| |src/main/kotlin|
|substituteGenericPagedModel|Detect schemas that represent paginated responses (an object with a 'content' array property and a pagination-metadata property) and replace their generated references with org.springframework.data.web.PagedModel<T>. The detected page schemas and the pagination metadata schema are suppressed from code generation. Only applies when library=spring-boot.| |false|
|substituteGenericPagedModel|Detect schemas that represent paginated responses (an object with a 'content' array property and a 'page' pagination-metadata property) and replace their generated references with PagedModel<T> generated into config package (default 'org.openapitools.configuration'). The detected page schemas and the pagination metadata schema are suppressed from code generation. Only applies when library=spring-boot.| |false|
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
|title|server title name or client service name| |OpenAPI Kotlin Spring|
|useBeanValidation|Use BeanValidation API annotations to validate data types| |true|
|useFeignClientUrl|Whether to generate Feign client with url parameter.| |true|
Expand Down
2 changes: 1 addition & 1 deletion docs/generators/spring.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true|
|sourceFolder|source folder for generated code| |src/main/java|
|springApiVersion|Value for 'version' attribute in @RequestMapping (for Spring 7 and above).| |null|
|substituteGenericPagedModel|Detect schemas that represent paginated responses (an object with a 'content' array property and a pagination-metadata property) and replace their generated references with org.springframework.data.web.PagedModel<T>. The detected page schemas and the pagination metadata schema are suppressed from code generation. Only applies when library=spring-boot.| |false|
|substituteGenericPagedModel|Detect schemas that represent paginated responses (an object with a 'content' array property and a 'page' pagination-metadata property) and replace their generated references with PagedModel<T> generated into config package (default 'org.openapitools.configuration'). The detected page schemas and the pagination metadata schema are suppressed from code generation. Only applies when library=spring-boot.| |false|
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
|testOutput|Set output folder for models and APIs tests| |${project.build.directory}/generated-test-sources/openapi|
|title|server title name or client service name| |OpenAPI Spring|
|unhandledException|Declare operation methods to throw a generic exception and allow unhandled exceptions (useful for Spring `@ControllerAdvice` directives).| |false|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ public String getDescription() {

// Map from schema name to detected paged-model info (populated when substituteGenericPagedModel=true)
private Map<String, PagedModelScanUtils.DetectedPagedModel> pagedModelRegistry = new HashMap<>();
// Simple class name of the PagedModel substitute (derived from importMapping; defaults to "PagedModel")
private String pagedModelClassName = "PagedModel";

public KotlinSpringServerCodegen() {
super();
Expand Down Expand Up @@ -296,9 +298,9 @@ public KotlinSpringServerCodegen() {
addSwitch(GENERATE_SORT_VALIDATION, "Generate a @ValidSort annotation and SortValidator class, and apply @ValidSort to the injected Pageable parameter of operations whose 'sort' parameter has enum values. The annotation validates that sort values in the Pageable object match the allowed enum values from the spec. Requires useBeanValidation=true and library=spring-boot.", generateSortValidation);
addSwitch(GENERATE_PAGEABLE_CONSTRAINT_VALIDATION, "Generate a @ValidPageable annotation and PageableConstraintValidator class, and apply @ValidPageable to the injected Pageable parameter of operations whose 'page' or 'size' parameter specifies a maximum constraint. The annotation enforces those constraints on the Pageable object that replaces the individual page/size query parameters. Requires useBeanValidation=true and library=spring-boot.", generatePageableConstraintValidation);
addSwitch(SUBSTITUTE_GENERIC_PAGED_MODEL,
"Detect schemas that represent paginated responses (an object with a 'content' array property and a "
"Detect schemas that represent paginated responses (an object with a 'content' array property and a 'page' "
+ "pagination-metadata property) and replace their generated references with "
+ "org.springframework.data.web.PagedModel<T>. The detected page schemas and the pagination metadata "
+ "PagedModel<T> generated into config package (default 'org.openapitools.configuration'). The detected page schemas and the pagination metadata "
+ "schema are suppressed from code generation. Only applies when library=spring-boot.",
substituteGenericPagedModel);
addSwitch(COMPANION_OBJECT, "Whether to generate companion objects in data classes, enabling companion extensions.", companionObject);
Expand Down Expand Up @@ -1142,21 +1144,24 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation
pagedModelRegistry.get(codegenOperation.returnBaseType);
if (detected != null) {
String oldType = codegenOperation.returnType;
String newBaseType = "PagedModel<" + detected.itemSchemaName + ">";
// Run through toModelName so that schemaMappings (e.g. User → com.example.MyUser)
// are honored: the mapped name is used both in the type arg and for import resolution.
String itemType = toModelName(detected.itemSchemaName);
String newBaseType = pagedModelClassName + "<" + itemType + ">";
codegenOperation.returnType = newBaseType;
codegenOperation.returnBaseType = "PagedModel";
codegenOperation.returnBaseType = pagedModelClassName;
// Clear any container flag — PagedModel is not itself a List/array
codegenOperation.returnContainer = null;
// Add item type import (needed for PagedModel<User> in method signature)
codegenOperation.imports.add(detected.itemSchemaName);
codegenOperation.imports.add("PagedModel");
// Add item type import (needed for PagedModel<T> in method signature)
codegenOperation.imports.add(itemType);
codegenOperation.imports.add(pagedModelClassName);
// Remove paged schema import when no annotations are generated —
// the class is suppressed and not referenced anywhere
if (getAnnotationLibrary() == AnnotationLibrary.NONE) {
codegenOperation.imports.remove(detected.schemaName);
}
LOGGER.info("substituteGenericPagedModel: operation '{}': replacing return type '{}' with PagedModel<{}>",
codegenOperation.operationId, oldType, detected.itemSchemaName);
LOGGER.info("substituteGenericPagedModel: operation '{}': replacing return type '{}' with {}<{}>",
codegenOperation.operationId, oldType, pagedModelClassName, itemType);
}
}

Expand Down Expand Up @@ -1202,7 +1207,22 @@ public void preprocessOpenAPI(OpenAPI openAPI) {
if (SPRING_BOOT.equals(library) && substituteGenericPagedModel) {
pagedModelRegistry = PagedModelScanUtils.scanPagedModels(openAPI);
if (!pagedModelRegistry.isEmpty()) {
importMapping.putIfAbsent("PagedModel", "org.springframework.data.web.PagedModel");
boolean customMapping = importMapping.containsKey("PagedModel");
importMapping.putIfAbsent("PagedModel", configPackage + ".PagedModel");
if (!customMapping) {
// No custom class provided — generate the simple PagedModel into the config package.
supportingFiles.add(new SupportingFile("pagedModel.mustache",
(sourceFolder + File.separator + configPackage).replace(".", File.separator), "PagedModel.kt"));
}
// Derive the actual simple class name from the FQN in importMapping so that a
// custom mapping (e.g. "PagedModel" → "com.example.MyPagedModel") is respected.
// The simple name of the FQN becomes the token used in generated code, and is
// registered in importMapping so that template import resolution works.
String fqn = importMapping.get("PagedModel");
pagedModelClassName = fqn.substring(fqn.lastIndexOf('.') + 1);
if (!pagedModelClassName.equals("PagedModel")) {
importMapping.put(pagedModelClassName, fqn);
}
LOGGER.info("substituteGenericPagedModel: detected {} paged-model schema(s): {}",
pagedModelRegistry.size(), pagedModelRegistry.keySet());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@ public enum RequestMappingMode {
private Map<String, SpringPageableScanUtils.PageableConstraintsData> pageableConstraintsRegistry = new HashMap<>();
// Map from schema name to detected paged-model info (populated when substituteGenericPagedModel=true)
private Map<String, PagedModelScanUtils.DetectedPagedModel> pagedModelRegistry = new HashMap<>();
// Simple class name of the PagedModel substitute (derived from importMapping; defaults to "PagedModel")
private String pagedModelClassName = "PagedModel";

public SpringCodegen() {
super();
Expand Down Expand Up @@ -374,9 +376,9 @@ public SpringCodegen() {
+ "Requires useBeanValidation=true and library=spring-boot.",
generatePageableConstraintValidation));
cliOptions.add(CliOption.newBoolean(SUBSTITUTE_GENERIC_PAGED_MODEL,
"Detect schemas that represent paginated responses (an object with a 'content' array property and a "
"Detect schemas that represent paginated responses (an object with a 'content' array property and a 'page' "
+ "pagination-metadata property) and replace their generated references with "
+ "org.springframework.data.web.PagedModel<T>. The detected page schemas and the pagination metadata "
+ "PagedModel<T> generated into config package (default 'org.openapitools.configuration'). The detected page schemas and the pagination metadata "
+ "schema are suppressed from code generation. Only applies when library=spring-boot.",
substituteGenericPagedModel));

Expand Down Expand Up @@ -870,7 +872,22 @@ public void preprocessOpenAPI(OpenAPI openAPI) {
if (SPRING_BOOT.equals(library) && substituteGenericPagedModel) {
pagedModelRegistry = PagedModelScanUtils.scanPagedModels(openAPI);
if (!pagedModelRegistry.isEmpty()) {
importMapping.putIfAbsent("PagedModel", "org.springframework.data.web.PagedModel");
boolean customMapping = importMapping.containsKey("PagedModel");
importMapping.putIfAbsent("PagedModel", configPackage + ".PagedModel");
if (!customMapping) {
// No custom class provided — generate the simple PagedModel into the config package.
supportingFiles.add(new SupportingFile("pagedModel.mustache",
(sourceFolder + File.separator + configPackage).replace(".", java.io.File.separator), "PagedModel.java"));
}
// Derive the actual simple class name from the FQN in importMapping so that a
// custom mapping (e.g. "PagedModel" → "com.example.MyPagedModel") is respected.
// The simple name of the FQN becomes the token used in generated code, and is
// registered in importMapping so that template import resolution works.
String fqn = importMapping.get("PagedModel");
pagedModelClassName = fqn.substring(fqn.lastIndexOf('.') + 1);
if (!pagedModelClassName.equals("PagedModel")) {
importMapping.put(pagedModelClassName, fqn);
}
LOGGER.info("substituteGenericPagedModel: detected {} paged-model schema(s): {}",
pagedModelRegistry.size(), pagedModelRegistry.keySet());
}
Expand Down Expand Up @@ -1352,21 +1369,24 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation
pagedModelRegistry.get(codegenOperation.returnBaseType);
if (detected != null) {
String oldType = codegenOperation.returnType;
String newBaseType = "PagedModel<" + detected.itemSchemaName + ">";
// Run through toModelName so that schemaMappings (e.g. User → com.example.MyUser)
// are honored: the mapped name is used both in the type arg and for import resolution.
String itemType = toModelName(detected.itemSchemaName);
String newBaseType = pagedModelClassName + "<" + itemType + ">";
codegenOperation.returnType = newBaseType;
codegenOperation.returnBaseType = "PagedModel";
codegenOperation.returnBaseType = pagedModelClassName;
// Clear any container flag — PagedModel is not itself a List/array
codegenOperation.returnContainer = null;
// Add item type import (needed for PagedModel<User> in method signature)
codegenOperation.imports.add(detected.itemSchemaName);
codegenOperation.imports.add("PagedModel");
// Add item type import (needed for PagedModel<T> in method signature)
codegenOperation.imports.add(itemType);
codegenOperation.imports.add(pagedModelClassName);
// Remove paged schema import when no annotations are generated —
// the class is suppressed and not referenced anywhere
if (getAnnotationLibrary() == AnnotationLibrary.NONE) {
codegenOperation.imports.remove(detected.schemaName);
}
LOGGER.info("substituteGenericPagedModel: operation '{}': replacing return type '{}' with PagedModel<{}>",
codegenOperation.operationId, oldType, detected.itemSchemaName);
LOGGER.info("substituteGenericPagedModel: operation '{}': replacing return type '{}' with {}<{}>",
codegenOperation.operationId, oldType, pagedModelClassName, itemType);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package {{configPackage}};

import java.util.List;

/**
* Simple generic paged response wrapper generated by openapi-generator.
*
* <p>Holds a page of content items and pagination metadata. Jackson deserializes this from the
* standard Spring {@code Page} JSON shape:
* <pre>
* {
* "content": [...],
* "page": { "size": 20, "totalElements": 100, "totalPages": 5, "number": 0 }
* }
* </pre>
*
* <p>To use your own class instead, set {@code importMappings.PagedModel} in the generator config.
*/
public class PagedModel<T> {

private List<T> content;
private PageMetadata page;

public PagedModel() {}

public PagedModel(List<T> content, PageMetadata page) {
this.content = content;
this.page = page;
}

public List<T> getContent() {
return content;
}

public void setContent(List<T> content) {
this.content = content;
}

public PageMetadata getPage() {
return page;
}

public void setPage(PageMetadata page) {
this.page = page;
}

public static class PageMetadata {

private long size;
private long totalElements;
private long totalPages;
private long number;

public PageMetadata() {}

public PageMetadata(long size, long totalElements, long totalPages, long number) {
this.size = size;
this.totalElements = totalElements;
this.totalPages = totalPages;
this.number = number;
}

public long getSize() {
return size;
}

public void setSize(long size) {
this.size = size;
}

public long getTotalElements() {
return totalElements;
}

public void setTotalElements(long totalElements) {
this.totalElements = totalElements;
}

public long getTotalPages() {
return totalPages;
}

public void setTotalPages(long totalPages) {
this.totalPages = totalPages;
}

public long getNumber() {
return number;
}

public void setNumber(long number) {
this.number = number;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package {{configPackage}}

/**
* Simple generic paged response wrapper generated by openapi-generator.
*
* Holds a page of content items and pagination metadata. Jackson deserializes this from the
* standard Spring `Page` JSON shape:
* ```json
* {
* "content": [...],
* "page": { "size": 20, "totalElements": 100, "totalPages": 5, "number": 0 }
* }
* ```
*
* To use your own class instead, set `importMappings.PagedModel` in the generator config.
*/
data class PagedModel<T>(
val content: List<T>,
val page: PageMetadata,
) {
data class PageMetadata(
val size: Long,
val totalElements: Long,
val totalPages: Long,
val number: Long,
)
}
Loading
Loading