Skip to content
26 changes: 24 additions & 2 deletions CI/circle_parallel.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,34 @@ NODE_INDEX=${CIRCLE_NODE_INDEX:-0}

set -e

# Retry a command up to $1 times, with exponential backoff starting at $2 seconds.
retry() {
local attempts=$1; shift
local delay=$1; shift
local count=0
until "$@"; do
count=$((count + 1))
if [ "$count" -ge "$attempts" ]; then
echo "Command failed after $attempts attempts: $*" >&2
return 1
fi
echo "Attempt $count failed. Retrying in ${delay}s..." >&2
sleep "$delay"
delay=$((delay * 2))
done
}

# Wrapper for apt-get install with automatic retry on transient network errors.
apt_install() {
retry 3 30 sudo apt-get -y install --fix-missing "$@"
}

export NODE_ENV=test

if [ "$NODE_INDEX" = "1" ]; then
echo "Running node $NODE_INDEX ..."

sudo apt-get -y install cpanminus
apt_install cpanminus

echo "Testing perl"
(cd samples/client/petstore/perl && /bin/bash ./test.bash)
Expand All @@ -22,7 +44,7 @@ elif [ "$NODE_INDEX" = "2" ]; then
echo "Running node $NODE_INDEX to test cpp-restsdk"

# install cpprestsdk
sudo apt-get install libcpprest-dev
apt_install libcpprest-dev
wget "https://github.com/aminya/setup-cpp/releases/download/v0.37.0/setup-cpp-x64-linux"
chmod +x ./setup-cpp-x64-linux
sudo ./setup-cpp-x64-linux --compiler llvm --cmake true --ninja true
Expand Down
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 'page' pagination-metadata property) and replace their generated references with 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.| |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>. 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.| |false|
|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 'page' pagination-metadata property) and replace their generated references with 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.| |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>. 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.| |false|
|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 'page' pagination-metadata property) and replace their generated references with 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.| |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>. 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.| |false|
|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 @@ -308,7 +308,7 @@ public KotlinSpringServerCodegen() {
"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>. 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.",
+ "schema are suppressed from code generation.",
substituteGenericPagedModel);
addSwitch(COMPANION_OBJECT, "Whether to generate companion objects in data classes, enabling companion extensions.", companionObject);
supportedLibraries.put(SPRING_BOOT, "Spring-boot Server application.");
Expand Down Expand Up @@ -750,7 +750,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) || library.equals(SPRING_DECLARATIVE_HTTP_INTERFACE_LIBRARY))) {
if (additionalProperties.containsKey(SUBSTITUTE_GENERIC_PAGED_MODEL)) {
this.setSubstituteGenericPagedModel(convertPropertyToBoolean(SUBSTITUTE_GENERIC_PAGED_MODEL));
}
writePropertyBack(SUBSTITUTE_GENERIC_PAGED_MODEL, substituteGenericPagedModel);
Expand Down Expand Up @@ -1211,7 +1211,7 @@ public void preprocessOpenAPI(OpenAPI openAPI) {
}
}

if ((SPRING_BOOT.equals(library) || SPRING_DECLARATIVE_HTTP_INTERFACE_LIBRARY.equals(library)) && substituteGenericPagedModel) {
if (substituteGenericPagedModel) {
pagedModelRegistry = PagedModelScanUtils.scanPagedModels(openAPI);
if (!pagedModelRegistry.isEmpty()) {
boolean customMapping = importMapping.containsKey("PagedModel");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,7 @@ public SpringCodegen() {
"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>. 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.",
+ "schema are suppressed from code generation.",
substituteGenericPagedModel));

}
Expand Down Expand Up @@ -591,9 +591,7 @@ 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);
}
convertPropertyToBooleanAndWriteBack(SUBSTITUTE_GENERIC_PAGED_MODEL, this::setSubstituteGenericPagedModel);

if (SPRING_BOOT.equals(library)) {
convertPropertyToBooleanAndWriteBack(AUTO_X_SPRING_PAGINATED, this::setAutoXSpringPaginated);
Expand Down Expand Up @@ -873,7 +871,7 @@ public void preprocessOpenAPI(OpenAPI openAPI) {
}
}

if ((SPRING_BOOT.equals(library) || SPRING_HTTP_INTERFACE.equals(library)) && substituteGenericPagedModel) {
if (substituteGenericPagedModel) {
pagedModelRegistry = PagedModelScanUtils.scanPagedModels(openAPI);
if (!pagedModelRegistry.isEmpty()) {
boolean customMapping = importMapping.containsKey("PagedModel");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7498,6 +7498,61 @@ private Map<String, Object> springHttpInterfacePagedModelProps() {
return props;
}

// -------------------------------------------------------------------------
// substituteGenericPagedModel — spring-cloud
// -------------------------------------------------------------------------

@Test
public void substituteGenericPagedModel_springCloud_replacesReturnTypeInOperation() throws IOException {
Map<String, File> files = generateFromContract(
"src/test/resources/3_0/spring/petstore-paged-model.yaml", SPRING_CLOUD_LIBRARY,
springCloudPagedModelProps());

JavaFileAssert.assertThat(files.get("UserApi.java"))
.assertMethod("listUsers")
.hasReturnType("ResponseEntity<PagedModel<User>>");
}

@Test
public void substituteGenericPagedModel_springCloud_generatesPagedModelSupportingFile() throws IOException {
Map<String, File> files = generateFromContract(
"src/test/resources/3_0/spring/petstore-paged-model.yaml", SPRING_CLOUD_LIBRARY,
springCloudPagedModelProps());

assertThat(files).containsKey("PagedModel.java");
}

@Test
public void substituteGenericPagedModel_springCloud_doesNotGeneratePagedModelFileWhenCustomMapping() throws IOException {
Map<String, File> files = generateFromContract(
"src/test/resources/3_0/spring/petstore-paged-model.yaml", SPRING_CLOUD_LIBRARY,
springCloudPagedModelProps(),
configurator -> configurator.addImportMapping("PagedModel", "com.example.custom.MyPagedModel"));

assertThat(files).doesNotContainKey("PagedModel.java");
}

@Test
public void substituteGenericPagedModel_springCloud_respectsCustomImportMappingClassName() throws IOException {
Map<String, File> files = generateFromContract(
"src/test/resources/3_0/spring/petstore-paged-model.yaml", SPRING_CLOUD_LIBRARY,
springCloudPagedModelProps(),
configurator -> configurator.addImportMapping("PagedModel", "com.example.custom.MyPagedModel"));

JavaFileAssert.assertThat(files.get("UserApi.java"))
.hasImports("com.example.custom.MyPagedModel")
.assertMethod("listUsers")
.hasReturnType("ResponseEntity<MyPagedModel<User>>");
}

/** Common properties for substituteGenericPagedModel tests using spring-cloud. */
private Map<String, Object> springCloudPagedModelProps() {
Map<String, Object> props = new HashMap<>();
props.put(SpringCodegen.USE_TAGS, "true");
props.put(SpringCodegen.SUBSTITUTE_GENERIC_PAGED_MODEL, "true");
return props;
}


@DataProvider(name = "replaceOneOf")
public Object[][] replaceOneOf() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5833,6 +5833,73 @@ private Map<String, Object> commonDeclarativeHttpInterfacePagedModelProps() {
return props;
}

// -------------------------------------------------------------------------
// substituteGenericPagedModel — spring-cloud
// -------------------------------------------------------------------------

@Test
public void substituteGenericPagedModel_springCloud_replacesReturnTypeInOperation() throws IOException {
Map<String, File> files = generateFromContract(
"src/test/resources/3_0/spring/petstore-paged-model.yaml",
springCloudKotlinPagedModelProps(),
new HashMap<>(),
configurator -> configurator.setLibrary(SPRING_CLOUD_LIBRARY));

File userApi = files.get("UserApi.kt");
assertThat(userApi).isNotNull();
String content = Files.readString(userApi.toPath());
assertThat(content).contains("PagedModel<User>");
}

@Test
public void substituteGenericPagedModel_springCloud_generatesPagedModelSupportingFile() throws IOException {
Map<String, File> files = generateFromContract(
"src/test/resources/3_0/spring/petstore-paged-model.yaml",
springCloudKotlinPagedModelProps(),
new HashMap<>(),
configurator -> configurator.setLibrary(SPRING_CLOUD_LIBRARY));

assertThat(files).containsKey("PagedModel.kt");
}

@Test
public void substituteGenericPagedModel_springCloud_doesNotGeneratePagedModelFileWhenCustomMapping() throws IOException {
Map<String, File> files = generateFromContract(
"src/test/resources/3_0/spring/petstore-paged-model.yaml",
springCloudKotlinPagedModelProps(),
new HashMap<>(),
configurator -> configurator
.setLibrary(SPRING_CLOUD_LIBRARY)
.addImportMapping("PagedModel", "com.example.custom.MyPagedModel"));

assertThat(files).doesNotContainKey("PagedModel.kt");
}

@Test
public void substituteGenericPagedModel_springCloud_respectsCustomImportMappingClassName() throws IOException {
Map<String, File> files = generateFromContract(
"src/test/resources/3_0/spring/petstore-paged-model.yaml",
springCloudKotlinPagedModelProps(),
new HashMap<>(),
configurator -> configurator
.setLibrary(SPRING_CLOUD_LIBRARY)
.addImportMapping("PagedModel", "com.example.custom.MyPagedModel"));

File userApi = files.get("UserApi.kt");
assertThat(userApi).isNotNull();
String content = Files.readString(userApi.toPath());
assertThat(content).contains("MyPagedModel<User>");
assertThat(content).contains("import com.example.custom.MyPagedModel");
}

/** Common properties for substituteGenericPagedModel tests using spring-cloud. */
private Map<String, Object> springCloudKotlinPagedModelProps() {
Map<String, Object> props = new HashMap<>();
props.put(USE_TAGS, "true");
props.put(SUBSTITUTE_GENERIC_PAGED_MODEL, "true");
return props;
}

@Test(description = "oneOf with discriminator generates thin sealed interface with Jackson annotations")
public void testOneOfWithDiscriminatorGeneratesThinInterface() throws IOException {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
Expand Down
Loading