BAEL-3493: Spring REST Docs vs OpenAPI (#9295)
This commit is contained in:
parent
b707c20297
commit
708e85ce38
@ -37,7 +37,7 @@
|
|||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.hibernate</groupId>
|
<groupId>org.hibernate</groupId>
|
||||||
<artifactId>hibernate-core</artifactId>
|
<artifactId>hibernate-core</artifactId>
|
||||||
<version>${hibernate.version}</version>
|
<version>${hibernate.version}</version>
|
||||||
@ -53,7 +53,19 @@
|
|||||||
<groupId>org.springdoc</groupId>
|
<groupId>org.springdoc</groupId>
|
||||||
<artifactId>springdoc-openapi-data-rest</artifactId>
|
<artifactId>springdoc-openapi-data-rest</artifactId>
|
||||||
<version>${springdoc.version}</version>
|
<version>${springdoc.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- restdocs -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.restdocs</groupId>
|
||||||
|
<artifactId>spring-restdocs-mockmvc</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.restdocs</groupId>
|
||||||
|
<artifactId>spring-restdocs-restassured</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
@ -62,13 +74,49 @@
|
|||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.asciidoctor</groupId>
|
||||||
|
<artifactId>asciidoctor-maven-plugin</artifactId>
|
||||||
|
<version>${asciidoctor-plugin.version}</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>generate-docs</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>process-asciidoc</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<backend>html</backend>
|
||||||
|
<doctype>book</doctype>
|
||||||
|
<attributes>
|
||||||
|
<snippets>${snippetsDirectory}</snippets>
|
||||||
|
</attributes>
|
||||||
|
<sourceDirectory>src/main/resources/asciidocs</sourceDirectory>
|
||||||
|
<outputDirectory>target/generated-docs</outputDirectory>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
|
<resources>
|
||||||
|
<resource>
|
||||||
|
<filtering>true</filtering>
|
||||||
|
<directory>src/main/resources</directory>
|
||||||
|
<includes>
|
||||||
|
<include>application.properties</include>
|
||||||
|
<include>data.sql</include>
|
||||||
|
<include>schema.sql</include>
|
||||||
|
</includes>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<java.version>1.8</java.version>
|
<java.version>1.8</java.version>
|
||||||
<hibernate.version>5.2.10.Final</hibernate.version>
|
<hibernate.version>5.2.10.Final</hibernate.version>
|
||||||
<springdoc.version>1.2.32</springdoc.version>
|
<springdoc.version>1.2.32</springdoc.version>
|
||||||
|
<asciidoctor-plugin.version>1.5.6</asciidoctor-plugin.version>
|
||||||
|
<snippetsDirectory>${project.build.directory}/generated-snippets</snippetsDirectory>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<profiles>
|
<profiles>
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
package com.baeldung.restdocopenapi;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.models.OpenAPI;
|
||||||
|
import io.swagger.v3.oas.models.info.Info;
|
||||||
|
import io.swagger.v3.oas.models.info.License;
|
||||||
|
|
||||||
|
@SpringBootApplication()
|
||||||
|
public class Application {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(Application.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public OpenAPI customOpenAPI(@Value("${springdoc.version}") String appVersion) {
|
||||||
|
return new OpenAPI().info(new Info().title("Foobar API")
|
||||||
|
.version(appVersion)
|
||||||
|
.description("This is a sample Foobar server created using springdocs - a library for OpenAPI 3 with spring boot.")
|
||||||
|
.termsOfService("http://swagger.io/terms/")
|
||||||
|
.license(new License().name("Apache 2.0")
|
||||||
|
.url("http://springdoc.org")));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
package com.baeldung.restdocopenapi;
|
||||||
|
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.GenerationType;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
public class Foo {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
private String body;
|
||||||
|
|
||||||
|
|
||||||
|
protected Foo() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Foo(long id, String title, String body) {
|
||||||
|
this.id = id;
|
||||||
|
this.title = title;
|
||||||
|
this.body = body;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 getBody() {
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBody(String body) {
|
||||||
|
this.body = body;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result;
|
||||||
|
result = prime * result + ((id == null) ? 0 : id.hashCode());
|
||||||
|
result = prime * result + ((title == null) ? 0 : title.hashCode());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj)
|
||||||
|
return true;
|
||||||
|
if (obj == null)
|
||||||
|
return false;
|
||||||
|
if (getClass() != obj.getClass())
|
||||||
|
return false;
|
||||||
|
Foo other = (Foo) obj;
|
||||||
|
if (id == null) {
|
||||||
|
if (other.id != null)
|
||||||
|
return false;
|
||||||
|
} else if (!id.equals(other.id))
|
||||||
|
return false;
|
||||||
|
if (title == null) {
|
||||||
|
if (other.title != null)
|
||||||
|
return false;
|
||||||
|
} else if (!title.equals(other.title))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Foo [id=" + id + ", title=" + title + "]";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,83 @@
|
|||||||
|
package com.baeldung.restdocopenapi;
|
||||||
|
|
||||||
|
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import javax.validation.Valid;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
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.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/foo")
|
||||||
|
public class FooController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
FooRepository repository;
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public ResponseEntity<List<Foo>> getAllFoos() {
|
||||||
|
List<Foo> fooList = (List<Foo>) repository.findAll();
|
||||||
|
if (fooList.isEmpty()) {
|
||||||
|
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
return new ResponseEntity<>(fooList, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = "{id}")
|
||||||
|
public ResponseEntity<Foo> getFooById(@PathVariable("id") Long id) {
|
||||||
|
|
||||||
|
Optional<Foo> foo = repository.findById(id);
|
||||||
|
return foo.isPresent() ? new ResponseEntity<>(foo.get(), HttpStatus.OK) : new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ResponseEntity<Foo> addFoo(@RequestBody @Valid Foo foo) {
|
||||||
|
HttpHeaders httpHeaders = new HttpHeaders();
|
||||||
|
httpHeaders.setLocation(linkTo(FooController.class).slash(foo.getId())
|
||||||
|
.toUri());
|
||||||
|
Foo savedFoo;
|
||||||
|
try {
|
||||||
|
savedFoo = repository.save(foo);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ResponseEntity<>(savedFoo, httpHeaders, HttpStatus.CREATED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public ResponseEntity<Void> deleteFoo(@PathVariable("id") long id) {
|
||||||
|
try {
|
||||||
|
repository.deleteById(id);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public ResponseEntity<Foo> updateFoo(@PathVariable("id") long id, @RequestBody Foo foo) {
|
||||||
|
boolean isFooPresent = repository.existsById(Long.valueOf(id));
|
||||||
|
|
||||||
|
if (!isFooPresent) {
|
||||||
|
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
Foo updatedFoo = repository.save(foo);
|
||||||
|
|
||||||
|
return new ResponseEntity<>(updatedFoo, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package com.baeldung.restdocopenapi;
|
||||||
|
|
||||||
|
import org.springframework.data.repository.PagingAndSortingRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface FooRepository extends PagingAndSortingRepository<Foo, Long>{
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,121 @@
|
|||||||
|
package com.baeldung.restdocopenapi.springdoc;
|
||||||
|
|
||||||
|
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import javax.validation.Valid;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
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.RestController;
|
||||||
|
|
||||||
|
import com.baeldung.restdocopenapi.Foo;
|
||||||
|
import com.baeldung.restdocopenapi.FooRepository;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
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 io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/foobar")
|
||||||
|
@Tag(name = "foobar", description = "the foobar API with documentation annotations")
|
||||||
|
public class FooBarController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
FooRepository repository;
|
||||||
|
|
||||||
|
@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 = "404", description = "No Foos found", content = @Content) })
|
||||||
|
@GetMapping
|
||||||
|
public ResponseEntity<List<Foo>> getAllFoos() {
|
||||||
|
List<Foo> fooList = (List<Foo>) repository.findAll();
|
||||||
|
if (fooList.isEmpty()) {
|
||||||
|
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
return new ResponseEntity<>(fooList, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "Get a foo by foo id")
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(responseCode = "200", description = "found the foo", content = {
|
||||||
|
@Content(mediaType = "application/json", schema = @Schema(implementation = Foo.class))}),
|
||||||
|
@ApiResponse(responseCode = "400", description = "Invalid id supplied", content = @Content),
|
||||||
|
@ApiResponse(responseCode = "404", description = "Foo not found", content = @Content) })
|
||||||
|
@GetMapping(value = "{id}")
|
||||||
|
public ResponseEntity<Foo> getFooById(@Parameter(description = "id of foo to be searched") @PathVariable("id") String id) {
|
||||||
|
|
||||||
|
Optional<Foo> foo = repository.findById(Long.valueOf(id));
|
||||||
|
return foo.isPresent() ? new ResponseEntity<>(foo.get(), HttpStatus.OK) : new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "Create a foo")
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(responseCode = "201", description = "foo created", content = { @
|
||||||
|
Content(mediaType = "application/json", schema = @Schema(implementation = Foo.class))}),
|
||||||
|
@ApiResponse(responseCode = "404", description = "Bad request", content = @Content) })
|
||||||
|
@PostMapping
|
||||||
|
public ResponseEntity<Foo> addFoo(@Parameter(description = "foo object to be created") @RequestBody @Valid Foo foo) {
|
||||||
|
HttpHeaders httpHeaders = new HttpHeaders();
|
||||||
|
httpHeaders.setLocation(linkTo(FooBarController.class).slash(foo.getId()).toUri());
|
||||||
|
Foo savedFoo;
|
||||||
|
try {
|
||||||
|
savedFoo = repository.save(foo);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ResponseEntity<>(savedFoo, httpHeaders, HttpStatus.CREATED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "Delete a foo")
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(responseCode = "204", description = "foo deleted"),
|
||||||
|
@ApiResponse(responseCode = "404", description = "Bad request", content = @Content) })
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public ResponseEntity<Void> deleteFoo(@Parameter(description = "id of foo to be deleted") @PathVariable("id") long id) {
|
||||||
|
try {
|
||||||
|
repository.deleteById(id);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "Update a foo")
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(responseCode = "200", description = "foo updated successfully", content = {
|
||||||
|
@Content(mediaType = "application/json", schema = @Schema(implementation = Foo.class))}),
|
||||||
|
@ApiResponse(responseCode = "404", description = "No Foo exists with given id", content = @Content) })
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public ResponseEntity<Foo> updateFoo(@Parameter(description = "id of foo to be updated") @PathVariable("id") long id, @RequestBody Foo foo) {
|
||||||
|
|
||||||
|
boolean isFooPresent = repository.existsById(Long.valueOf(id));
|
||||||
|
|
||||||
|
if (!isFooPresent) {
|
||||||
|
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
Foo updatedFoo = repository.save(foo);
|
||||||
|
|
||||||
|
return new ResponseEntity<>(updatedFoo, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
}
|
@ -5,4 +5,7 @@ springdoc.swagger-ui.path=/swagger-ui-custom.html
|
|||||||
springdoc.api-docs.path=/api-docs
|
springdoc.api-docs.path=/api-docs
|
||||||
|
|
||||||
# H2 Related Configurations
|
# H2 Related Configurations
|
||||||
spring.datasource.url=jdbc:h2:mem:springdoc
|
spring.datasource.url=jdbc:h2:mem:springdoc
|
||||||
|
|
||||||
|
springdoc.version=@springdoc.version@
|
||||||
|
spring.jpa.hibernate.ddl-auto=none
|
@ -0,0 +1,153 @@
|
|||||||
|
= RESTful Notes API Guide
|
||||||
|
Baeldung;
|
||||||
|
:doctype: book
|
||||||
|
:icons: font
|
||||||
|
:source-highlighter: highlightjs
|
||||||
|
:toc: left
|
||||||
|
:toclevels: 4
|
||||||
|
:sectlinks:
|
||||||
|
|
||||||
|
[[overview]]
|
||||||
|
= Overview
|
||||||
|
|
||||||
|
[[overview-http-verbs]]
|
||||||
|
== HTTP verbs
|
||||||
|
|
||||||
|
RESTful notes tries to adhere as closely as possible to standard HTTP and REST conventions in its
|
||||||
|
use of HTTP verbs.
|
||||||
|
|
||||||
|
|===
|
||||||
|
| Verb | Usage
|
||||||
|
|
||||||
|
| `GET`
|
||||||
|
| Used to retrieve a resource
|
||||||
|
|
||||||
|
| `POST`
|
||||||
|
| Used to create a new resource
|
||||||
|
|
||||||
|
| `PUT`
|
||||||
|
| Used to update an existing resource
|
||||||
|
|
||||||
|
| `DELETE`
|
||||||
|
| Used to delete an existing resource
|
||||||
|
|===
|
||||||
|
|
||||||
|
RESTful notes tries to adhere as closely as possible to standard HTTP and REST conventions in its
|
||||||
|
use of HTTP status codes.
|
||||||
|
|
||||||
|
|===
|
||||||
|
| Status code | Usage
|
||||||
|
|
||||||
|
| `200 OK`
|
||||||
|
| The request completed successfully
|
||||||
|
|
||||||
|
| `201 Created`
|
||||||
|
| A new resource has been created successfully. The resource's URI is available from the response's
|
||||||
|
`Location` header
|
||||||
|
|
||||||
|
| `204 No Content`
|
||||||
|
| An update to an existing resource has been applied successfully
|
||||||
|
|
||||||
|
| `400 Bad Request`
|
||||||
|
| The request was malformed. The response body will include an error providing further information
|
||||||
|
|
||||||
|
| `404 Not Found`
|
||||||
|
| The requested resource did not exist
|
||||||
|
|===
|
||||||
|
|
||||||
|
[[overview-hypermedia]]
|
||||||
|
== Hypermedia
|
||||||
|
|
||||||
|
RESTful Notes uses hypermedia and resources include links to other resources in their
|
||||||
|
responses. Responses are in http://stateless.co/hal_specification.html[Hypertext Application
|
||||||
|
from resource to resource.
|
||||||
|
Language (HAL)] format. Links can be found beneath the `_links` key. Users of the API should
|
||||||
|
not create URIs themselves, instead they should use the above-described links to navigate
|
||||||
|
|
||||||
|
[[resources]]
|
||||||
|
= Resources
|
||||||
|
|
||||||
|
[[resources-FOO]]
|
||||||
|
== FOO REST Service
|
||||||
|
|
||||||
|
The FOO provides the entry point into the service.
|
||||||
|
|
||||||
|
[[resources-foo-get]]
|
||||||
|
=== Accessing the foo GET
|
||||||
|
|
||||||
|
A `GET` request is used to access the foo read.
|
||||||
|
|
||||||
|
==== Request structure
|
||||||
|
|
||||||
|
include::{snippets}/getAFoo/http-request.adoc[]
|
||||||
|
|
||||||
|
==== Path Parameters
|
||||||
|
include::{snippets}/getAFoo/path-parameters.adoc[]
|
||||||
|
|
||||||
|
==== Example response
|
||||||
|
|
||||||
|
include::{snippets}/getAFoo/http-response.adoc[]
|
||||||
|
|
||||||
|
==== CURL request
|
||||||
|
|
||||||
|
include::{snippets}/getAFoo/curl-request.adoc[]
|
||||||
|
|
||||||
|
[[resources-foo-post]]
|
||||||
|
=== Accessing the foo POST
|
||||||
|
|
||||||
|
A `POST` request is used to access the foo create.
|
||||||
|
|
||||||
|
==== Request structure
|
||||||
|
|
||||||
|
include::{snippets}/createFoo/http-request.adoc[]
|
||||||
|
|
||||||
|
==== Example response
|
||||||
|
|
||||||
|
include::{snippets}/createFoo/http-response.adoc[]
|
||||||
|
|
||||||
|
==== CURL request
|
||||||
|
|
||||||
|
include::{snippets}/createFoo/curl-request.adoc[]
|
||||||
|
|
||||||
|
[[resources-foo-delete]]
|
||||||
|
=== Accessing the foo DELETE
|
||||||
|
|
||||||
|
A `DELETE` request is used to access the foo delete.
|
||||||
|
|
||||||
|
==== Request structure
|
||||||
|
|
||||||
|
include::{snippets}/deleteFoo/http-request.adoc[]
|
||||||
|
|
||||||
|
==== Path Parameters
|
||||||
|
include::{snippets}/deleteFoo/path-parameters.adoc[]
|
||||||
|
|
||||||
|
==== Example response
|
||||||
|
|
||||||
|
include::{snippets}/deleteFoo/http-response.adoc[]
|
||||||
|
|
||||||
|
==== CURL request
|
||||||
|
|
||||||
|
include::{snippets}/deleteFoo/curl-request.adoc[]
|
||||||
|
|
||||||
|
[[resources-foo-put]]
|
||||||
|
=== Accessing the foo PUT
|
||||||
|
|
||||||
|
A `PUT` request is used to access the foo update.
|
||||||
|
|
||||||
|
==== Request structure
|
||||||
|
|
||||||
|
include::{snippets}/updateFoo/http-request.adoc[]
|
||||||
|
|
||||||
|
==== Path Parameters
|
||||||
|
include::{snippets}/updateFoo/path-parameters.adoc[]
|
||||||
|
|
||||||
|
==== Example response
|
||||||
|
|
||||||
|
include::{snippets}/updateFoo/http-response.adoc[]
|
||||||
|
|
||||||
|
==== CURL request
|
||||||
|
|
||||||
|
include::{snippets}/updateFoo/curl-request.adoc[]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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');
|
||||||
|
|
@ -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)
|
||||||
|
);
|
@ -0,0 +1,120 @@
|
|||||||
|
package com.baeldung.restdocopenapi.restdoc;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
|
||||||
|
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
|
||||||
|
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.delete;
|
||||||
|
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
|
||||||
|
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post;
|
||||||
|
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.put;
|
||||||
|
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest;
|
||||||
|
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse;
|
||||||
|
import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint;
|
||||||
|
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
|
||||||
|
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
|
||||||
|
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
|
||||||
|
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
|
||||||
|
import static org.springframework.restdocs.request.RequestDocumentation.pathParameters;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
import static org.springframework.util.StringUtils.collectionToDelimitedString;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.hateoas.MediaTypes;
|
||||||
|
import org.springframework.restdocs.RestDocumentationContextProvider;
|
||||||
|
import org.springframework.restdocs.RestDocumentationExtension;
|
||||||
|
import org.springframework.restdocs.constraints.ConstraintDescriptions;
|
||||||
|
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||||
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||||
|
import org.springframework.web.context.WebApplicationContext;
|
||||||
|
|
||||||
|
import com.baeldung.restdocopenapi.Application;
|
||||||
|
import com.baeldung.restdocopenapi.Foo;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
@ExtendWith({ RestDocumentationExtension.class, SpringExtension.class })
|
||||||
|
@SpringBootTest(classes = Application.class)
|
||||||
|
public class SpringRestDocsUnitTest {
|
||||||
|
|
||||||
|
private MockMvc mockMvc;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setup(WebApplicationContext webApplicationContext, RestDocumentationContextProvider restDocumentation) {
|
||||||
|
this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
|
||||||
|
.apply(documentationConfiguration(restDocumentation))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenGetFoo_thenSuccessful() throws Exception {
|
||||||
|
this.mockMvc.perform(get("/foo"))
|
||||||
|
.andDo(print())
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(content().string(containsString("Foo 1")))
|
||||||
|
.andDo(document("getAllFoos"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenGetFooById_thenSuccessful() throws Exception {
|
||||||
|
ConstraintDescriptions desc = new ConstraintDescriptions(Foo.class);
|
||||||
|
|
||||||
|
this.mockMvc.perform(get("/foo/{id}", 1))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andDo(document("getAFoo", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()),
|
||||||
|
pathParameters(parameterWithName("id").description("id of foo to be searched")),
|
||||||
|
responseFields(fieldWithPath("id").description("The id of the foo" + collectionToDelimitedString(desc.descriptionsForProperty("id"), ". ")),
|
||||||
|
fieldWithPath("title").description("The title of the foo"), fieldWithPath("body").description("The body of the foo"))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenPostFoo_thenSuccessful() throws Exception {
|
||||||
|
Map<String, Object> foo = new HashMap<>();
|
||||||
|
foo.put("id", 4L);
|
||||||
|
foo.put("title", "New Foo");
|
||||||
|
foo.put("body", "Body of New Foo");
|
||||||
|
|
||||||
|
this.mockMvc.perform(post("/foo").contentType(MediaTypes.HAL_JSON)
|
||||||
|
.content(this.objectMapper.writeValueAsString(foo)))
|
||||||
|
.andExpect(status().isCreated())
|
||||||
|
.andDo(document("createFoo", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), requestFields(fieldWithPath("id").description("The id of the foo"), fieldWithPath("title").description("The title of the foo"),
|
||||||
|
fieldWithPath("body").description("The body of the foo"))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenDeleteFoo_thenSuccessful() throws Exception {
|
||||||
|
this.mockMvc.perform(delete("/foo/{id}", 2))
|
||||||
|
.andExpect(status().isNoContent())
|
||||||
|
.andDo(document("deleteFoo", pathParameters(parameterWithName("id").description("The id of the foo to delete"))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenUpdateFoo_thenSuccessful() throws Exception {
|
||||||
|
|
||||||
|
ConstraintDescriptions desc = new ConstraintDescriptions(Foo.class);
|
||||||
|
|
||||||
|
Map<String, Object> foo = new HashMap<>();
|
||||||
|
foo.put("title", "Updated Foo");
|
||||||
|
foo.put("body", "Body of Updated Foo");
|
||||||
|
|
||||||
|
this.mockMvc.perform(put("/foo/{id}", 3).contentType(MediaTypes.HAL_JSON)
|
||||||
|
.content(this.objectMapper.writeValueAsString(foo)))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andDo(document("updateFoo", pathParameters(parameterWithName("id").description("The id of the foo to update")),
|
||||||
|
responseFields(fieldWithPath("id").description("The id of the updated foo" + collectionToDelimitedString(desc.descriptionsForProperty("id"), ". ")),
|
||||||
|
fieldWithPath("title").description("The title of the updated foo"), fieldWithPath("body").description("The body of the updated foo"))));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user