add request validation

This commit is contained in:
Denis Savosin
2024-09-30 15:27:45 +07:00
parent 27595e08dc
commit f9632ac568
8 changed files with 139 additions and 13 deletions

View File

@@ -5,8 +5,7 @@ import com.example.demo.exceptions.UnprocessableException
import com.example.demo.requests.CreateProductRequest
import com.example.demo.responses.makeOkResponse
import com.example.demo.services.ProductService
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.encodeToJsonElement
import jakarta.validation.Valid
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
@@ -16,31 +15,31 @@ import java.util.*
@RestController
@RequestMapping(value = ["/api/product"], produces = [MediaType.APPLICATION_JSON_VALUE])
class ProductController(
val productService: ProductService
val productService: ProductService,
) {
@GetMapping("/{guid}")
@ResponseBody
@Throws(NotFoundException::class)
fun getProduct(
@PathVariable guid: UUID
): String {
@PathVariable guid: UUID,
): ResponseEntity<Any> {
val product = productService.findByGuid(guid = guid) ?: throw NotFoundException()
return Json.encodeToJsonElement(value = product).toString()
return ResponseEntity(product, HttpStatus.OK)
}
@PostMapping(value = ["/"], consumes = [MediaType.APPLICATION_JSON_VALUE])
@PostMapping(value = [""], consumes = [MediaType.APPLICATION_JSON_VALUE])
@ResponseBody
fun createProduct(
@RequestBody product: CreateProductRequest
): String {
@Valid @RequestBody product: CreateProductRequest,
): ResponseEntity<Any> {
val saved = productService.create(
product.name,
product.price,
product.description,
)
return Json.encodeToJsonElement(value = saved).toString()
return ResponseEntity(saved, HttpStatus.CREATED)
}
@DeleteMapping("/{guid}")

View File

@@ -1,20 +1,41 @@
package com.example.demo.exceptions
import com.example.demo.responses.makeBadRequestResponse
import com.example.demo.responses.makeNotFoundResponse
import com.example.demo.responses.makeUnprocessableResponse
import com.example.demo.responses.makeUnprocessableResponseWithErrors
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
@ControllerAdvice
class ExceptionHandler {
/* 4xx status codes */
// 400
@ExceptionHandler(HttpMessageNotReadableException::class)
fun handleMessageNotReadable(exception: HttpMessageNotReadableException): ResponseEntity<Any> = ResponseEntity(
makeBadRequestResponse(exception.message.toString()),
HttpStatus.BAD_REQUEST,
)
// 404
@ExceptionHandler(NotFoundException::class)
fun handleNotFound(): ResponseEntity<Any> = ResponseEntity(makeNotFoundResponse(), HttpStatus.NOT_FOUND)
// 422
@ExceptionHandler(UnprocessableException::class)
fun handleUnprocessable(exception: UnprocessableException): ResponseEntity<Any> = ResponseEntity(
makeUnprocessableResponse(exception.message),
HttpStatus.UNPROCESSABLE_ENTITY,
)
@ExceptionHandler(MethodArgumentNotValidException::class)
fun handleMethodArgumentNotValid(exception: MethodArgumentNotValidException): ResponseEntity<Any> = ResponseEntity(
makeUnprocessableResponseWithErrors(exception.javaClass.name, exception.allErrors),
HttpStatus.UNPROCESSABLE_ENTITY,
)
}

View File

@@ -1,7 +1,12 @@
package com.example.demo.requests
import jakarta.validation.constraints.Min
import jakarta.validation.constraints.NotBlank
data class CreateProductRequest(
@field:NotBlank(message = "name value required")
val name: String,
val description: String?,
@field:Min(value = 0, message = "price must be positive value")
val price: Long,
)

View File

@@ -0,0 +1,7 @@
package com.example.demo.responses
data class BadRequestResponse(
val cause: String,
): BaseResponse(status = ResponseStatus.BAD_REQUEST)
fun makeBadRequestResponse(cause: String): BadRequestResponse = BadRequestResponse(cause)

View File

@@ -5,5 +5,6 @@ import com.fasterxml.jackson.annotation.JsonValue
enum class ResponseStatus(@JsonValue val status: String) {
OK("ok"),
NOT_FOUND("not found"),
BAD_REQUEST("bad request"),
UNPROCESSABLE("unprocessable");
}

View File

@@ -1,7 +1,13 @@
package com.example.demo.responses
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 makeUnprocessableResponse(cause: String): UnprocessableResponse = UnprocessableResponse(cause)
fun makeUnprocessableResponseWithErrors(
cause: String, errors: List<ObjectError>,
): UnprocessableResponse = UnprocessableResponse(cause, errors)