-
-
Notifications
You must be signed in to change notification settings - Fork 7.5k
[JAVA-SPRING;KOTLIN-SPRING] bugfix - PagedModel<T> - default to custom DTO; allow override via import mapping and schema mapping; feature - allow for declarative http interfaces #23601
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f7c827a
42bd4e1
5780ef7
2764446
2267cbb
c837967
5222df2
d3900d3
7de158b
ae4f2a5
a1fc411
93bc67a
ecf07b0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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(); | ||
|
|
@@ -296,10 +298,10 @@ 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 " | ||
| + "schema are suppressed from code generation. Only applies when library=spring-boot.", | ||
| + "PagedModel<T>. By default this uses a generated type in the config package (default 'org.openapitools.configuration'), but `importMappings.PagedModel` can override it to a custom/FQCN-mapped type. The detected page schemas and the pagination metadata " | ||
| + "schema are suppressed from code generation. Only applies when library=spring-boot or spring-declarative-http-interface.", | ||
| substituteGenericPagedModel); | ||
| addSwitch(COMPANION_OBJECT, "Whether to generate companion objects in data classes, enabling companion extensions.", companionObject); | ||
| supportedLibraries.put(SPRING_BOOT, "Spring-boot Server application."); | ||
|
|
@@ -741,7 +743,7 @@ public void processOpts() { | |
| this.setGeneratePageableConstraintValidation(convertPropertyToBoolean(GENERATE_PAGEABLE_CONSTRAINT_VALIDATION)); | ||
| } | ||
| writePropertyBack(GENERATE_PAGEABLE_CONSTRAINT_VALIDATION, generatePageableConstraintValidation); | ||
| if (additionalProperties.containsKey(SUBSTITUTE_GENERIC_PAGED_MODEL) && library.equals(SPRING_BOOT)) { | ||
| if (additionalProperties.containsKey(SUBSTITUTE_GENERIC_PAGED_MODEL) && (library.equals(SPRING_BOOT) || library.equals(SPRING_DECLARATIVE_HTTP_INTERFACE_LIBRARY))) { | ||
| this.setSubstituteGenericPagedModel(convertPropertyToBoolean(SUBSTITUTE_GENERIC_PAGED_MODEL)); | ||
| } | ||
| writePropertyBack(SUBSTITUTE_GENERIC_PAGED_MODEL, substituteGenericPagedModel); | ||
|
|
@@ -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); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -1199,10 +1204,25 @@ public void preprocessOpenAPI(OpenAPI openAPI) { | |
| } | ||
| } | ||
|
|
||
| if (SPRING_BOOT.equals(library) && substituteGenericPagedModel) { | ||
| if ((SPRING_BOOT.equals(library) || SPRING_DECLARATIVE_HTTP_INTERFACE_LIBRARY.equals(library)) && substituteGenericPagedModel) { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. change relevant to enabling the functionality in spring declarative http interfaces |
||
| 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.putIfAbsent(pagedModelClassName, fqn); | ||
| } | ||
| LOGGER.info("substituteGenericPagedModel: detected {} paged-model schema(s): {}", | ||
| pagedModelRegistry.size(), pagedModelRegistry.keySet()); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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(); | ||
|
|
@@ -374,10 +376,10 @@ 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 " | ||
| + "schema are suppressed from code generation. Only applies when library=spring-boot.", | ||
| + "PagedModel<T>. By default this uses a generated type in the config package (default 'org.openapitools.configuration'), but `importMappings.PagedModel` can override it to a custom/FQCN-mapped type. The detected page schemas and the pagination metadata " | ||
| + "schema are suppressed from code generation. Only applies when library=spring-boot or spring-http-interface.", | ||
| substituteGenericPagedModel)); | ||
|
|
||
| } | ||
|
|
@@ -588,11 +590,14 @@ public void processOpts() { | |
|
|
||
| convertPropertyToBooleanAndWriteBack(ADDITIONAL_NOT_NULL_ANNOTATIONS, this::setAdditionalNotNullAnnotations); | ||
|
|
||
| if (SPRING_BOOT.equals(library) || SPRING_HTTP_INTERFACE.equals(library)) { | ||
| convertPropertyToBooleanAndWriteBack(SUBSTITUTE_GENERIC_PAGED_MODEL, this::setSubstituteGenericPagedModel); | ||
| } | ||
|
|
||
|
Comment on lines
+593
to
+596
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. change relevant to enabling the functionality in spring declarative http interfaces |
||
| if (SPRING_BOOT.equals(library)) { | ||
| convertPropertyToBooleanAndWriteBack(AUTO_X_SPRING_PAGINATED, this::setAutoXSpringPaginated); | ||
| convertPropertyToBooleanAndWriteBack(GENERATE_SORT_VALIDATION, this::setGenerateSortValidation); | ||
| convertPropertyToBooleanAndWriteBack(GENERATE_PAGEABLE_CONSTRAINT_VALIDATION, this::setGeneratePageableConstraintValidation); | ||
| convertPropertyToBooleanAndWriteBack(SUBSTITUTE_GENERIC_PAGED_MODEL, this::setSubstituteGenericPagedModel); | ||
| } | ||
|
|
||
| // override parent one | ||
|
|
@@ -867,10 +872,25 @@ public void preprocessOpenAPI(OpenAPI openAPI) { | |
| } | ||
| } | ||
|
|
||
| if (SPRING_BOOT.equals(library) && substituteGenericPagedModel) { | ||
| if ((SPRING_BOOT.equals(library) || SPRING_HTTP_INTERFACE.equals(library)) && substituteGenericPagedModel) { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. change relevant to enabling the functionality in spring declarative http interfaces |
||
| 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()); | ||
| } | ||
|
|
@@ -1352,21 +1372,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); | ||
| } | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
change relevant to enabling the functionality in spring declarative http interfaces