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 + + + + + + +