[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
This commit is contained in:
Harsha Veeravalli 2019-11-07 04:12:51 +01:00 committed by KevinGilmore
parent 289c3e4c34
commit 78e23b246c
11 changed files with 338 additions and 2 deletions

View File

@ -722,10 +722,11 @@
<module>spring-boot-parent</module>
<module>spring-boot-property-exp</module>
<module>spring-boot-security</module>
<module>spring-boot-springdoc</module>
<module>spring-boot-testing</module>
<module>spring-boot-vue</module>
<module>spring-caching</module>
<module>spring-boot-libraries</module>
<module>spring-boot-libraries</module>
<module>spring-cloud</module>
@ -922,6 +923,7 @@
<module>spring-boot-keycloak</module>
<module>spring-boot-mvc</module>
<module>spring-boot-property-exp</module>
<module>spring-boot-springdoc</module>
<module>spring-boot-vue</module>
<module>spring-cloud</module>
<module>spring-cloud/spring-cloud-archaius/basic-config</module>
@ -1467,8 +1469,9 @@
<module>spring-boot-parent</module>
<module>spring-boot-property-exp</module>
<module>spring-boot-security</module>
<module>spring-boot-springdoc</module>
<module>spring-boot-vue</module>
<module>spring-caching</module>
<module>spring-caching</module>
<module>spring-cloud</module>
<module>spring-cloud-bus</module>
<!-- <module>spring-cloud-cli</module> --> <!-- Not a maven project -->

View File

@ -0,0 +1,106 @@
<?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>
<groupId>com.baeldung</groupId>
<artifactId>spring-boot-springdoc</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-springdoc</name>
<description>Project for Springdoc integration</description>
<packaging>jar</packaging>
<parent>
<artifactId>parent-boot-2</artifactId>
<groupId>com.baeldung</groupId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../parent-boot-2</relativePath>
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- SpringDoc -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-core</artifactId>
<version>1.1.45</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.1.45</version>
</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>2.1.8.RELEASE</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>0.2</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>
</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,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<Book> 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;
}
}

View File

@ -0,0 +1,10 @@
package com.baeldung.springdoc.exception;
@SuppressWarnings("serial")
public class BookNotFoundException extends RuntimeException {
public BookNotFoundException() {
}
}

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> handleConnversion(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,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;
}
}

View File

@ -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<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();
}
}

View File

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

View File

@ -0,0 +1,16 @@
<?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>
<logger name="org.springframework" level="DEBUG" />
<logger name="org.springframework.transaction" level="WARN" />
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>

View File

@ -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() {
}
}