From 78e23b246cd354b5f9fe6150c3648188cafd75d4 Mon Sep 17 00:00:00 2001 From: Harsha Veeravalli Date: Thu, 7 Nov 2019 04:12:51 +0100 Subject: [PATCH] [BAEL-3319] - Documenting a Spring REST API using OpenAPI (#7919) * [BAEL-3211] Generate Integers within a range in Java * [BAEL-3211] Generate Integers within a range in Java * Moved files from java-numbers to java-numbers-2 * [BAEL-3319] - Documenting a Spring REST API using OpenAPI * [BAEL-3319] Added a new module for springdoc-openapi library * Incorporated feedback - indents, GetMapping et al annotations, package as jar, remove README & spring-webflux change, use of map in BookRepository * Used DeleteMapping instead of RequestMapping * Add the spring-boot-springdoc module to root pom.xml * Remove plain spring boot starter from pom.xml --- pom.xml | 7 +- spring-boot-springdoc/pom.xml | 106 ++++++++++++++++++ .../springdoc/SpringdocApplication.java | 13 +++ .../springdoc/controller/BookController.java | 73 ++++++++++++ .../exception/BookNotFoundException.java | 10 ++ .../GlobalControllerExceptionHandler.java | 24 ++++ .../com/baeldung/springdoc/model/Book.java | 41 +++++++ .../springdoc/repository/BookRepository.java | 28 +++++ .../src/main/resources/application.properties | 5 + .../src/main/resources/logback.xml | 16 +++ .../SpringContextIntegrationTest.java | 17 +++ 11 files changed, 338 insertions(+), 2 deletions(-) create mode 100644 spring-boot-springdoc/pom.xml create mode 100644 spring-boot-springdoc/src/main/java/com/baeldung/springdoc/SpringdocApplication.java create mode 100644 spring-boot-springdoc/src/main/java/com/baeldung/springdoc/controller/BookController.java create mode 100644 spring-boot-springdoc/src/main/java/com/baeldung/springdoc/exception/BookNotFoundException.java create mode 100644 spring-boot-springdoc/src/main/java/com/baeldung/springdoc/exception/GlobalControllerExceptionHandler.java create mode 100644 spring-boot-springdoc/src/main/java/com/baeldung/springdoc/model/Book.java create mode 100644 spring-boot-springdoc/src/main/java/com/baeldung/springdoc/repository/BookRepository.java create mode 100644 spring-boot-springdoc/src/main/resources/application.properties create mode 100644 spring-boot-springdoc/src/main/resources/logback.xml create mode 100644 spring-boot-springdoc/src/test/java/com/baeldung/springdoc/SpringContextIntegrationTest.java diff --git a/pom.xml b/pom.xml index 88aa7011d0..163c3bdcc8 100644 --- a/pom.xml +++ b/pom.xml @@ -722,10 +722,11 @@ spring-boot-parent spring-boot-property-exp spring-boot-security + spring-boot-springdoc spring-boot-testing spring-boot-vue spring-caching - spring-boot-libraries + spring-boot-libraries spring-cloud @@ -922,6 +923,7 @@ spring-boot-keycloak spring-boot-mvc spring-boot-property-exp + spring-boot-springdoc spring-boot-vue spring-cloud spring-cloud/spring-cloud-archaius/basic-config @@ -1467,8 +1469,9 @@ spring-boot-parent spring-boot-property-exp spring-boot-security + spring-boot-springdoc spring-boot-vue - spring-caching + spring-caching spring-cloud spring-cloud-bus diff --git a/spring-boot-springdoc/pom.xml b/spring-boot-springdoc/pom.xml new file mode 100644 index 0000000000..a818db6e53 --- /dev/null +++ b/spring-boot-springdoc/pom.xml @@ -0,0 +1,106 @@ + + + 4.0.0 + com.baeldung + spring-boot-springdoc + 0.0.1-SNAPSHOT + spring-boot-springdoc + Project for Springdoc integration + jar + + + parent-boot-2 + com.baeldung + 0.0.1-SNAPSHOT + ../parent-boot-2 + + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + org.springdoc + springdoc-openapi-core + 1.1.45 + + + org.springdoc + springdoc-openapi-ui + 1.1.45 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + integration + + + + org.springframework.boot + spring-boot-maven-plugin + 2.1.8.RELEASE + + + pre-integration-test + + start + + + + post-integration-test + + stop + + + + + + org.springdoc + springdoc-openapi-maven-plugin + 0.2 + + + integration-test + + generate + + + + + http://localhost:8080/api-docs + openapi.json + ${project.build.directory} + + + + + + + + + \ No newline at end of file diff --git a/spring-boot-springdoc/src/main/java/com/baeldung/springdoc/SpringdocApplication.java b/spring-boot-springdoc/src/main/java/com/baeldung/springdoc/SpringdocApplication.java new file mode 100644 index 0000000000..45c7c23621 --- /dev/null +++ b/spring-boot-springdoc/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-springdoc/src/main/java/com/baeldung/springdoc/controller/BookController.java b/spring-boot-springdoc/src/main/java/com/baeldung/springdoc/controller/BookController.java new file mode 100644 index 0000000000..4d7d9e3d85 --- /dev/null +++ b/spring-boot-springdoc/src/main/java/com/baeldung/springdoc/controller/BookController.java @@ -0,0 +1,73 @@ +package com.baeldung.springdoc.controller; + +import java.util.Collection; + +import javax.validation.Valid; +import javax.validation.constraints.NotNull; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.DeleteMapping; +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.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import com.baeldung.springdoc.exception.BookNotFoundException; +import com.baeldung.springdoc.model.Book; +import com.baeldung.springdoc.repository.BookRepository; + +@RestController +@RequestMapping("/api/book") +public class BookController { + + @Autowired + private BookRepository repository; + + @GetMapping("/{id}") + public Book findById(@PathVariable long id) { + return repository.findById(id) + .orElseThrow(() -> new BookNotFoundException()); + } + + @GetMapping("/") + public Collection findBooks() { + return repository.getBooks(); + } + + @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-springdoc/src/main/java/com/baeldung/springdoc/exception/BookNotFoundException.java b/spring-boot-springdoc/src/main/java/com/baeldung/springdoc/exception/BookNotFoundException.java new file mode 100644 index 0000000000..632cb683bc --- /dev/null +++ b/spring-boot-springdoc/src/main/java/com/baeldung/springdoc/exception/BookNotFoundException.java @@ -0,0 +1,10 @@ +package com.baeldung.springdoc.exception; + +@SuppressWarnings("serial") +public class BookNotFoundException extends RuntimeException { + + public BookNotFoundException() { + + } + +} \ No newline at end of file diff --git a/spring-boot-springdoc/src/main/java/com/baeldung/springdoc/exception/GlobalControllerExceptionHandler.java b/spring-boot-springdoc/src/main/java/com/baeldung/springdoc/exception/GlobalControllerExceptionHandler.java new file mode 100644 index 0000000000..c829072236 --- /dev/null +++ b/spring-boot-springdoc/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 handleConnversion(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-springdoc/src/main/java/com/baeldung/springdoc/model/Book.java b/spring-boot-springdoc/src/main/java/com/baeldung/springdoc/model/Book.java new file mode 100644 index 0000000000..8f678a7ec2 --- /dev/null +++ b/spring-boot-springdoc/src/main/java/com/baeldung/springdoc/model/Book.java @@ -0,0 +1,41 @@ +package com.baeldung.springdoc.model; + +import javax.validation.constraints.NotBlank; +import javax.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-springdoc/src/main/java/com/baeldung/springdoc/repository/BookRepository.java b/spring-boot-springdoc/src/main/java/com/baeldung/springdoc/repository/BookRepository.java new file mode 100644 index 0000000000..4040ba28c2 --- /dev/null +++ b/spring-boot-springdoc/src/main/java/com/baeldung/springdoc/repository/BookRepository.java @@ -0,0 +1,28 @@ +package com.baeldung.springdoc.repository; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import org.springframework.stereotype.Repository; + +import com.baeldung.springdoc.model.Book; + +@Repository +public class BookRepository { + + private 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(); + } +} diff --git a/spring-boot-springdoc/src/main/resources/application.properties b/spring-boot-springdoc/src/main/resources/application.properties new file mode 100644 index 0000000000..f03e90feb6 --- /dev/null +++ b/spring-boot-springdoc/src/main/resources/application.properties @@ -0,0 +1,5 @@ +# custom path for swagger-ui +springdoc.swagger-ui.path=/swagger-ui-custom.html + +# custom path for api docs +springdoc.api-docs.path=/api-docs diff --git a/spring-boot-springdoc/src/main/resources/logback.xml b/spring-boot-springdoc/src/main/resources/logback.xml new file mode 100644 index 0000000000..6a07b178e9 --- /dev/null +++ b/spring-boot-springdoc/src/main/resources/logback.xml @@ -0,0 +1,16 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + \ No newline at end of file diff --git a/spring-boot-springdoc/src/test/java/com/baeldung/springdoc/SpringContextIntegrationTest.java b/spring-boot-springdoc/src/test/java/com/baeldung/springdoc/SpringContextIntegrationTest.java new file mode 100644 index 0000000000..58c12dc3bc --- /dev/null +++ b/spring-boot-springdoc/src/test/java/com/baeldung/springdoc/SpringContextIntegrationTest.java @@ -0,0 +1,17 @@ +package com.baeldung.springdoc; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringContextIntegrationTest { + + @Test + public void whenSpringContextIsBootstrapped_thenNoExceptions() { + + } + +}