JAVA-6350: Add comments for SpringDoc v2 compatible with Spring Boot 3 (#13989)

This commit is contained in:
Harry9656 2023-06-18 12:54:06 +02:00 committed by GitHub
parent d4c6226aa5
commit 43fc8b38ea
19 changed files with 484 additions and 50 deletions

View File

@ -0,0 +1,3 @@
### Relevant Articles:
- [Documenting a Spring REST API Using OpenAPI 3.0](https://www.baeldung.com/spring-rest-openapi-documentation)

View File

@ -0,0 +1,119 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-boot-springdoc-2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-springdoc-2</name>
<packaging>jar</packaging>
<description>Project for Springdoc integration</description>
<parent>
<groupId>com.baeldung</groupId>
<artifactId>parent-boot-3</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../../parent-boot-3</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-common</artifactId>
<version>${springdoc.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>integration</id>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<executions>
<execution>
<id>pre-integration-test</id>
<goals>
<goal>start</goal>
</goals>
</execution>
<execution>
<id>post-integration-test</id>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-maven-plugin</artifactId>
<version>${springdoc-openapi-maven-plugin.version}</version>
<executions>
<execution>
<phase>integration-test</phase>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<configuration>
<apiDocsUrl>http://localhost:8080/api-docs</apiDocsUrl>
<outputFileName>openapi.json</outputFileName>
<outputDir>${project.build.directory}</outputDir>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<properties>
<springdoc.version>2.1.0</springdoc.version>
<springdoc-openapi-maven-plugin.version>1.4</springdoc-openapi-maven-plugin.version>
</properties>
</project>

View File

@ -0,0 +1,13 @@
package com.baeldung.springdoc;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringdocApplication {
public static void main(String[] args) {
SpringApplication.run(SpringdocApplication.class, args);
}
}

View File

@ -0,0 +1,83 @@
package com.baeldung.springdoc.controller;
import com.baeldung.springdoc.exception.BookNotFoundException;
import com.baeldung.springdoc.model.Book;
import com.baeldung.springdoc.repository.BookRepository;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import java.util.Collection;
@RestController
@RequestMapping("/api/book")
public class BookController {
private final BookRepository repository;
public BookController(BookRepository repository) {
this.repository = repository;
}
@Operation(summary = "Get a book by its id")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Found the book",
content = {@Content(mediaType = "application/json", schema = @Schema(implementation = Book.class))}),
@ApiResponse(responseCode = "400", description = "Invalid id supplied", content = @Content),
@ApiResponse(responseCode = "404", description = "Book not found", content = @Content)}) // @formatter:on
@GetMapping("/{id}")
public Book findById(@Parameter(description = "id of book to be searched") @PathVariable long id) {
return repository.findById(id)
.orElseThrow(BookNotFoundException::new);
}
@GetMapping("/")
public Collection<Book> findBooks() {
return repository.getBooks();
}
@GetMapping("/filter")
public Page<Book> filterBooks(@ParameterObject Pageable pageable) {
return repository.getBooks(pageable);
}
@PutMapping("/{id}")
@ResponseStatus(HttpStatus.OK)
public Book updateBook(@PathVariable("id") final String id, @RequestBody final Book book) {
return book;
}
@PatchMapping("/{id}")
@ResponseStatus(HttpStatus.OK)
public Book patchBook(@PathVariable("id") final String id, @RequestBody final Book book) {
return book;
}
@PostMapping("/")
@ResponseStatus(HttpStatus.CREATED)
public Book postBook(@NotNull @Valid @RequestBody final Book book) {
return book;
}
@RequestMapping(method = RequestMethod.HEAD, value = "/")
@ResponseStatus(HttpStatus.OK)
public Book headBook() {
return new Book();
}
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.OK)
public long deleteBook(@PathVariable final long id) {
return id;
}
}

View File

@ -0,0 +1,30 @@
package com.baeldung.springdoc.controller;
import io.swagger.v3.oas.annotations.Hidden;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDate;
import java.time.LocalTime;
@Hidden
@RestController
public class RegularRestController {
@Hidden
@GetMapping("/getAuthor")
public String getAuthor() {
return "Umang Budhwar";
}
@GetMapping("/getDate")
public LocalDate getDate() {
return LocalDate.now();
}
@GetMapping("/getTime")
public LocalTime getTime() {
return LocalTime.now();
}
}

View File

@ -0,0 +1,5 @@
package com.baeldung.springdoc.exception;
public class BookNotFoundException extends RuntimeException {
}

View File

@ -0,0 +1,24 @@
package com.baeldung.springdoc.exception;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalControllerExceptionHandler {
@ExceptionHandler(ConversionFailedException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseEntity<String> handleConversion(RuntimeException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(BookNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ResponseEntity<String> handleBookNotFound(RuntimeException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
}
}

View File

@ -0,0 +1,16 @@
package com.baeldung.springdoc.kotlin
import jakarta.persistence.Entity
import jakarta.persistence.Id
import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.Size
@Entity
data class Foo(
@Id
val id: Long = 0,
@NotBlank
@Size(min = 0, max = 50)
val name: String = ""
)

View File

@ -0,0 +1,36 @@
package com.baeldung.springdoc.kotlin
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.media.ArraySchema
import io.swagger.v3.oas.annotations.media.Content
import io.swagger.v3.oas.annotations.media.Schema
import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.responses.ApiResponses
import org.hibernate.internal.util.collections.CollectionHelper.listOf
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/")
class FooController {
val fooList: List<Foo> = listOf(Foo(1, "one"), Foo(2, "two"))
@Operation(summary = "Get all foos")
@ApiResponses(
value = [
ApiResponse(
responseCode = "200", description = "Found Foos", content = [
(Content(
mediaType = "application/json", array = (
ArraySchema(schema = Schema(implementation = Foo::class)))
))]
),
ApiResponse(responseCode = "400", description = "Bad request", content = [Content()]),
ApiResponse(responseCode = "404", description = "Did not find any Foos", content = [Content()])]
)
@GetMapping("/foo")
fun getAllFoos(): List<Foo> = fooList
}

View File

@ -0,0 +1,42 @@
package com.baeldung.springdoc.model;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
public class Book {
private long id;
@NotBlank
@Size(min = 0, max = 20)
private String title;
@NotBlank
@Size(min = 0, max = 30)
private String author;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}

View File

@ -0,0 +1,34 @@
package com.baeldung.springdoc.repository;
import com.baeldung.springdoc.model.Book;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;
import java.util.*;
@Repository
public class BookRepository {
private final Map<Long, Book> books = new HashMap<>();
public Optional<Book> findById(long id) {
return Optional.ofNullable(books.get(id));
}
public void add(Book book) {
books.put(book.getId(), book);
}
public Collection<Book> getBooks() {
return books.values();
}
public Page<Book> getBooks(Pageable pageable) {
int toSkip = pageable.getPageSize() * pageable.getPageNumber();
List<Book> result = books.values().stream().skip(toSkip).limit(pageable.getPageSize()).toList();
return new PageImpl<>(result, pageable, books.size());
}
}

View File

@ -0,0 +1,24 @@
# custom path for swagger-ui
springdoc.swagger-ui.path=/swagger-ui-custom.html
# custom path for api docs
springdoc.api-docs.path=/api-docs
# H2 Related Configurations
spring.datasource.url=jdbc:h2:mem:springdoc
## for com.baeldung.restdocopenapi ##
springdoc.version=@springdoc.version@
spring.jpa.hibernate.ddl-auto=none
## for com.baeldung.jwt ##
jwt.private.key=classpath:app.key
jwt.public.key=classpath:app.pub
api.version=1.0-SNAPSHOT
tos.uri=terms-of-service
api.server.url=https://www.baeldung.com
api.description=The User API is used to create, update, and delete users. Users can be created with or without an associated account. If an account is created, the user will be granted the <strong>ROLE_USER</strong> role. If an account is not created, the user will be granted the <b>ROLE_USER</b> role.
springdoc.swagger-ui.operationsSorter=alpha
springdoc.swagger-ui.tagsSorter=alpha

View File

@ -0,0 +1,4 @@
INSERT INTO Foo(id, title, body) VALUES (1, 'Foo 1', 'Foo body 1');
INSERT INTO Foo(id, title, body) VALUES (2, 'Foo 2', 'Foo body 2');
INSERT INTO Foo(id, title, body) VALUES (3, 'Foo 3', 'Foo body 3');

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</pattern>
</encoder>
</appender>
<root level="WARN">
<appender-ref ref="STDOUT" />
</root>
</configuration>

View File

@ -0,0 +1,8 @@
DROP TABLE IF EXISTS foo;
CREATE TABLE foo (
id INTEGER NOT NULL AUTO_INCREMENT,
title VARCHAR(250) NOT NULL,
body VARCHAR(250),
PRIMARY KEY (id)
);

View File

@ -0,0 +1,17 @@
package com.baeldung.springdoc;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class)
@SpringBootTest
class SpringContextTest {
@Test
void whenSpringContextIsBootstrapped_thenNoExceptions() {
}
}

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="15 seconds" debug="false">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>[%d{ISO8601}]-[%thread] %-5level %logger - %msg%n</pattern>
</encoder>
</appender>
<root level="WARN">
<appender-ref ref="STDOUT" />
</root>
</configuration>

View File

@ -1,6 +1,5 @@
### Relevant Articles:
- [Documenting a Spring REST API Using OpenAPI 3.0](https://www.baeldung.com/spring-rest-openapi-documentation)
- [Spring REST Docs vs OpenAPI](https://www.baeldung.com/spring-rest-docs-vs-openapi)
- [Hiding Endpoints From Swagger Documentation in Spring Boot](https://www.baeldung.com/spring-swagger-hiding-endpoints)
- [Swagger @Api Description Is Deprecated](https://www.baeldung.com/java-swagger-api-description-deprecated)

View File

@ -45,7 +45,7 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- SpringDoc -->
<!-- SpringDoc for Spring Boot Version 1.x and 2.x -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
@ -61,7 +61,6 @@
<artifactId>springdoc-openapi-security</artifactId>
<version>${springdoc.version}</version>
</dependency>
<!-- restdocs -->
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-mockmvc</artifactId>
@ -119,53 +118,6 @@
</resources>
</build>
<profiles>
<profile>
<id>integration</id>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<executions>
<execution>
<id>pre-integration-test</id>
<goals>
<goal>start</goal>
</goals>
</execution>
<execution>
<id>post-integration-test</id>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-maven-plugin</artifactId>
<version>${springdoc-openapi-maven-plugin.version}</version>
<executions>
<execution>
<phase>integration-test</phase>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<configuration>
<apiDocsUrl>http://localhost:8080/api-docs</apiDocsUrl>
<outputFileName>openapi.json</outputFileName>
<outputDir>${project.build.directory}</outputDir>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<properties>
<springdoc.version>1.7.0</springdoc.version>
<asciidoctor-plugin.version>1.5.6</asciidoctor-plugin.version>