diff --git a/pom.xml b/pom.xml
index 11339c18a..19bd4b57c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,7 +11,7 @@
org.springframework.boot
spring-boot-starter-parent
- 3.2.4
+ 3.3.0
diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocPageConfiguration.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocPageConfiguration.java
new file mode 100644
index 000000000..6df0b467a
--- /dev/null
+++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocPageConfiguration.java
@@ -0,0 +1,96 @@
+/*
+ *
+ * *
+ * * *
+ * * * * Copyright 2019-2024 the original author or authors.
+ * * * *
+ * * * * Licensed under the Apache License, Version 2.0 (the "License");
+ * * * * you may not use this file except in compliance with the License.
+ * * * * You may obtain a copy of the License at
+ * * * *
+ * * * * https://www.apache.org/licenses/LICENSE-2.0
+ * * * *
+ * * * * Unless required by applicable law or agreed to in writing, software
+ * * * * distributed under the License is distributed on an "AS IS" BASIS,
+ * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * * * * See the License for the specific language governing permissions and
+ * * * * limitations under the License.
+ * * *
+ * *
+ *
+ */
+
+package org.springdoc.core.configuration;
+
+import org.springdoc.core.converters.PageOpenAPIConverter;
+import org.springdoc.core.converters.SortOpenAPIConverter;
+import org.springdoc.core.converters.models.SortObject;
+import org.springdoc.core.customizers.DataRestDelegatingMethodParameterCustomizer;
+import org.springdoc.core.customizers.DelegatingMethodParameterCustomizer;
+import org.springdoc.core.providers.ObjectMapperProvider;
+import org.springdoc.core.providers.RepositoryRestConfigurationProvider;
+import org.springdoc.core.providers.SpringDataWebPropertiesProvider;
+import org.springframework.boot.autoconfigure.condition.*;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.web.PagedModel;
+import org.springframework.data.web.config.EnableSpringDataWebSupport;
+import org.springframework.data.web.config.SpringDataWebSettings;
+
+import java.util.Optional;
+
+import static org.springdoc.core.utils.Constants.SPRINGDOC_ENABLED;
+import static org.springdoc.core.utils.Constants.SPRINGDOC_SORT_CONVERTER_ENABLED;
+import static org.springdoc.core.utils.SpringDocUtils.getConfig;
+
+/**
+ * The type Spring doc page configuration.
+ *
+ * @author Claudio Nave
+ */
+@Lazy(false)
+@Configuration(proxyBeanMethods = false)
+@ConditionalOnProperty(name = SPRINGDOC_ENABLED, matchIfMissing = true)
+@ConditionalOnClass({ Page.class, PagedModel.class })
+@ConditionalOnWebApplication
+@ConditionalOnBean(SpringDocConfiguration.class)
+public class SpringDocPageConfiguration {
+
+ /**
+ * Page open api converter.
+ * @param objectMapperProvider the object mapper provider
+ * @return the page open api converter
+ */
+ @Bean
+ @ConditionalOnMissingBean
+ @ConditionalOnBean(SpringDataWebSettings.class)
+ @Lazy(false)
+ PageOpenAPIConverter pageOpenAPIConverter(SpringDataWebSettings settings,
+ ObjectMapperProvider objectMapperProvider) {
+ return new PageOpenAPIConverter(
+ settings.pageSerializationMode() == EnableSpringDataWebSupport.PageSerializationMode.VIA_DTO,
+ objectMapperProvider);
+ }
+
+ /**
+ * Delegating method parameter customizer delegating method parameter customizer.
+ * @param optionalSpringDataWebPropertiesProvider the optional spring data web
+ * properties
+ * @param optionalRepositoryRestConfiguration the optional repository rest
+ * configuration
+ * @return the delegating method parameter customizer
+ */
+ @Bean
+ @ConditionalOnMissingBean
+ @Lazy(false)
+ DelegatingMethodParameterCustomizer delegatingMethodParameterCustomizer(
+ Optional optionalSpringDataWebPropertiesProvider,
+ Optional optionalRepositoryRestConfiguration) {
+ return new DataRestDelegatingMethodParameterCustomizer(optionalSpringDataWebPropertiesProvider,
+ optionalRepositoryRestConfiguration);
+ }
+
+}
\ No newline at end of file
diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/converters/PageOpenAPIConverter.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/converters/PageOpenAPIConverter.java
new file mode 100644
index 000000000..3fcada041
--- /dev/null
+++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/converters/PageOpenAPIConverter.java
@@ -0,0 +1,108 @@
+/*
+ *
+ * *
+ * * *
+ * * * * Copyright 2019-2024 the original author or authors.
+ * * * *
+ * * * * Licensed under the Apache License, Version 2.0 (the "License");
+ * * * * you may not use this file except in compliance with the License.
+ * * * * You may obtain a copy of the License at
+ * * * *
+ * * * * https://www.apache.org/licenses/LICENSE-2.0
+ * * * *
+ * * * * Unless required by applicable law or agreed to in writing, software
+ * * * * distributed under the License is distributed on an "AS IS" BASIS,
+ * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * * * * See the License for the specific language governing permissions and
+ * * * * limitations under the License.
+ * * *
+ * *
+ *
+ */
+
+package org.springdoc.core.converters;
+
+import com.fasterxml.jackson.databind.JavaType;
+import io.swagger.v3.core.converter.AnnotatedType;
+import io.swagger.v3.core.converter.ModelConverter;
+import io.swagger.v3.core.converter.ModelConverterContext;
+import io.swagger.v3.oas.models.media.Schema;
+import org.apache.commons.lang3.StringUtils;
+import org.springdoc.core.providers.ObjectMapperProvider;
+import org.springframework.core.ResolvableType;
+import org.springframework.data.web.PagedModel;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Iterator;
+
+/**
+ * The Spring Data Page type model converter.
+ *
+ * @author Claudio Nave
+ */
+public class PageOpenAPIConverter implements ModelConverter {
+
+ private static final String PAGE_TO_REPLACE = "org.springframework.data.domain.Page";
+
+ /**
+ * The constant PAGED_MODEL.
+ */
+ private static final AnnotatedType PAGED_MODEL = new AnnotatedType(PagedModel.class).resolveAsRef(true);
+
+ /**
+ * The Spring doc object mapper.
+ */
+ private final ObjectMapperProvider springDocObjectMapper;
+ /**
+ * Flag to replace Page with PagedModel or not.
+ */
+ private final boolean replacePageWithPagedModel;
+
+ /**
+ * Instantiates a new Page open api converter.
+ * @param replacePageWithPagedModel flag to replace Page with PagedModel or not
+ * @param springDocObjectMapper the spring doc object mapper
+ */
+ public PageOpenAPIConverter(boolean replacePageWithPagedModel, ObjectMapperProvider springDocObjectMapper) {
+ this.replacePageWithPagedModel = replacePageWithPagedModel;
+ this.springDocObjectMapper = springDocObjectMapper;
+ }
+
+ /**
+ * Resolve schema.
+ * @param type the type
+ * @param context the context
+ * @param chain the chain
+ * @return the schema
+ */
+ @Override
+ public Schema resolve(AnnotatedType type, ModelConverterContext context, Iterator chain) {
+ JavaType javaType = springDocObjectMapper.jsonMapper().constructType(type.getType());
+ if (javaType != null) {
+ Class> cls = javaType.getRawClass();
+ if (replacePageWithPagedModel && PAGE_TO_REPLACE.equals(cls.getCanonicalName())) {
+ if (!type.isSchemaProperty())
+ type = resolvePagedModelType(type);
+ else
+ type.name(cls.getSimpleName() + StringUtils.capitalize(type.getParent().getType()));
+ }
+ }
+ return (chain.hasNext()) ? chain.next().resolve(type, context, chain) : null;
+ }
+
+ private AnnotatedType resolvePagedModelType(AnnotatedType type) {
+ Type pageType = type.getType();
+ if (pageType instanceof ParameterizedType) {
+ Type argumentType = ((ParameterizedType) type.getType()).getActualTypeArguments()[0];
+ Type pagedModelType = ResolvableType
+ .forClassWithGenerics(PagedModel.class, ResolvableType.forType(argumentType))
+ .getType();
+ return new AnnotatedType(pagedModelType).resolveAsRef(true);
+ }
+ else {
+ return PAGED_MODEL;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/springdoc-openapi-starter-common/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/springdoc-openapi-starter-common/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
index 12694c003..ad61f1fa5 100644
--- a/springdoc-openapi-starter-common/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
+++ b/springdoc-openapi-starter-common/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -7,6 +7,7 @@ org.springdoc.core.configuration.SpringDocFunctionCatalogConfiguration
org.springdoc.core.configuration.SpringDocHateoasConfiguration
org.springdoc.core.configuration.SpringDocPageableConfiguration
org.springdoc.core.configuration.SpringDocSortConfiguration
+org.springdoc.core.configuration.SpringDocPageConfiguration
org.springdoc.core.configuration.SpringDocSpecPropertiesConfiguration
org.springdoc.core.configuration.SpringDocDataRestConfiguration
org.springdoc.core.configuration.SpringDocKotlinConfiguration
diff --git a/springdoc-openapi-tests/springdoc-openapi-actuator-webflux-tests/src/test/resources/results/app146-1.json b/springdoc-openapi-tests/springdoc-openapi-actuator-webflux-tests/src/test/resources/results/app146-1.json
index 4d21f4405..dc3884dd2 100644
--- a/springdoc-openapi-tests/springdoc-openapi-actuator-webflux-tests/src/test/resources/results/app146-1.json
+++ b/springdoc-openapi-tests/springdoc-openapi-actuator-webflux-tests/src/test/resources/results/app146-1.json
@@ -217,6 +217,68 @@
}
}
},
+ "/application/sbom": {
+ "get": {
+ "operationId": "sbom",
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object"
+ }
+ },
+ "application/vnd.spring-boot.actuator.v2+json": {
+ "schema": {
+ "type": "object"
+ }
+ },
+ "application/vnd.spring-boot.actuator.v3+json": {
+ "schema": {
+ "type": "object"
+ }
+ }
+ },
+ "description": "OK"
+ }
+ },
+ "summary": "Actuator web endpoint 'sbom'",
+ "tags": [
+ "Actuator"
+ ]
+ }
+ },
+ "/application/sbom/{id}": {
+ "get": {
+ "operationId": "sbom-id",
+ "parameters": [
+ {
+ "in": "path",
+ "name": "id",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "content": {
+ "application/octet-stream": {
+ "schema": {
+ "type": "object"
+ }
+ }
+ },
+ "description": "OK"
+ }
+ },
+ "summary": "Actuator web endpoint 'sbom-id'",
+ "tags": [
+ "Actuator"
+ ]
+ }
+ },
"/application/metrics": {
"get": {
"tags": [
diff --git a/springdoc-openapi-tests/springdoc-openapi-actuator-webflux-tests/src/test/resources/results/app147-1.json b/springdoc-openapi-tests/springdoc-openapi-actuator-webflux-tests/src/test/resources/results/app147-1.json
index cbcf7fd50..2140650d2 100644
--- a/springdoc-openapi-tests/springdoc-openapi-actuator-webflux-tests/src/test/resources/results/app147-1.json
+++ b/springdoc-openapi-tests/springdoc-openapi-actuator-webflux-tests/src/test/resources/results/app147-1.json
@@ -217,6 +217,68 @@
}
}
},
+ "/application/sbom": {
+ "get": {
+ "operationId": "sbom",
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object"
+ }
+ },
+ "application/vnd.spring-boot.actuator.v2+json": {
+ "schema": {
+ "type": "object"
+ }
+ },
+ "application/vnd.spring-boot.actuator.v3+json": {
+ "schema": {
+ "type": "object"
+ }
+ }
+ },
+ "description": "OK"
+ }
+ },
+ "summary": "Actuator web endpoint 'sbom'",
+ "tags": [
+ "Actuator"
+ ]
+ }
+ },
+ "/application/sbom/{id}": {
+ "get": {
+ "operationId": "sbom-id",
+ "parameters": [
+ {
+ "in": "path",
+ "name": "id",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "content": {
+ "application/octet-stream": {
+ "schema": {
+ "type": "object"
+ }
+ }
+ },
+ "description": "OK"
+ }
+ },
+ "summary": "Actuator web endpoint 'sbom-id'",
+ "tags": [
+ "Actuator"
+ ]
+ }
+ },
"/application/metrics": {
"get": {
"tags": [
diff --git a/springdoc-openapi-tests/springdoc-openapi-actuator-webflux-tests/src/test/resources/results/app148-2.json b/springdoc-openapi-tests/springdoc-openapi-actuator-webflux-tests/src/test/resources/results/app148-2.json
index 837455a90..ab8724aca 100644
--- a/springdoc-openapi-tests/springdoc-openapi-actuator-webflux-tests/src/test/resources/results/app148-2.json
+++ b/springdoc-openapi-tests/springdoc-openapi-actuator-webflux-tests/src/test/resources/results/app148-2.json
@@ -217,6 +217,68 @@
}
}
},
+ "/application/sbom": {
+ "get": {
+ "operationId": "sbom",
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object"
+ }
+ },
+ "application/vnd.spring-boot.actuator.v2+json": {
+ "schema": {
+ "type": "object"
+ }
+ },
+ "application/vnd.spring-boot.actuator.v3+json": {
+ "schema": {
+ "type": "object"
+ }
+ }
+ },
+ "description": "OK"
+ }
+ },
+ "summary": "Actuator web endpoint 'sbom'",
+ "tags": [
+ "Actuator"
+ ]
+ }
+ },
+ "/application/sbom/{id}": {
+ "get": {
+ "operationId": "sbom-id",
+ "parameters": [
+ {
+ "in": "path",
+ "name": "id",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "content": {
+ "application/octet-stream": {
+ "schema": {
+ "type": "object"
+ }
+ }
+ },
+ "description": "OK"
+ }
+ },
+ "summary": "Actuator web endpoint 'sbom-id'",
+ "tags": [
+ "Actuator"
+ ]
+ }
+ },
"/application/metrics": {
"get": {
"tags": [
diff --git a/springdoc-openapi-tests/springdoc-openapi-actuator-webflux-tests/src/test/resources/results/app186.json b/springdoc-openapi-tests/springdoc-openapi-actuator-webflux-tests/src/test/resources/results/app186.json
index 6f5111c7a..dc6ace088 100644
--- a/springdoc-openapi-tests/springdoc-openapi-actuator-webflux-tests/src/test/resources/results/app186.json
+++ b/springdoc-openapi-tests/springdoc-openapi-actuator-webflux-tests/src/test/resources/results/app186.json
@@ -217,6 +217,68 @@
}
}
},
+ "/actuator/sbom": {
+ "get": {
+ "operationId": "sbom",
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object"
+ }
+ },
+ "application/vnd.spring-boot.actuator.v2+json": {
+ "schema": {
+ "type": "object"
+ }
+ },
+ "application/vnd.spring-boot.actuator.v3+json": {
+ "schema": {
+ "type": "object"
+ }
+ }
+ },
+ "description": "OK"
+ }
+ },
+ "summary": "Actuator web endpoint 'sbom'",
+ "tags": [
+ "Actuator"
+ ]
+ }
+ },
+ "/actuator/sbom/{id}": {
+ "get": {
+ "operationId": "sbom-id",
+ "parameters": [
+ {
+ "in": "path",
+ "name": "id",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "content": {
+ "application/octet-stream": {
+ "schema": {
+ "type": "object"
+ }
+ }
+ },
+ "description": "OK"
+ }
+ },
+ "summary": "Actuator web endpoint 'sbom-id'",
+ "tags": [
+ "Actuator"
+ ]
+ }
+ },
"/actuator/metrics": {
"get": {
"tags": [
diff --git a/springdoc-openapi-tests/springdoc-openapi-actuator-webmvc-tests/src/test/resources/results/app147-1.json b/springdoc-openapi-tests/springdoc-openapi-actuator-webmvc-tests/src/test/resources/results/app147-1.json
index 601c304ca..5cd279fa0 100644
--- a/springdoc-openapi-tests/springdoc-openapi-actuator-webmvc-tests/src/test/resources/results/app147-1.json
+++ b/springdoc-openapi-tests/springdoc-openapi-actuator-webmvc-tests/src/test/resources/results/app147-1.json
@@ -297,6 +297,68 @@
}
}
},
+ "/application/sbom": {
+ "get": {
+ "operationId": "sbom",
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object"
+ }
+ },
+ "application/vnd.spring-boot.actuator.v2+json": {
+ "schema": {
+ "type": "object"
+ }
+ },
+ "application/vnd.spring-boot.actuator.v3+json": {
+ "schema": {
+ "type": "object"
+ }
+ }
+ },
+ "description": "OK"
+ }
+ },
+ "summary": "Actuator web endpoint 'sbom'",
+ "tags": [
+ "Actuator"
+ ]
+ }
+ },
+ "/application/sbom/{id}": {
+ "get": {
+ "operationId": "sbom-id",
+ "parameters": [
+ {
+ "in": "path",
+ "name": "id",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "content": {
+ "application/octet-stream": {
+ "schema": {
+ "type": "object"
+ }
+ }
+ },
+ "description": "OK"
+ }
+ },
+ "summary": "Actuator web endpoint 'sbom-id'",
+ "tags": [
+ "Actuator"
+ ]
+ }
+ },
"/application/mappings": {
"get": {
"tags": [
diff --git a/springdoc-openapi-tests/springdoc-openapi-actuator-webmvc-tests/src/test/resources/results/app148-2.json b/springdoc-openapi-tests/springdoc-openapi-actuator-webmvc-tests/src/test/resources/results/app148-2.json
index 1d726c0ba..210744fd1 100644
--- a/springdoc-openapi-tests/springdoc-openapi-actuator-webmvc-tests/src/test/resources/results/app148-2.json
+++ b/springdoc-openapi-tests/springdoc-openapi-actuator-webmvc-tests/src/test/resources/results/app148-2.json
@@ -297,6 +297,68 @@
}
}
},
+ "/application/sbom": {
+ "get": {
+ "operationId": "sbom",
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object"
+ }
+ },
+ "application/vnd.spring-boot.actuator.v2+json": {
+ "schema": {
+ "type": "object"
+ }
+ },
+ "application/vnd.spring-boot.actuator.v3+json": {
+ "schema": {
+ "type": "object"
+ }
+ }
+ },
+ "description": "OK"
+ }
+ },
+ "summary": "Actuator web endpoint 'sbom'",
+ "tags": [
+ "Actuator"
+ ]
+ }
+ },
+ "/application/sbom/{id}": {
+ "get": {
+ "operationId": "sbom-id",
+ "parameters": [
+ {
+ "in": "path",
+ "name": "id",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "content": {
+ "application/octet-stream": {
+ "schema": {
+ "type": "object"
+ }
+ }
+ },
+ "description": "OK"
+ }
+ },
+ "summary": "Actuator web endpoint 'sbom-id'",
+ "tags": [
+ "Actuator"
+ ]
+ }
+ },
"/application/mappings": {
"get": {
"tags": [
diff --git a/springdoc-openapi-tests/springdoc-openapi-actuator-webmvc-tests/src/test/resources/results/app186.json b/springdoc-openapi-tests/springdoc-openapi-actuator-webmvc-tests/src/test/resources/results/app186.json
index c136df41f..77e236426 100644
--- a/springdoc-openapi-tests/springdoc-openapi-actuator-webmvc-tests/src/test/resources/results/app186.json
+++ b/springdoc-openapi-tests/springdoc-openapi-actuator-webmvc-tests/src/test/resources/results/app186.json
@@ -297,6 +297,68 @@
}
}
},
+ "/actuator/sbom": {
+ "get": {
+ "operationId": "sbom",
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object"
+ }
+ },
+ "application/vnd.spring-boot.actuator.v2+json": {
+ "schema": {
+ "type": "object"
+ }
+ },
+ "application/vnd.spring-boot.actuator.v3+json": {
+ "schema": {
+ "type": "object"
+ }
+ }
+ },
+ "description": "OK"
+ }
+ },
+ "summary": "Actuator web endpoint 'sbom'",
+ "tags": [
+ "Actuator"
+ ]
+ }
+ },
+ "/actuator/sbom/{id}": {
+ "get": {
+ "operationId": "sbom-id",
+ "parameters": [
+ {
+ "in": "path",
+ "name": "id",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "content": {
+ "application/octet-stream": {
+ "schema": {
+ "type": "object"
+ }
+ }
+ },
+ "description": "OK"
+ }
+ },
+ "summary": "Actuator web endpoint 'sbom-id'",
+ "tags": [
+ "Actuator"
+ ]
+ }
+ },
"/actuator/mappings": {
"get": {
"tags": [
diff --git a/springdoc-openapi-tests/springdoc-openapi-hateoas-tests/src/test/java/test/org/springdoc/api/app10/Dummy.java b/springdoc-openapi-tests/springdoc-openapi-hateoas-tests/src/test/java/test/org/springdoc/api/app10/Dummy.java
new file mode 100644
index 000000000..761794ae2
--- /dev/null
+++ b/springdoc-openapi-tests/springdoc-openapi-hateoas-tests/src/test/java/test/org/springdoc/api/app10/Dummy.java
@@ -0,0 +1,34 @@
+/*
+ *
+ * *
+ * * *
+ * * * * Copyright 2019-2024 the original author or authors.
+ * * * *
+ * * * * Licensed under the Apache License, Version 2.0 (the "License");
+ * * * * you may not use this file except in compliance with the License.
+ * * * * You may obtain a copy of the License at
+ * * * *
+ * * * * https://www.apache.org/licenses/LICENSE-2.0
+ * * * *
+ * * * * Unless required by applicable law or agreed to in writing, software
+ * * * * distributed under the License is distributed on an "AS IS" BASIS,
+ * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * * * * See the License for the specific language governing permissions and
+ * * * * limitations under the License.
+ * * *
+ * *
+ *
+ */
+
+package test.org.springdoc.api.app10;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+@Data
+@AllArgsConstructor
+public class Dummy {
+
+ private T value;
+
+}
diff --git a/springdoc-openapi-tests/springdoc-openapi-hateoas-tests/src/test/java/test/org/springdoc/api/app10/HelloController.java b/springdoc-openapi-tests/springdoc-openapi-hateoas-tests/src/test/java/test/org/springdoc/api/app10/HelloController.java
new file mode 100644
index 000000000..0597d1fd4
--- /dev/null
+++ b/springdoc-openapi-tests/springdoc-openapi-hateoas-tests/src/test/java/test/org/springdoc/api/app10/HelloController.java
@@ -0,0 +1,75 @@
+/*
+ *
+ * *
+ * * *
+ * * * * Copyright 2019-2024 the original author or authors.
+ * * * *
+ * * * * Licensed under the Apache License, Version 2.0 (the "License");
+ * * * * you may not use this file except in compliance with the License.
+ * * * * You may obtain a copy of the License at
+ * * * *
+ * * * * https://www.apache.org/licenses/LICENSE-2.0
+ * * * *
+ * * * * Unless required by applicable law or agreed to in writing, software
+ * * * * distributed under the License is distributed on an "AS IS" BASIS,
+ * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * * * * See the License for the specific language governing permissions and
+ * * * * limitations under the License.
+ * * *
+ * *
+ *
+ */
+
+package test.org.springdoc.api.app10;
+
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.web.PagedModel;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+@SuppressWarnings("rawtypes")
+@RestController
+public class HelloController {
+
+ @GetMapping("/page-simple")
+ public Page pageSimple() {
+ return pageImpl("test");
+ }
+
+ @GetMapping("/paged-model-simple")
+ public PagedModel pagedModelSimple() {
+ return pagedModel("test");
+ }
+
+ @GetMapping("/page-complex")
+ public Page>> pageComplex() {
+ return pageImpl(new Dummy<>(List.of("test")));
+ }
+
+ @GetMapping("/paged-model-complex")
+ public PagedModel>> pagedModelComplex() {
+ return pagedModel(new Dummy<>(List.of("test")));
+ }
+
+ @GetMapping("/page-raw")
+ public Page pageRaw() {
+ return pageSimple();
+ }
+
+ @GetMapping("/paged-model-raw")
+ public PagedModel pagedModelRaw() {
+ return pagedModelSimple();
+ }
+
+ private PagedModel pagedModel(T value) {
+ return new PagedModel<>(pageImpl(value));
+ }
+
+ private Page pageImpl(T value) {
+ return new PageImpl<>(List.of(value));
+ }
+
+}
diff --git a/springdoc-openapi-tests/springdoc-openapi-hateoas-tests/src/test/java/test/org/springdoc/api/app10/SpringDocApp10DirectTest.java b/springdoc-openapi-tests/springdoc-openapi-hateoas-tests/src/test/java/test/org/springdoc/api/app10/SpringDocApp10DirectTest.java
new file mode 100644
index 000000000..6ced6708d
--- /dev/null
+++ b/springdoc-openapi-tests/springdoc-openapi-hateoas-tests/src/test/java/test/org/springdoc/api/app10/SpringDocApp10DirectTest.java
@@ -0,0 +1,53 @@
+/*
+ *
+ * *
+ * * *
+ * * * * Copyright 2019-2024 the original author or authors.
+ * * * *
+ * * * * Licensed under the Apache License, Version 2.0 (the "License");
+ * * * * you may not use this file except in compliance with the License.
+ * * * * You may obtain a copy of the License at
+ * * * *
+ * * * * https://www.apache.org/licenses/LICENSE-2.0
+ * * * *
+ * * * * Unless required by applicable law or agreed to in writing, software
+ * * * * distributed under the License is distributed on an "AS IS" BASIS,
+ * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * * * * See the License for the specific language governing permissions and
+ * * * * limitations under the License.
+ * * *
+ * *
+ *
+ */
+
+package test.org.springdoc.api.app10;
+
+import org.junit.jupiter.api.Test;
+import org.springdoc.core.utils.Constants;
+import org.springframework.boot.SpringBootConfiguration;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.data.web.config.EnableSpringDataWebSupport;
+import test.org.springdoc.api.AbstractSpringDocTest;
+
+import static org.hamcrest.Matchers.is;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+public class SpringDocApp10DirectTest extends AbstractSpringDocTest {
+
+ @Override
+ @Test
+ public void testApp() throws Exception {
+ mockMvc.perform(get(Constants.DEFAULT_API_DOCS_URL))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.openapi", is("3.0.1")))
+ .andExpect(content().json(getContent("results/app10-direct.json"), true));
+ }
+
+ @SpringBootApplication
+ @EnableSpringDataWebSupport(pageSerializationMode = EnableSpringDataWebSupport.PageSerializationMode.DIRECT)
+ public static class SpringDocTestApp {
+
+ }
+
+}
diff --git a/springdoc-openapi-tests/springdoc-openapi-hateoas-tests/src/test/java/test/org/springdoc/api/app10/SpringDocApp10NotSpecifiedTest.java b/springdoc-openapi-tests/springdoc-openapi-hateoas-tests/src/test/java/test/org/springdoc/api/app10/SpringDocApp10NotSpecifiedTest.java
new file mode 100644
index 000000000..c6f6856dd
--- /dev/null
+++ b/springdoc-openapi-tests/springdoc-openapi-hateoas-tests/src/test/java/test/org/springdoc/api/app10/SpringDocApp10NotSpecifiedTest.java
@@ -0,0 +1,52 @@
+/*
+ *
+ * *
+ * * *
+ * * * * Copyright 2019-2024 the original author or authors.
+ * * * *
+ * * * * Licensed under the Apache License, Version 2.0 (the "License");
+ * * * * you may not use this file except in compliance with the License.
+ * * * * You may obtain a copy of the License at
+ * * * *
+ * * * * https://www.apache.org/licenses/LICENSE-2.0
+ * * * *
+ * * * * Unless required by applicable law or agreed to in writing, software
+ * * * * distributed under the License is distributed on an "AS IS" BASIS,
+ * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * * * * See the License for the specific language governing permissions and
+ * * * * limitations under the License.
+ * * *
+ * *
+ *
+ */
+
+package test.org.springdoc.api.app10;
+
+import org.junit.jupiter.api.Test;
+import org.springdoc.core.utils.Constants;
+import org.springframework.boot.SpringBootConfiguration;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.data.web.config.EnableSpringDataWebSupport;
+import test.org.springdoc.api.AbstractSpringDocTest;
+
+import static org.hamcrest.Matchers.is;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+public class SpringDocApp10NotSpecifiedTest extends AbstractSpringDocTest {
+
+ @Override
+ @Test
+ public void testApp() throws Exception {
+ mockMvc.perform(get(Constants.DEFAULT_API_DOCS_URL))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.openapi", is("3.0.1")))
+ .andExpect(content().json(getContent("results/app10-direct.json"), true));
+ }
+
+ @SpringBootApplication
+ public static class SpringDocTestApp {
+
+ }
+
+}
diff --git a/springdoc-openapi-tests/springdoc-openapi-hateoas-tests/src/test/java/test/org/springdoc/api/app10/SpringDocApp10ViaDtoTest.java b/springdoc-openapi-tests/springdoc-openapi-hateoas-tests/src/test/java/test/org/springdoc/api/app10/SpringDocApp10ViaDtoTest.java
new file mode 100644
index 000000000..7d37e9f38
--- /dev/null
+++ b/springdoc-openapi-tests/springdoc-openapi-hateoas-tests/src/test/java/test/org/springdoc/api/app10/SpringDocApp10ViaDtoTest.java
@@ -0,0 +1,54 @@
+/*
+ *
+ * *
+ * * *
+ * * * * Copyright 2019-2024 the original author or authors.
+ * * * *
+ * * * * Licensed under the Apache License, Version 2.0 (the "License");
+ * * * * you may not use this file except in compliance with the License.
+ * * * * You may obtain a copy of the License at
+ * * * *
+ * * * * https://www.apache.org/licenses/LICENSE-2.0
+ * * * *
+ * * * * Unless required by applicable law or agreed to in writing, software
+ * * * * distributed under the License is distributed on an "AS IS" BASIS,
+ * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * * * * See the License for the specific language governing permissions and
+ * * * * limitations under the License.
+ * * *
+ * *
+ *
+ */
+
+package test.org.springdoc.api.app10;
+
+import org.junit.jupiter.api.Test;
+import org.springdoc.core.utils.Constants;
+import org.springframework.boot.SpringBootConfiguration;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.data.web.config.EnableSpringDataWebSupport;
+import test.org.springdoc.api.AbstractSpringDocTest;
+
+import static org.hamcrest.Matchers.is;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+public class SpringDocApp10ViaDtoTest extends AbstractSpringDocTest {
+
+ @Override
+ @Test
+ public void testApp() throws Exception {
+ mockMvc.perform(get(Constants.DEFAULT_API_DOCS_URL))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.openapi", is("3.0.1")))
+ .andExpect(content().json(getContent("results/app10-via_dto.json"), true));
+ }
+
+ @SpringBootApplication
+ @EnableSpringDataWebSupport(pageSerializationMode = EnableSpringDataWebSupport.PageSerializationMode.VIA_DTO)
+ public static class SpringDocTestApp {
+
+ }
+
+}
diff --git a/springdoc-openapi-tests/springdoc-openapi-hateoas-tests/src/test/resources/results/app10-direct.json b/springdoc-openapi-tests/springdoc-openapi-hateoas-tests/src/test/resources/results/app10-direct.json
new file mode 100644
index 000000000..886b96496
--- /dev/null
+++ b/springdoc-openapi-tests/springdoc-openapi-hateoas-tests/src/test/resources/results/app10-direct.json
@@ -0,0 +1,409 @@
+{
+ "openapi": "3.0.1",
+ "info": {
+ "title": "OpenAPI definition",
+ "version": "v0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost",
+ "description": "Generated server url"
+ }
+ ],
+ "paths": {
+ "/paged-model-simple": {
+ "get": {
+ "tags": [
+ "hello-controller"
+ ],
+ "operationId": "pagedModelSimple",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "*/*": {
+ "schema": {
+ "$ref": "#/components/schemas/PagedModelString"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/paged-model-raw": {
+ "get": {
+ "tags": [
+ "hello-controller"
+ ],
+ "operationId": "pagedModelRaw",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "*/*": {
+ "schema": {
+ "$ref": "#/components/schemas/PagedModel"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/paged-model-complex": {
+ "get": {
+ "tags": [
+ "hello-controller"
+ ],
+ "operationId": "pagedModelComplex",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "*/*": {
+ "schema": {
+ "$ref": "#/components/schemas/PagedModelDummyListString"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/page-simple": {
+ "get": {
+ "tags": [
+ "hello-controller"
+ ],
+ "operationId": "pageSimple",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "*/*": {
+ "schema": {
+ "$ref": "#/components/schemas/PageString"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/page-raw": {
+ "get": {
+ "tags": [
+ "hello-controller"
+ ],
+ "operationId": "pageRaw",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "*/*": {
+ "schema": {
+ "$ref": "#/components/schemas/Page"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/page-complex": {
+ "get": {
+ "tags": [
+ "hello-controller"
+ ],
+ "operationId": "pageComplex",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "*/*": {
+ "schema": {
+ "$ref": "#/components/schemas/PageDummyListString"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PageMetadata": {
+ "type": "object",
+ "properties": {
+ "size": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "number": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "totalElements": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "totalPages": {
+ "type": "integer",
+ "format": "int64"
+ }
+ }
+ },
+ "PagedModelString": {
+ "type": "object",
+ "properties": {
+ "content": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "page": {
+ "$ref": "#/components/schemas/PageMetadata"
+ }
+ }
+ },
+ "PagedModel": {
+ "type": "object",
+ "properties": {
+ "content": {
+ "type": "array",
+ "items": {
+ "type": "object"
+ }
+ },
+ "page": {
+ "$ref": "#/components/schemas/PageMetadata"
+ }
+ }
+ },
+ "DummyListString": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "PagedModelDummyListString": {
+ "type": "object",
+ "properties": {
+ "content": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/DummyListString"
+ }
+ },
+ "page": {
+ "$ref": "#/components/schemas/PageMetadata"
+ }
+ }
+ },
+ "PageString": {
+ "type": "object",
+ "properties": {
+ "totalPages": {
+ "type": "integer",
+ "format": "int32"
+ },
+ "totalElements": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "pageable": {
+ "$ref": "#/components/schemas/PageableObject"
+ },
+ "first": {
+ "type": "boolean"
+ },
+ "last": {
+ "type": "boolean"
+ },
+ "size": {
+ "type": "integer",
+ "format": "int32"
+ },
+ "content": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "number": {
+ "type": "integer",
+ "format": "int32"
+ },
+ "sort": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/SortObject"
+ }
+ },
+ "numberOfElements": {
+ "type": "integer",
+ "format": "int32"
+ },
+ "empty": {
+ "type": "boolean"
+ }
+ }
+ },
+ "PageableObject": {
+ "type": "object",
+ "properties": {
+ "paged": {
+ "type": "boolean"
+ },
+ "pageNumber": {
+ "type": "integer",
+ "format": "int32"
+ },
+ "pageSize": {
+ "type": "integer",
+ "format": "int32"
+ },
+ "offset": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "sort": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/SortObject"
+ }
+ },
+ "unpaged": {
+ "type": "boolean"
+ }
+ }
+ },
+ "SortObject": {
+ "type": "object",
+ "properties": {
+ "direction": {
+ "type": "string"
+ },
+ "nullHandling": {
+ "type": "string"
+ },
+ "ascending": {
+ "type": "boolean"
+ },
+ "property": {
+ "type": "string"
+ },
+ "ignoreCase": {
+ "type": "boolean"
+ }
+ }
+ },
+ "Page": {
+ "type": "object",
+ "properties": {
+ "totalPages": {
+ "type": "integer",
+ "format": "int32"
+ },
+ "totalElements": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "pageable": {
+ "$ref": "#/components/schemas/PageableObject"
+ },
+ "first": {
+ "type": "boolean"
+ },
+ "last": {
+ "type": "boolean"
+ },
+ "size": {
+ "type": "integer",
+ "format": "int32"
+ },
+ "content": {
+ "type": "array",
+ "items": {
+ "type": "object"
+ }
+ },
+ "number": {
+ "type": "integer",
+ "format": "int32"
+ },
+ "sort": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/SortObject"
+ }
+ },
+ "numberOfElements": {
+ "type": "integer",
+ "format": "int32"
+ },
+ "empty": {
+ "type": "boolean"
+ }
+ }
+ },
+ "PageDummyListString": {
+ "type": "object",
+ "properties": {
+ "totalPages": {
+ "type": "integer",
+ "format": "int32"
+ },
+ "totalElements": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "pageable": {
+ "$ref": "#/components/schemas/PageableObject"
+ },
+ "first": {
+ "type": "boolean"
+ },
+ "last": {
+ "type": "boolean"
+ },
+ "size": {
+ "type": "integer",
+ "format": "int32"
+ },
+ "content": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/DummyListString"
+ }
+ },
+ "number": {
+ "type": "integer",
+ "format": "int32"
+ },
+ "sort": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/SortObject"
+ }
+ },
+ "numberOfElements": {
+ "type": "integer",
+ "format": "int32"
+ },
+ "empty": {
+ "type": "boolean"
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/springdoc-openapi-tests/springdoc-openapi-hateoas-tests/src/test/resources/results/app10-via_dto.json b/springdoc-openapi-tests/springdoc-openapi-hateoas-tests/src/test/resources/results/app10-via_dto.json
new file mode 100644
index 000000000..32cad61c1
--- /dev/null
+++ b/springdoc-openapi-tests/springdoc-openapi-hateoas-tests/src/test/resources/results/app10-via_dto.json
@@ -0,0 +1,213 @@
+{
+ "openapi": "3.0.1",
+ "info": {
+ "title": "OpenAPI definition",
+ "version": "v0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost",
+ "description": "Generated server url"
+ }
+ ],
+ "paths": {
+ "/paged-model-simple": {
+ "get": {
+ "tags": [
+ "hello-controller"
+ ],
+ "operationId": "pagedModelSimple",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "*/*": {
+ "schema": {
+ "$ref": "#/components/schemas/PagedModelString"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/paged-model-raw": {
+ "get": {
+ "tags": [
+ "hello-controller"
+ ],
+ "operationId": "pagedModelRaw",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "*/*": {
+ "schema": {
+ "$ref": "#/components/schemas/PagedModel"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/paged-model-complex": {
+ "get": {
+ "tags": [
+ "hello-controller"
+ ],
+ "operationId": "pagedModelComplex",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "*/*": {
+ "schema": {
+ "$ref": "#/components/schemas/PagedModelDummyListString"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/page-simple": {
+ "get": {
+ "tags": [
+ "hello-controller"
+ ],
+ "operationId": "pageSimple",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "*/*": {
+ "schema": {
+ "$ref": "#/components/schemas/PagedModelString"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/page-raw": {
+ "get": {
+ "tags": [
+ "hello-controller"
+ ],
+ "operationId": "pageRaw",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "*/*": {
+ "schema": {
+ "$ref": "#/components/schemas/PagedModel"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/page-complex": {
+ "get": {
+ "tags": [
+ "hello-controller"
+ ],
+ "operationId": "pageComplex",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "*/*": {
+ "schema": {
+ "$ref": "#/components/schemas/PagedModelDummyListString"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PageMetadata": {
+ "type": "object",
+ "properties": {
+ "size": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "number": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "totalElements": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "totalPages": {
+ "type": "integer",
+ "format": "int64"
+ }
+ }
+ },
+ "PagedModelString": {
+ "type": "object",
+ "properties": {
+ "content": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "page": {
+ "$ref": "#/components/schemas/PageMetadata"
+ }
+ }
+ },
+ "PagedModel": {
+ "type": "object",
+ "properties": {
+ "content": {
+ "type": "array",
+ "items": {
+ "type": "object"
+ }
+ },
+ "page": {
+ "$ref": "#/components/schemas/PageMetadata"
+ }
+ }
+ },
+ "DummyListString": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "PagedModelDummyListString": {
+ "type": "object",
+ "properties": {
+ "content": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/DummyListString"
+ }
+ },
+ "page": {
+ "$ref": "#/components/schemas/PageMetadata"
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file