mirror of
https://github.com/Dannecron/spring-boot-demo.git
synced 2025-12-26 00:32:34 +03:00
add openapi auto generation
some responses refactoring
This commit is contained in:
@@ -3,11 +3,18 @@ package com.example.demo.http.controllers
|
||||
import com.example.demo.http.exceptions.NotFoundException
|
||||
import com.example.demo.http.exceptions.UnprocessableException
|
||||
import com.example.demo.http.requests.CreateProductRequest
|
||||
import com.example.demo.http.responses.NotFoundResponse
|
||||
import com.example.demo.http.responses.makeOkResponse
|
||||
import com.example.demo.http.responses.page.PageResponse
|
||||
import com.example.demo.models.Product
|
||||
import com.example.demo.services.database.exceptions.AlreadyDeletedException
|
||||
import com.example.demo.services.database.product.ProductService
|
||||
import com.example.demo.services.database.product.exceptions.ProductNotFoundException
|
||||
import com.example.demo.services.kafka.exceptions.InvalidArgumentException
|
||||
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 org.springdoc.core.annotations.ParameterObject
|
||||
import org.springframework.data.domain.Pageable
|
||||
@@ -24,6 +31,14 @@ class ProductController(
|
||||
) {
|
||||
@GetMapping("/{guid}")
|
||||
@Throws(NotFoundException::class)
|
||||
@ApiResponses(value = [
|
||||
ApiResponse(responseCode = "200", content = [
|
||||
Content(mediaType = "application/json", schema = Schema(implementation = Product::class)),
|
||||
]),
|
||||
ApiResponse(responseCode = "404", content = [
|
||||
Content(mediaType = "application/json", schema = Schema(implementation = NotFoundResponse::class))
|
||||
])
|
||||
])
|
||||
fun getProduct(
|
||||
@PathVariable guid: UUID,
|
||||
): ResponseEntity<Any> {
|
||||
@@ -33,19 +48,18 @@ class ProductController(
|
||||
}
|
||||
|
||||
@GetMapping("")
|
||||
@ApiResponses(value = [
|
||||
ApiResponse(responseCode = "200", content = [
|
||||
Content(mediaType = "application/json", schema = Schema(implementation = PageResponse::class)),
|
||||
]),
|
||||
])
|
||||
fun getProducts(
|
||||
@ParameterObject pageable: Pageable,
|
||||
): ResponseEntity<Any> {
|
||||
val products = productService.findAll(pageable)
|
||||
|
||||
return ResponseEntity(
|
||||
mapOf(
|
||||
"data" to products.content,
|
||||
"meta" to mapOf(
|
||||
"total" to products.totalElements,
|
||||
"pages" to products.totalPages,
|
||||
),
|
||||
),
|
||||
PageResponse(products),
|
||||
HttpStatus.OK,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
package com.example.demo.http.exceptions
|
||||
|
||||
import com.example.demo.http.responses.makeBadRequestResponse
|
||||
import com.example.demo.http.responses.makeNotFoundResponse
|
||||
import com.example.demo.http.responses.makeUnprocessableResponse
|
||||
import com.example.demo.http.responses.makeUnprocessableResponseWithErrors
|
||||
import com.example.demo.http.responses.BadRequestResponse
|
||||
import com.example.demo.http.responses.NotFoundResponse
|
||||
import com.example.demo.http.responses.UnprocessableResponse
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler
|
||||
import org.springframework.web.bind.annotation.ResponseStatus
|
||||
|
||||
@ControllerAdvice
|
||||
class ExceptionHandler {
|
||||
@@ -17,25 +17,29 @@ class ExceptionHandler {
|
||||
|
||||
// 400
|
||||
@ExceptionHandler(HttpMessageNotReadableException::class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
fun handleMessageNotReadable(exception: HttpMessageNotReadableException): ResponseEntity<Any> = ResponseEntity(
|
||||
makeBadRequestResponse(exception.message.toString()),
|
||||
BadRequestResponse(exception.message.toString()),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
)
|
||||
|
||||
// 404
|
||||
@ExceptionHandler(NotFoundException::class)
|
||||
fun handleNotFound(): ResponseEntity<Any> = ResponseEntity(makeNotFoundResponse(), HttpStatus.NOT_FOUND)
|
||||
@ResponseStatus(HttpStatus.NOT_FOUND)
|
||||
fun handleNotFound(): ResponseEntity<Any> = ResponseEntity(NotFoundResponse(), HttpStatus.NOT_FOUND)
|
||||
|
||||
// 422
|
||||
@ExceptionHandler(UnprocessableException::class)
|
||||
@ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
|
||||
fun handleUnprocessable(exception: UnprocessableException): ResponseEntity<Any> = ResponseEntity(
|
||||
makeUnprocessableResponse(exception.message),
|
||||
UnprocessableResponse(exception.message),
|
||||
HttpStatus.UNPROCESSABLE_ENTITY,
|
||||
)
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException::class)
|
||||
@ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
|
||||
fun handleMethodArgumentNotValid(exception: MethodArgumentNotValidException): ResponseEntity<Any> = ResponseEntity(
|
||||
makeUnprocessableResponseWithErrors(exception.javaClass.name, exception.allErrors),
|
||||
UnprocessableResponse(exception.javaClass.name, exception.allErrors),
|
||||
HttpStatus.UNPROCESSABLE_ENTITY,
|
||||
)
|
||||
}
|
||||
@@ -3,5 +3,3 @@ package com.example.demo.http.responses
|
||||
data class BadRequestResponse(
|
||||
val cause: String,
|
||||
): BaseResponse(status = ResponseStatus.BAD_REQUEST)
|
||||
|
||||
fun makeBadRequestResponse(cause: String): BadRequestResponse = BadRequestResponse(cause)
|
||||
|
||||
@@ -3,5 +3,3 @@ package com.example.demo.http.responses
|
||||
open class BaseResponse(val status: ResponseStatus)
|
||||
|
||||
fun makeOkResponse(): BaseResponse = BaseResponse(status = ResponseStatus.OK)
|
||||
|
||||
fun makeNotFoundResponse(): BaseResponse = BaseResponse(status = ResponseStatus.NOT_FOUND)
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.example.demo.http.responses
|
||||
|
||||
class NotFoundResponse: BaseResponse(ResponseStatus.NOT_FOUND)
|
||||
@@ -4,10 +4,7 @@ import org.springframework.validation.ObjectError
|
||||
|
||||
class UnprocessableResponse(
|
||||
val cause: String,
|
||||
val errors: List<ObjectError>? = null
|
||||
): BaseResponse(status = ResponseStatus.UNPROCESSABLE)
|
||||
|
||||
fun makeUnprocessableResponse(cause: String): UnprocessableResponse = UnprocessableResponse(cause)
|
||||
fun makeUnprocessableResponseWithErrors(
|
||||
cause: String, errors: List<ObjectError>,
|
||||
): UnprocessableResponse = UnprocessableResponse(cause, errors)
|
||||
val errors: List<ObjectError>?
|
||||
): BaseResponse(status = ResponseStatus.UNPROCESSABLE) {
|
||||
constructor(cause: String): this(cause, null)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.example.demo.http.responses.page
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class PageMetaDto(
|
||||
val total: Long,
|
||||
val pages: Int,
|
||||
)
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.example.demo.http.responses.page
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.springframework.data.domain.Page
|
||||
|
||||
@Serializable
|
||||
data class PageResponse<T>(
|
||||
val data: List<T>,
|
||||
val meta: PageMetaDto,
|
||||
) {
|
||||
constructor(page: Page<T>) : this(
|
||||
data = page.content,
|
||||
meta = PageMetaDto(
|
||||
total = page.totalElements,
|
||||
pages = page.totalPages
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -24,3 +24,9 @@ kafka:
|
||||
group-id: demo-consumer
|
||||
topics: demo-city-sync
|
||||
auto-startup: true
|
||||
|
||||
springdoc:
|
||||
api-docs:
|
||||
path: /doc/openapi
|
||||
swagger-ui:
|
||||
path: /doc/swagger-ui.html
|
||||
@@ -83,8 +83,10 @@ class ProductControllerTest(@Autowired val mockMvc: MockMvc): BaseUnitTest() {
|
||||
@Test
|
||||
fun getProducts_success() {
|
||||
val now = OffsetDateTime.now()
|
||||
val pageRequest = PageRequest.of(1, 2, Sort.by(Sort.Direction.DESC, "createdAt"))
|
||||
|
||||
whenever(productService.findAll(
|
||||
PageRequest.of(1, 2, Sort.by(Sort.Direction.DESC, "createdAt")),
|
||||
pageRequest,
|
||||
)) doReturn PageImpl(listOf(Product(
|
||||
id = 12,
|
||||
guid = UUID.randomUUID(),
|
||||
@@ -96,7 +98,7 @@ class ProductControllerTest(@Autowired val mockMvc: MockMvc): BaseUnitTest() {
|
||||
deletedAt = null,
|
||||
)))
|
||||
|
||||
mockMvc.get("/api/product?page=1&size=2&sort=created_at,desc")
|
||||
mockMvc.get("/api/product?page=1&size=2&sort=createdAt,desc")
|
||||
.andExpect { status { status { isOk() } } }
|
||||
.andExpect { content { contentType(MediaType.APPLICATION_JSON) } }
|
||||
.andExpect { jsonPath("\$.meta.total") { value(1) } }
|
||||
|
||||
Reference in New Issue
Block a user