diff --git a/spring-boot-modules/spring-boot-springdoc-2/README.md b/spring-boot-modules/spring-boot-springdoc-2/README.md new file mode 100644 index 0000000000..80977ae5d5 --- /dev/null +++ b/spring-boot-modules/spring-boot-springdoc-2/README.md @@ -0,0 +1,3 @@ +### Relevant Articles: + +- [Documenting a Spring REST API Using OpenAPI 3.0](https://www.baeldung.com/spring-rest-openapi-documentation) \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-springdoc-2/pom.xml b/spring-boot-modules/spring-boot-springdoc-2/pom.xml new file mode 100644 index 0000000000..5aff9ec4fa --- /dev/null +++ b/spring-boot-modules/spring-boot-springdoc-2/pom.xml @@ -0,0 +1,119 @@ + + + 4.0.0 + spring-boot-springdoc-2 + 0.0.1-SNAPSHOT + spring-boot-springdoc-2 + jar + Project for Springdoc integration + + + com.baeldung + parent-boot-3 + 0.0.1-SNAPSHOT + ../../parent-boot-3 + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-data-jpa + + + com.h2database + h2 + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + ${springdoc.version} + + + org.springdoc + springdoc-openapi-starter-common + ${springdoc.version} + + + org.jetbrains.kotlin + kotlin-stdlib + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + integration + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + pre-integration-test + + start + + + + post-integration-test + + stop + + + + + + org.springdoc + springdoc-openapi-maven-plugin + ${springdoc-openapi-maven-plugin.version} + + + integration-test + + generate + + + + + http://localhost:8080/api-docs + openapi.json + ${project.build.directory} + + + + + + + + + 2.1.0 + 1.4 + + + \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-springdoc-2/src/main/java/com/baeldung/springdoc/SpringdocApplication.java b/spring-boot-modules/spring-boot-springdoc-2/src/main/java/com/baeldung/springdoc/SpringdocApplication.java new file mode 100644 index 0000000000..45c7c23621 --- /dev/null +++ b/spring-boot-modules/spring-boot-springdoc-2/src/main/java/com/baeldung/springdoc/SpringdocApplication.java @@ -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); + } + +} diff --git a/spring-boot-modules/spring-boot-springdoc-2/src/main/java/com/baeldung/springdoc/controller/BookController.java b/spring-boot-modules/spring-boot-springdoc-2/src/main/java/com/baeldung/springdoc/controller/BookController.java new file mode 100644 index 0000000000..790cc0797e --- /dev/null +++ b/spring-boot-modules/spring-boot-springdoc-2/src/main/java/com/baeldung/springdoc/controller/BookController.java @@ -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 findBooks() { + return repository.getBooks(); + } + + @GetMapping("/filter") + public Page 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; + } +} diff --git a/spring-boot-modules/spring-boot-springdoc-2/src/main/java/com/baeldung/springdoc/controller/RegularRestController.java b/spring-boot-modules/spring-boot-springdoc-2/src/main/java/com/baeldung/springdoc/controller/RegularRestController.java new file mode 100644 index 0000000000..c17cebf37b --- /dev/null +++ b/spring-boot-modules/spring-boot-springdoc-2/src/main/java/com/baeldung/springdoc/controller/RegularRestController.java @@ -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(); + } + +} diff --git a/spring-boot-modules/spring-boot-springdoc-2/src/main/java/com/baeldung/springdoc/exception/BookNotFoundException.java b/spring-boot-modules/spring-boot-springdoc-2/src/main/java/com/baeldung/springdoc/exception/BookNotFoundException.java new file mode 100644 index 0000000000..331f663548 --- /dev/null +++ b/spring-boot-modules/spring-boot-springdoc-2/src/main/java/com/baeldung/springdoc/exception/BookNotFoundException.java @@ -0,0 +1,5 @@ +package com.baeldung.springdoc.exception; + +public class BookNotFoundException extends RuntimeException { + +} \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-springdoc-2/src/main/java/com/baeldung/springdoc/exception/GlobalControllerExceptionHandler.java b/spring-boot-modules/spring-boot-springdoc-2/src/main/java/com/baeldung/springdoc/exception/GlobalControllerExceptionHandler.java new file mode 100644 index 0000000000..0a1cbd5d35 --- /dev/null +++ b/spring-boot-modules/spring-boot-springdoc-2/src/main/java/com/baeldung/springdoc/exception/GlobalControllerExceptionHandler.java @@ -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 handleConversion(RuntimeException ex) { + return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(BookNotFoundException.class) + @ResponseStatus(HttpStatus.NOT_FOUND) + public ResponseEntity handleBookNotFound(RuntimeException ex) { + return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND); + } +} diff --git a/spring-boot-modules/spring-boot-springdoc-2/src/main/java/com/baeldung/springdoc/kotlin/Foo.kt b/spring-boot-modules/spring-boot-springdoc-2/src/main/java/com/baeldung/springdoc/kotlin/Foo.kt new file mode 100644 index 0000000000..08af8fddc6 --- /dev/null +++ b/spring-boot-modules/spring-boot-springdoc-2/src/main/java/com/baeldung/springdoc/kotlin/Foo.kt @@ -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 = "" +) \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-springdoc-2/src/main/java/com/baeldung/springdoc/kotlin/FooController.kt b/spring-boot-modules/spring-boot-springdoc-2/src/main/java/com/baeldung/springdoc/kotlin/FooController.kt new file mode 100644 index 0000000000..953cf6d00e --- /dev/null +++ b/spring-boot-modules/spring-boot-springdoc-2/src/main/java/com/baeldung/springdoc/kotlin/FooController.kt @@ -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 = 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 = fooList +} + + diff --git a/spring-boot-modules/spring-boot-springdoc-2/src/main/java/com/baeldung/springdoc/model/Book.java b/spring-boot-modules/spring-boot-springdoc-2/src/main/java/com/baeldung/springdoc/model/Book.java new file mode 100644 index 0000000000..7b378a5183 --- /dev/null +++ b/spring-boot-modules/spring-boot-springdoc-2/src/main/java/com/baeldung/springdoc/model/Book.java @@ -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; + } +} diff --git a/spring-boot-modules/spring-boot-springdoc-2/src/main/java/com/baeldung/springdoc/repository/BookRepository.java b/spring-boot-modules/spring-boot-springdoc-2/src/main/java/com/baeldung/springdoc/repository/BookRepository.java new file mode 100644 index 0000000000..51247910b8 --- /dev/null +++ b/spring-boot-modules/spring-boot-springdoc-2/src/main/java/com/baeldung/springdoc/repository/BookRepository.java @@ -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 books = new HashMap<>(); + + public Optional findById(long id) { + return Optional.ofNullable(books.get(id)); + } + + public void add(Book book) { + books.put(book.getId(), book); + } + + public Collection getBooks() { + return books.values(); + } + + public Page getBooks(Pageable pageable) { + int toSkip = pageable.getPageSize() * pageable.getPageNumber(); + List result = books.values().stream().skip(toSkip).limit(pageable.getPageSize()).toList(); + + return new PageImpl<>(result, pageable, books.size()); + } +} diff --git a/spring-boot-modules/spring-boot-springdoc-2/src/main/resources/application.properties b/spring-boot-modules/spring-boot-springdoc-2/src/main/resources/application.properties new file mode 100644 index 0000000000..a668601a7d --- /dev/null +++ b/spring-boot-modules/spring-boot-springdoc-2/src/main/resources/application.properties @@ -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 ROLE_USER role. If an account is not created, the user will be granted the ROLE_USER role. +springdoc.swagger-ui.operationsSorter=alpha +springdoc.swagger-ui.tagsSorter=alpha \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-springdoc-2/src/main/resources/data.sql b/spring-boot-modules/spring-boot-springdoc-2/src/main/resources/data.sql new file mode 100644 index 0000000000..f80e53b717 --- /dev/null +++ b/spring-boot-modules/spring-boot-springdoc-2/src/main/resources/data.sql @@ -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'); + diff --git a/spring-boot-modules/spring-boot-springdoc-2/src/main/resources/logback.xml b/spring-boot-modules/spring-boot-springdoc-2/src/main/resources/logback.xml new file mode 100644 index 0000000000..73dd672c1a --- /dev/null +++ b/spring-boot-modules/spring-boot-springdoc-2/src/main/resources/logback.xml @@ -0,0 +1,13 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-springdoc-2/src/main/resources/schema.sql b/spring-boot-modules/spring-boot-springdoc-2/src/main/resources/schema.sql new file mode 100644 index 0000000000..e5d33da019 --- /dev/null +++ b/spring-boot-modules/spring-boot-springdoc-2/src/main/resources/schema.sql @@ -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) +); \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-springdoc-2/src/test/java/com/baeldung/springdoc/SpringContextTest.java b/spring-boot-modules/spring-boot-springdoc-2/src/test/java/com/baeldung/springdoc/SpringContextTest.java new file mode 100644 index 0000000000..d7cd6bc30b --- /dev/null +++ b/spring-boot-modules/spring-boot-springdoc-2/src/test/java/com/baeldung/springdoc/SpringContextTest.java @@ -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() { + + } + +} diff --git a/spring-boot-modules/spring-boot-springdoc-2/src/test/resources/logback-test.xml b/spring-boot-modules/spring-boot-springdoc-2/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..ab4a5f3a20 --- /dev/null +++ b/spring-boot-modules/spring-boot-springdoc-2/src/test/resources/logback-test.xml @@ -0,0 +1,12 @@ + + + + + [%d{ISO8601}]-[%thread] %-5level %logger - %msg%n + + + + + + + diff --git a/spring-boot-modules/spring-boot-springdoc/README.md b/spring-boot-modules/spring-boot-springdoc/README.md index 3d0fd19ab1..95e6bf6ead 100644 --- a/spring-boot-modules/spring-boot-springdoc/README.md +++ b/spring-boot-modules/spring-boot-springdoc/README.md @@ -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) diff --git a/spring-boot-modules/spring-boot-springdoc/pom.xml b/spring-boot-modules/spring-boot-springdoc/pom.xml index e21efc35d2..8ed9b7c22b 100644 --- a/spring-boot-modules/spring-boot-springdoc/pom.xml +++ b/spring-boot-modules/spring-boot-springdoc/pom.xml @@ -45,7 +45,7 @@ spring-boot-starter-test test - + org.springdoc springdoc-openapi-ui @@ -61,7 +61,6 @@ springdoc-openapi-security ${springdoc.version} - org.springframework.restdocs spring-restdocs-mockmvc @@ -119,53 +118,6 @@ - - - integration - - - - org.springframework.boot - spring-boot-maven-plugin - ${spring-boot.version} - - - pre-integration-test - - start - - - - post-integration-test - - stop - - - - - - org.springdoc - springdoc-openapi-maven-plugin - ${springdoc-openapi-maven-plugin.version} - - - integration-test - - generate - - - - - http://localhost:8080/api-docs - openapi.json - ${project.build.directory} - - - - - - - 1.7.0 1.5.6