diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 89b8f180b5..b3c10c9f5e 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -23,6 +23,7 @@ commons-text = "1.12.0"
guava = "33.3.1-jre"
jmustache = "1.15"
commonmark = "0.23.0"
+spring-boot = "3.3.3"
micronaut = "4.6.5"
micronaut-platform = "4.6.2"
@@ -41,6 +42,7 @@ micronaut-kotlin = "4.4.0"
micronaut-logging = "1.4.0"
micronaut-session = "4.4.0"
micronaut-grpc = "4.7.1"
+micronaut-spring = "5.8.0"
micronaut-docs = "2.0.0"
[libraries]
@@ -73,6 +75,7 @@ micronaut-data = { module = "io.micronaut.data:micronaut-data-bom", version.ref
micronaut-test = { module = "io.micronaut.test:micronaut-test-bom", version.ref = "micronaut-test" }
micronaut-kotlin = { module = "io.micronaut.kotlin:micronaut-kotlin-bom", version.ref = "micronaut-kotlin" }
micronaut-grpc = { module = "io.micronaut.grpc:micronaut-grpc-bom", version.ref = "micronaut-grpc" }
+micronaut-spring = { module = "io.micronaut.spring:micronaut-spring-bom", version.ref = "micronaut-spring" }
micronaut-platform = { module = "io.micronaut.platform:micronaut-platform", version.ref = "micronaut-platform"}
micronaut-gradle-plugin = { module = "io.micronaut.gradle:micronaut-minimal-plugin", version.ref = "micronaut-gradle-plugin"}
@@ -88,6 +91,7 @@ commons-lang3 = { module = "org.apache.commons:commons-lang3", version.ref = "co
commons-text = { module = "org.apache.commons:commons-text", version.ref = "commons-text" }
guava = { module = "com.google.guava:guava", version.ref = "guava" }
commonmark = { module = "org.commonmark:commonmark", version.ref = "commonmark" }
+spring-boot-dependencies = { module = "org.springframework.boot:spring-boot-dependencies", version.ref = "spring-boot" }
openapi-generator = { module = "org.openapitools:openapi-generator", version.ref = "openapi-generator" }
swagger-parser = { module = "io.swagger:swagger-parser", version.ref = "swagger-parser" }
diff --git a/openapi/src/main/java/io/micronaut/openapi/visitor/ElementUtils.java b/openapi/src/main/java/io/micronaut/openapi/visitor/ElementUtils.java
index fe0a6ee47b..a06c1de6b6 100644
--- a/openapi/src/main/java/io/micronaut/openapi/visitor/ElementUtils.java
+++ b/openapi/src/main/java/io/micronaut/openapi/visitor/ElementUtils.java
@@ -27,6 +27,7 @@
import io.micronaut.http.HttpRequest;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.annotation.Header;
+import io.micronaut.http.annotation.RequestAttribute;
import io.micronaut.http.multipart.FileUpload;
import io.micronaut.inject.annotation.AnnotationMetadataHierarchy;
import io.micronaut.inject.ast.ClassElement;
@@ -246,8 +247,10 @@ public static boolean isIgnoredParameter(TypedElement parameter) {
|| parameter.isAnnotationPresent(Hidden.class)
|| parameter.isAnnotationPresent(JsonIgnore.class)
|| parameter.isAnnotationPresent(Header.class) && parameter.getType().isAssignable(Map.class)
+ || parameter.isAnnotationPresent(RequestAttribute.class)
|| parameter.booleanValue(Parameter.class, PROP_HIDDEN).orElse(false)
|| parameter.hasAnnotation("io.micronaut.session.annotation.SessionValue")
+ || parameter.hasAnnotation("org.springframework.web.bind.annotation.RequestAttribute")
|| parameter.hasAnnotation("org.springframework.web.bind.annotation.SessionAttribute")
|| parameter.hasAnnotation("org.springframework.web.bind.annotation.SessionAttributes")
|| parameter.hasAnnotation("jakarta.ws.rs.core.Context")
diff --git a/openapi/src/main/java/io/micronaut/openapi/visitor/SchemaDefinitionUtils.java b/openapi/src/main/java/io/micronaut/openapi/visitor/SchemaDefinitionUtils.java
index 7cab7e3f01..0249446dab 100644
--- a/openapi/src/main/java/io/micronaut/openapi/visitor/SchemaDefinitionUtils.java
+++ b/openapi/src/main/java/io/micronaut/openapi/visitor/SchemaDefinitionUtils.java
@@ -2481,10 +2481,10 @@ private static void processPropertyElements(OpenAPI openAPI, VisitorContext cont
if (publicField instanceof MemberElement memberEl && (memberEl.getDeclaringType().getType().getName().equals(type.getName()) || isGetterOverridden)) {
- ClassElement fieldType = publicField.getGenericType();
if (withJsonView && !allowedByJsonView(publicField, classLvlJsonViewClasses, jsonViewClass, context)) {
continue;
}
+ ClassElement fieldType = publicField.getGenericType();
Schema> propertySchema = resolveSchema(openAPI, publicField, fieldType, context, mediaTypes, jsonViewClass, fieldJavadoc, classJavadoc);
diff --git a/settings.gradle b/settings.gradle
index f28f1f40db..3a38bd9ac9 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -8,6 +8,7 @@ pluginManagement {
plugins {
id 'io.micronaut.build.shared.settings' version '7.2.1'
+ id "dev.aga.gradle.version-catalog-generator" version "1.5.0"
}
enableFeaturePreview 'TYPESAFE_PROJECT_ACCESSORS'
@@ -24,6 +25,7 @@ include 'docs-examples:example-java'
include 'docs-examples:example-kotlin'
include 'test-suite-java-client-generator'
include 'test-suite-java-jaxrs'
+include 'test-suite-java-spring'
include 'test-suite-java-server-generator'
include 'test-suite-kotlin-kapt-client-generator'
include 'test-suite-kotlin-kapt-server-generator'
@@ -35,6 +37,9 @@ dependencyResolutionManagement {
repositories {
mavenCentral()
}
+ versionCatalogs {
+ generator.generate("spring") { from(toml("spring-boot-dependencies")) }
+ }
}
micronautBuild {
@@ -52,4 +57,5 @@ micronautBuild {
importMicronautCatalog("micronaut-session")
importMicronautCatalog("micronaut-jaxrs")
importMicronautCatalog("micronaut-grpc")
+ importMicronautCatalog("micronaut-spring")
}
diff --git a/src/main/docs/guide/spring.adoc b/src/main/docs/guide/spring.adoc
new file mode 100644
index 0000000000..aeb0b82c12
--- /dev/null
+++ b/src/main/docs/guide/spring.adoc
@@ -0,0 +1 @@
+You can use `micronaut-openapi` to build openapi specification for `Spring` / `Spring Boot` applications. In this case, you do not need to change anything in the code. You can continue to use spring, and use micronaut as a replacement for such libraries as `springdoc-openapi`, `springfox`, `swagger` etc.
diff --git a/src/main/docs/guide/spring/springWithGradle.adoc b/src/main/docs/guide/spring/springWithGradle.adoc
new file mode 100644
index 0000000000..82b5bded10
--- /dev/null
+++ b/src/main/docs/guide/spring/springWithGradle.adoc
@@ -0,0 +1,23 @@
+To use micronaut-openapi with spring in gradle add this code to you `build.gradle`:
+
+.build.gradle
+[source,groovy]
+----
+dependencies {
+
+ // add to annotationProcessor and compileOnly blocks next libraries:
+
+ annotationProcessor("io.micronaut:micronaut-inject-java:$micronautCoreVersion")
+ annotationProcessor("io.micronaut.spring:micronaut-spring-annotation:$micronautSpringVersion")
+ annotationProcessor("io.micronaut.spring:micronaut-spring-web-annotation:$micronautSpringVersion")
+ annotationProcessor("io.micronaut.spring:micronaut-spring-boot-annotation:$micronautSpringVersion")
+ annotationProcessor("io.micronaut.openapi:micronaut-openapi:$micronautOpenapiVersion")
+
+ compileOnly("io.micronaut:micronaut-inject-java:$micronautCoreVersion")
+ compileOnly("io.micronaut.openapi:micronaut-openapi-annotations:$micronautOpenapiVersion")
+ compileOnly("io.micronaut.serde:micronaut-serde-api:$micronautSerdeVersion")
+
+}
+----
+
+For kotlin just change block `annotationProcessor` to `kapt` or `ksp`.
diff --git a/src/main/docs/guide/spring/springWithMaven.adoc b/src/main/docs/guide/spring/springWithMaven.adoc
new file mode 100644
index 0000000000..4899a369d7
--- /dev/null
+++ b/src/main/docs/guide/spring/springWithMaven.adoc
@@ -0,0 +1,131 @@
+To use micronaut-openapi with spring in maven add this code to your `pom.xml`
+
+.pom.xml
+[source,xml]
+----
+
+
+ io.micronaut
+ micronaut-inject-java
+ ${micronaut.core.version}
+ provided
+
+
+ io.micronaut.openapi
+ micronaut-openapi-annotations
+ ${micronaut.openapi.version}
+ provided
+
+
+ io.micronaut.serde
+ micronaut-serde-api
+ ${micronaut.serde.version}
+ provided
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ io.micronaut
+ micronaut-inject-java
+ ${micronaut.core.version}
+
+
+ io.micronaut.spring
+ micronaut-spring-annotation
+ ${micronaut.spring.version}
+
+
+ io.micronaut.spring
+ micronaut-spring-web-annotation
+ ${micronaut.spring.version}
+
+
+ io.micronaut.spring
+ micronaut-spring-boot-annotation
+ ${micronaut.spring.version}
+
+
+ io.micronaut.openapi
+ micronaut-openapi
+ ${micronaut.openapi.version}
+
+
+
+
+
+
+----
+
+For kotlin
+.pom.xml
+[source,xml]
+----
+
+
+ io.micronaut
+ micronaut-inject-java
+ ${micronaut.core.version}
+ provided
+
+
+ io.micronaut.openapi
+ micronaut-openapi-annotations
+ ${micronaut.openapi.version}
+ provided
+
+
+ io.micronaut.serde
+ micronaut-serde-api
+ ${micronaut.serde.version}
+ provided
+
+
+
+
+
+ kapt
+
+ kapt
+
+
+
+ ${project.basedir}/src/main/kotlin
+
+
+
+ io.micronaut
+ micronaut-inject-java
+ ${micronaut.core.version}
+
+
+ io.micronaut.spring
+ micronaut-spring-annotation
+ ${micronaut.spring.version}
+
+
+ io.micronaut.spring
+ micronaut-spring-web-annotation
+ ${micronaut.spring.version}
+
+
+ io.micronaut.spring
+ micronaut-spring-boot-annotation
+ ${micronaut.spring.version}
+
+
+ io.micronaut.openapi
+ micronaut-openapi
+ ${micronaut.openapi.version}
+
+
+
+
+
+----
diff --git a/src/main/docs/guide/spring/springWithOpenApiView.adoc b/src/main/docs/guide/spring/springWithOpenApiView.adoc
new file mode 100644
index 0000000000..f3f904a7b1
--- /dev/null
+++ b/src/main/docs/guide/spring/springWithOpenApiView.adoc
@@ -0,0 +1,4 @@
+To use micronaut openapi views (Swagger UI, OpenAPi Explorer, Redoc, RapiDoc) you need to add static resources to Spring configuration like this:
+
+.Example Swagger UI resources routing config
+snippet::io.micronaut.openapi.spring.WebConfig[tags="imports,clazz", project-base="test-suite-java-spring"]
diff --git a/src/main/docs/guide/toc.yml b/src/main/docs/guide/toc.yml
index d405a0be54..2486024903 100644
--- a/src/main/docs/guide/toc.yml
+++ b/src/main/docs/guide/toc.yml
@@ -63,6 +63,10 @@ serverContext:
compileResolution: Compile Time Resolution
serverFilter: HttpServerFilter
urlParameter: URL Parameter
+spring:
+ title: Micronaut OpenAPI with Spring
+ springWithGradle: Spring with Gradle
+ springWithMaven: Spring with Maven
openApiGuides: Guides
repository: Repository
breaks: Breaking Changes
diff --git a/test-suite-java-jaxrs/src/test/java/io/micronaut/open/jaxrs/Application.java b/test-suite-java-jaxrs/src/test/java/io/micronaut/openapi/jaxrs/Application.java
similarity index 91%
rename from test-suite-java-jaxrs/src/test/java/io/micronaut/open/jaxrs/Application.java
rename to test-suite-java-jaxrs/src/test/java/io/micronaut/openapi/jaxrs/Application.java
index f828f687c0..9982a69c2c 100644
--- a/test-suite-java-jaxrs/src/test/java/io/micronaut/open/jaxrs/Application.java
+++ b/test-suite-java-jaxrs/src/test/java/io/micronaut/openapi/jaxrs/Application.java
@@ -1,4 +1,4 @@
-package io.micronaut.open.jaxrs;
+package io.micronaut.openapi.jaxrs;
import io.micronaut.runtime.Micronaut;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
diff --git a/test-suite-java-jaxrs/src/test/java/io/micronaut/open/jaxrs/OpenApiExposedTest.java b/test-suite-java-jaxrs/src/test/java/io/micronaut/openapi/jaxrs/OpenApiExposedTest.java
similarity index 94%
rename from test-suite-java-jaxrs/src/test/java/io/micronaut/open/jaxrs/OpenApiExposedTest.java
rename to test-suite-java-jaxrs/src/test/java/io/micronaut/openapi/jaxrs/OpenApiExposedTest.java
index c840566982..819d29d98b 100644
--- a/test-suite-java-jaxrs/src/test/java/io/micronaut/open/jaxrs/OpenApiExposedTest.java
+++ b/test-suite-java-jaxrs/src/test/java/io/micronaut/openapi/jaxrs/OpenApiExposedTest.java
@@ -1,4 +1,4 @@
-package io.micronaut.open.jaxrs;
+package io.micronaut.openapi.jaxrs;
import io.micronaut.http.client.BlockingHttpClient;
import io.micronaut.http.client.HttpClient;
diff --git a/test-suite-java-jaxrs/src/test/java/io/micronaut/open/jaxrs/OpenApiGeneratedTest.java b/test-suite-java-jaxrs/src/test/java/io/micronaut/openapi/jaxrs/OpenApiGeneratedTest.java
similarity index 92%
rename from test-suite-java-jaxrs/src/test/java/io/micronaut/open/jaxrs/OpenApiGeneratedTest.java
rename to test-suite-java-jaxrs/src/test/java/io/micronaut/openapi/jaxrs/OpenApiGeneratedTest.java
index 13b6b0113f..fb27ee6cc7 100644
--- a/test-suite-java-jaxrs/src/test/java/io/micronaut/open/jaxrs/OpenApiGeneratedTest.java
+++ b/test-suite-java-jaxrs/src/test/java/io/micronaut/openapi/jaxrs/OpenApiGeneratedTest.java
@@ -1,4 +1,4 @@
-package io.micronaut.open.jaxrs;
+package io.micronaut.openapi.jaxrs;
import io.micronaut.core.io.ResourceLoader;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
diff --git a/test-suite-java-jaxrs/src/test/java/io/micronaut/open/jaxrs/TestController.java b/test-suite-java-jaxrs/src/test/java/io/micronaut/openapi/jaxrs/TestController.java
similarity index 90%
rename from test-suite-java-jaxrs/src/test/java/io/micronaut/open/jaxrs/TestController.java
rename to test-suite-java-jaxrs/src/test/java/io/micronaut/openapi/jaxrs/TestController.java
index b50acdfdae..0f507e7ff0 100644
--- a/test-suite-java-jaxrs/src/test/java/io/micronaut/open/jaxrs/TestController.java
+++ b/test-suite-java-jaxrs/src/test/java/io/micronaut/openapi/jaxrs/TestController.java
@@ -1,4 +1,4 @@
-package io.micronaut.open.jaxrs;
+package io.micronaut.openapi.jaxrs;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
diff --git a/test-suite-java-jaxrs/src/test/java/io/micronaut/open/jaxrs/TestControllerTest.java b/test-suite-java-jaxrs/src/test/java/io/micronaut/openapi/jaxrs/TestControllerTest.java
similarity index 97%
rename from test-suite-java-jaxrs/src/test/java/io/micronaut/open/jaxrs/TestControllerTest.java
rename to test-suite-java-jaxrs/src/test/java/io/micronaut/openapi/jaxrs/TestControllerTest.java
index 0380eb7c11..4397984f57 100644
--- a/test-suite-java-jaxrs/src/test/java/io/micronaut/open/jaxrs/TestControllerTest.java
+++ b/test-suite-java-jaxrs/src/test/java/io/micronaut/openapi/jaxrs/TestControllerTest.java
@@ -1,4 +1,4 @@
-package io.micronaut.open.jaxrs;
+package io.micronaut.openapi.jaxrs;
import io.micronaut.http.client.BlockingHttpClient;
import io.micronaut.http.client.HttpClient;
diff --git a/test-suite-java-spring/build.gradle b/test-suite-java-spring/build.gradle
new file mode 100644
index 0000000000..f635c2575d
--- /dev/null
+++ b/test-suite-java-spring/build.gradle
@@ -0,0 +1,52 @@
+plugins {
+ id("io.micronaut.build.internal.openapi-test-java")
+}
+
+sourceSets {
+ test {
+ java {
+ srcDirs += "$buildDir/classes/java"
+ }
+ }
+}
+
+dependencies {
+
+ annotationProcessor(mnSpring.micronaut.spring.annotation)
+ annotationProcessor(mnSpring.micronaut.spring.web.annotation)
+ annotationProcessor(mnSpring.micronaut.spring.boot.annotation)
+ annotationProcessor(mn.micronaut.inject.java)
+ annotationProcessor(projects.micronautOpenapi)
+
+ compileOnly(projects.micronautOpenapiAnnotations)
+ compileOnly(mn.jackson.annotations)
+ compileOnly(mn.micronaut.inject.java)
+ compileOnly(mnSerde.micronaut.serde.api)
+
+ implementation(spring.spring.springBootStarterWeb)
+ implementation(spring.spring.springBootStarterValidation)
+ implementation(spring.spring.springBootStarterDataRest)
+
+ testCompileOnly(projects.micronautOpenapiAnnotations)
+ testCompileOnly(mn.jackson.annotations)
+ testCompileOnly(mn.micronaut.inject.java)
+ testCompileOnly(mnSerde.micronaut.serde.api)
+
+ testImplementation(spring.spring.springBootStarterTest)
+ testImplementation(mnTest.junit.jupiter.api)
+ testImplementation(projects.micronautOpenapiCommon)
+
+ testRuntimeOnly(mnLogging.logback.classic)
+ testRuntimeOnly(mnTest.junit.jupiter.engine)
+}
+
+tasks.withType(JavaCompile).configureEach {
+ options.encoding = "UTF-8"
+ options.incremental = true
+ options.fork = true
+ options.compilerArgs = [
+ '-parameters',
+ '-Xlint:unchecked',
+ '-Xlint:deprecation'
+ ]
+}
diff --git a/test-suite-java-spring/openapi.properties b/test-suite-java-spring/openapi.properties
new file mode 100644
index 0000000000..b0a131d727
--- /dev/null
+++ b/test-suite-java-spring/openapi.properties
@@ -0,0 +1 @@
+micronaut.openapi.views.spec=swagger-ui.enabled=true
diff --git a/test-suite-java-spring/src/main/java/io/micronaut/openapi/spring/Application.java b/test-suite-java-spring/src/main/java/io/micronaut/openapi/spring/Application.java
new file mode 100644
index 0000000000..335485fc29
--- /dev/null
+++ b/test-suite-java-spring/src/main/java/io/micronaut/openapi/spring/Application.java
@@ -0,0 +1,23 @@
+package io.micronaut.openapi.spring;
+
+import io.swagger.v3.oas.annotations.OpenAPIDefinition;
+import io.swagger.v3.oas.annotations.info.Info;
+import org.springframework.boot.Banner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+@OpenAPIDefinition(
+ info = @Info(
+ title = "demo",
+ version = "0.0"
+ )
+)
+public class Application {
+
+ public static void main(String[] args) {
+ var application = new SpringApplication(Application.class);
+ application.setBannerMode(Banner.Mode.OFF);
+ application.run(args);
+ }
+}
diff --git a/test-suite-java-spring/src/main/java/io/micronaut/openapi/spring/WebConfig.java b/test-suite-java-spring/src/main/java/io/micronaut/openapi/spring/WebConfig.java
new file mode 100644
index 0000000000..363c056ec2
--- /dev/null
+++ b/test-suite-java-spring/src/main/java/io/micronaut/openapi/spring/WebConfig.java
@@ -0,0 +1,22 @@
+package io.micronaut.openapi.spring;
+// tag::imports[]
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+// end::imports[]
+// tag::clazz[]
+@Configuration
+@EnableWebMvc
+public class WebConfig implements WebMvcConfigurer {
+
+ @Override
+ public void addResourceHandlers(ResourceHandlerRegistry registry) {
+ registry.addResourceHandler("/swagger-ui/**")
+ .addResourceLocations("classpath:/META-INF/swagger/views/swagger-ui/");
+ registry.addResourceHandler("/swagger/**")
+ .addResourceLocations("classpath:/META-INF/swagger/");
+ }
+}
+//end::clazz[]
diff --git a/test-suite-java-spring/src/main/java/io/micronaut/openapi/spring/api/HelloController.java b/test-suite-java-spring/src/main/java/io/micronaut/openapi/spring/api/HelloController.java
new file mode 100644
index 0000000000..159b90dab7
--- /dev/null
+++ b/test-suite-java-spring/src/main/java/io/micronaut/openapi/spring/api/HelloController.java
@@ -0,0 +1,29 @@
+package io.micronaut.openapi.spring.api;
+
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+import java.util.Locale;
+
+@RestController
+@RequestMapping("/api")
+public class HelloController {
+
+ @GetMapping
+ public ResponseEntity>> endpoint() {
+ return ResponseEntity.ok(new ResponseObject<>());
+ }
+
+ public static class ResponseObject {
+
+ public T body;
+ }
+
+ public static class Dto {
+
+ public Locale locale;
+ }
+}
\ No newline at end of file
diff --git a/test-suite-java-spring/src/main/java/io/micronaut/openapi/spring/api/TestController.java b/test-suite-java-spring/src/main/java/io/micronaut/openapi/spring/api/TestController.java
new file mode 100644
index 0000000000..e3e426e030
--- /dev/null
+++ b/test-suite-java-spring/src/main/java/io/micronaut/openapi/spring/api/TestController.java
@@ -0,0 +1,69 @@
+package io.micronaut.openapi.spring.api;
+
+import io.micronaut.openapi.spring.api.dto.User;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PatchMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.SessionAttribute;
+
+@RestController("/user")
+class TestController {
+
+ /**
+ * {@summary Create post op summary.} Operation post description.
+ *
+ * @param user User request body
+ *
+ * @return created post user
+ */
+ @PostMapping(value = "/create",
+ produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE},
+ consumes = MediaType.APPLICATION_JSON_VALUE
+ )
+ public User createPost(@RequestBody User user) {
+ user.setId(9876L);
+ return user;
+ }
+
+ /**
+ * {@summary Create patch op summary.} Operation patch description.
+ *
+ * @param user User request body
+ */
+ @PatchMapping("/create")
+ @ResponseStatus(code = HttpStatus.ACCEPTED)
+ public void createPatch(@RequestBody(required = false) User user) {
+ }
+
+ @GetMapping(value = "/{userId}", produces = MediaType.TEXT_HTML_VALUE)
+ public String get(
+ @PathVariable String userId,
+ @RequestParam(required = false, defaultValue = "123") Integer age
+ ) {
+ return "Pong userId " + userId;
+ }
+
+ @PatchMapping(value = "/patch")
+ public User patch(
+ @RequestBody User user,
+ @SessionAttribute(name = "mySesAttr", required = false) String sesAttr
+ ) {
+ user.setId(9876L);
+ return user;
+ }
+
+ @GetMapping("/pageable")
+ public Page getSomeDTOs(Pageable pageable) {
+ return null;
+ }
+
+}
diff --git a/test-suite-java-spring/src/main/java/io/micronaut/openapi/spring/api/dto/User.java b/test-suite-java-spring/src/main/java/io/micronaut/openapi/spring/api/dto/User.java
new file mode 100644
index 0000000000..6628c4b534
--- /dev/null
+++ b/test-suite-java-spring/src/main/java/io/micronaut/openapi/spring/api/dto/User.java
@@ -0,0 +1,32 @@
+package io.micronaut.openapi.spring.api.dto;
+
+public class User {
+
+ private long id;
+ private String name;
+ private int age;
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public void setAge(int age) {
+ this.age = age;
+ }
+}
diff --git a/test-suite-java-spring/src/main/resources/application.yml b/test-suite-java-spring/src/main/resources/application.yml
new file mode 100644
index 0000000000..d39d018d0f
--- /dev/null
+++ b/test-suite-java-spring/src/main/resources/application.yml
@@ -0,0 +1,11 @@
+spring:
+ application:
+ name: test-suite-java-spring
+ main:
+ banner-mode: off
+
+logging:
+ pattern:
+ console: '%cyan(%d{HH:mm:ss.SSS}) %gray([%thread]) %highlight(%-5level) %magenta(%logger{36}) - %msg%n'
+ level:
+ root: info
diff --git a/test-suite-java-spring/src/test/java/io/micronaut/openapi/spring/OpenApiExposedTest.java b/test-suite-java-spring/src/test/java/io/micronaut/openapi/spring/OpenApiExposedTest.java
new file mode 100644
index 0000000000..19437341fa
--- /dev/null
+++ b/test-suite-java-spring/src/test/java/io/micronaut/openapi/spring/OpenApiExposedTest.java
@@ -0,0 +1,69 @@
+package io.micronaut.openapi.spring;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.SpringBootTest.UseMainMethod;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.web.client.RestClient;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+@ActiveProfiles("test")
+@SpringBootTest(useMainMethod = UseMainMethod.ALWAYS, classes = {
+ WebConfig.class,
+ TestConfig.class,
+ Application.class,
+}, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
+class OpenApiExposedTest {
+
+ @Autowired
+ RestClient restClient;
+
+ @Test
+ void testOpenApiSpecEndpoint() throws IOException {
+
+ String openApiSpec;
+ try (var is = getClass().getResourceAsStream("/META-INF/swagger/demo-0.0.yml")) {
+ assertNotNull(is);
+ openApiSpec = new String(is.readAllBytes());
+ }
+ var recievedOpenApiSpec = new AtomicReference();
+
+ assertDoesNotThrow(() -> {
+ var result = restClient.get()
+ .uri("/swagger/demo-0.0.yml")
+ .retrieve();
+
+ recievedOpenApiSpec.set(result.body(String.class));
+ });
+
+ assertEquals(openApiSpec, recievedOpenApiSpec.get());
+ }
+
+ @Test
+ void testSwaggerUiEndpoint() throws IOException {
+
+ String openApiSpec;
+ try (var is = getClass().getResourceAsStream("/META-INF/swagger/views/swagger-ui/index.html")) {
+ assertNotNull(is);
+ openApiSpec = new String(is.readAllBytes());
+ }
+ var recievedOpenApiSpec = new AtomicReference();
+
+ assertDoesNotThrow(() -> {
+ var result = restClient.get()
+ .uri("/swagger-ui/index.html")
+ .retrieve();
+
+ recievedOpenApiSpec.set(result.body(String.class));
+ });
+
+ assertEquals(openApiSpec, recievedOpenApiSpec.get());
+ }
+}
diff --git a/test-suite-java-spring/src/test/java/io/micronaut/openapi/spring/TestConfig.java b/test-suite-java-spring/src/test/java/io/micronaut/openapi/spring/TestConfig.java
new file mode 100644
index 0000000000..fc0a12a23d
--- /dev/null
+++ b/test-suite-java-spring/src/test/java/io/micronaut/openapi/spring/TestConfig.java
@@ -0,0 +1,17 @@
+package io.micronaut.openapi.spring;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.client.RestClient;
+
+@Configuration
+public class TestConfig {
+
+ @Bean
+ RestClient restClient(@Value("${server.port:8080}") int port) {
+ return RestClient.builder()
+ .baseUrl("http://localhost:" + port)
+ .build();
+ }
+}
diff --git a/test-suite-java-spring/src/test/java/io/micronaut/openapi/spring/TestControllerTest.java b/test-suite-java-spring/src/test/java/io/micronaut/openapi/spring/TestControllerTest.java
new file mode 100644
index 0000000000..4eb314d3c2
--- /dev/null
+++ b/test-suite-java-spring/src/test/java/io/micronaut/openapi/spring/TestControllerTest.java
@@ -0,0 +1,88 @@
+package io.micronaut.openapi.spring;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import io.micronaut.openapi.OpenApiUtils;
+import io.swagger.v3.oas.models.OpenAPI;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.web.client.RestClient;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@ActiveProfiles("test")
+@SpringBootTest(useMainMethod = SpringBootTest.UseMainMethod.ALWAYS, classes = {
+ WebConfig.class,
+ TestConfig.class,
+ Application.class,
+}, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
+class TestControllerTest {
+
+ @Autowired
+ RestClient restClient;
+
+ @Test
+ void springOpenApiPathTest() throws JsonProcessingException {
+ var result = restClient.get()
+ .uri("/swagger/demo-0.0.yml")
+ .retrieve()
+ .body(String.class);
+
+ var openApi = OpenApiUtils.getYamlMapper().readValue(result, OpenAPI.class);
+ assertNotNull(openApi.getPaths());
+
+ var userSchema = openApi.getComponents().getSchemas().get("User");
+ assertNotNull(userSchema);
+
+ var createPostOp = openApi.getPaths().get("/create").getPost();
+
+ assertEquals("Create post op summary.", createPostOp.getSummary());
+ assertEquals("Create post op summary. Operation post description.", createPostOp.getDescription());
+ assertNotNull(createPostOp.getRequestBody());
+
+ var mediaType = createPostOp.getRequestBody().getContent().get("application/json");
+ assertNotNull(mediaType);
+ assertNotNull(mediaType.getSchema());
+ assertEquals("#/components/schemas/User", mediaType.getSchema().get$ref());
+ assertNotNull(createPostOp.getResponses());
+ assertNotNull(createPostOp.getResponses().get("200"));
+ assertEquals("created post user", createPostOp.getResponses().get("200").getDescription());
+
+ var createPatchOp = openApi.getPaths().get("/create").getPatch();
+
+ assertEquals("Create patch op summary.", createPatchOp.getSummary());
+ assertEquals("Create patch op summary. Operation patch description.", createPatchOp.getDescription());
+
+ mediaType = createPatchOp.getRequestBody().getContent().get("application/json");
+ assertNotNull(mediaType);
+ assertNotNull(mediaType.getSchema());
+ assertEquals("#/components/schemas/User", mediaType.getSchema().get$ref());
+ assertNotNull(createPatchOp.getResponses());
+ assertNotNull(createPatchOp.getResponses().get("202"));
+ assertEquals("createPatch 202 response", createPatchOp.getResponses().get("202").getDescription());
+
+ var userIdOp = openApi.getPaths().get("/{userId}").getGet();
+
+ assertNotNull(userIdOp);
+
+ var params = userIdOp.getParameters();
+ assertNotNull(params);
+ assertEquals(2, params.size());
+
+ assertEquals("userId", params.get(0).getName());
+ assertEquals("path", params.get(0).getIn());
+ assertTrue(params.get(0).getRequired());
+ assertNotNull(params.get(0).getSchema());
+ assertEquals("string", params.get(0).getSchema().getType());
+
+ assertEquals("age", params.get(1).getName());
+ assertEquals("query", params.get(1).getIn());
+ assertNotNull(params.get(1).getSchema());
+ assertEquals("integer", params.get(1).getSchema().getType());
+ assertEquals("int32", params.get(1).getSchema().getFormat());
+ assertTrue(params.get(1).getSchema().getNullable());
+ }
+}
diff --git a/test-suite-java-spring/src/test/resources/logback.xml b/test-suite-java-spring/src/test/resources/logback.xml
new file mode 100644
index 0000000000..2ec10d0fde
--- /dev/null
+++ b/test-suite-java-spring/src/test/resources/logback.xml
@@ -0,0 +1,13 @@
+
+
+
+
+ %cyan(%d{HH:mm:ss.SSS}) %gray([%thread]) %highlight(%-5level) %magenta(%logger{36}) - %msg%n
+
+
+
+
+
+
+