Skip to content

Commit

Permalink
9. Валидация моделей в REST [#505115]
Browse files Browse the repository at this point in the history
  • Loading branch information
vadimstrya committed Jan 27, 2025
1 parent 9747305 commit 9fc10d2
Show file tree
Hide file tree
Showing 12 changed files with 98 additions and 13 deletions.
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
Expand Down
15 changes: 10 additions & 5 deletions src/main/java/ru/job4j/api/controller/PostController.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
package ru.job4j.api.controller;

import jakarta.validation.Valid;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import ru.job4j.api.dto.PostDto;
import ru.job4j.api.dto.response.PostDto;
import ru.job4j.api.entity.UserPost;
import ru.job4j.api.service.PostService;

import java.util.List;

@Validated
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/post")
Expand All @@ -19,14 +24,14 @@ public class PostController {
private final PostService postService;

@GetMapping("/{postId}")
public ResponseEntity<UserPost> get(@PathVariable Long postId) {
public ResponseEntity<UserPost> get(@PathVariable @NotNull @Min(value = 1, message = "postId должен быть больше нуля") Long postId) {
return postService.getById(postId)
.map(ResponseEntity::ok)
.orElseGet(() -> ResponseEntity.notFound().build());
}

@PostMapping
public ResponseEntity<UserPost> save(@RequestBody UserPost post) {
public ResponseEntity<UserPost> save(@Valid @RequestBody UserPost post) {
postService.create(post);
var uri = ServletUriComponentsBuilder
.fromCurrentRequest()
Expand All @@ -39,15 +44,15 @@ public ResponseEntity<UserPost> save(@RequestBody UserPost post) {
}

@PutMapping
public ResponseEntity<Void> update(@RequestBody UserPost post) {
public ResponseEntity<Void> update(@Valid @RequestBody UserPost post) {
if (postService.update(post)) {
return ResponseEntity.ok().build();
}
return ResponseEntity.notFound().build();
}

@DeleteMapping("/{postId}")
public ResponseEntity<Void> delete(@PathVariable Long postId) {
public ResponseEntity<Void> delete(@PathVariable @NotNull @Min(value = 1, message = "postId должен быть больше нуля") Long postId) {
if (postService.deleteById(postId)) {
return ResponseEntity.noContent().build();
}
Expand Down
13 changes: 9 additions & 4 deletions src/main/java/ru/job4j/api/controller/UserController.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
package ru.job4j.api.controller;

import jakarta.validation.Valid;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import ru.job4j.api.entity.User;
import ru.job4j.api.service.UserService;

@Validated
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/user")
Expand All @@ -16,14 +21,14 @@ public class UserController {
private final UserService userService;

@GetMapping("/{userId}")
public ResponseEntity<User> get(@PathVariable Long userId) {
public ResponseEntity<User> get(@PathVariable @NotNull @Min(value = 1, message = "userId должен быть больше нуля") Long userId) {
return userService.getById(userId)
.map(ResponseEntity::ok)
.orElseGet(() -> ResponseEntity.notFound().build());
}

@PostMapping
public ResponseEntity<User> save(@RequestBody User user) {
public ResponseEntity<User> save(@Valid @RequestBody User user) {
userService.create(user);
var uri = ServletUriComponentsBuilder
.fromCurrentRequest()
Expand All @@ -36,15 +41,15 @@ public ResponseEntity<User> save(@RequestBody User user) {
}

@PutMapping
public ResponseEntity<Void> update(@RequestBody User user) {
public ResponseEntity<Void> update(@Valid @RequestBody User user) {
if (userService.update(user)) {
return ResponseEntity.ok().build();
}
return ResponseEntity.notFound().build();
}

@DeleteMapping("/{userId}")
public ResponseEntity<Void> delete(@PathVariable Long userId) {
public ResponseEntity<Void> delete(@PathVariable @NotNull @Min(value = 1, message = "userId должен быть больше нуля") Long userId) {
if (userService.deleteById(userId)) {
return ResponseEntity.noContent().build();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package ru.job4j.api.dto;
package ru.job4j.api.dto.response;

import lombok.Getter;
import lombok.Setter;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package ru.job4j.api.dto.response;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

import java.util.List;

@Getter
@RequiredArgsConstructor
public class ValidationErrorResponse {

private final List<Violation> violations;
}
12 changes: 12 additions & 0 deletions src/main/java/ru/job4j/api/dto/response/Violation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package ru.job4j.api.dto.response;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public class Violation {

private final String fieldName;
private final String message;
}
6 changes: 6 additions & 0 deletions src/main/java/ru/job4j/api/entity/User.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package ru.job4j.api.entity;

import jakarta.persistence.*;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
import ru.job4j.api.enums.Statuses;
Expand All @@ -21,14 +23,18 @@ public class User {
private Long id;

/** Имя */
@NotBlank
@Column(name = "login")
private String login;

/** Email */
@NotBlank
@Email
@Column(name = "email")
private String email;

/** Пароль */
@NotBlank
@Column(name = "password")
private String password;

Expand Down
5 changes: 5 additions & 0 deletions src/main/java/ru/job4j/api/entity/UserPost.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package ru.job4j.api.entity;

import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import ru.job4j.api.enums.Statuses;

import lombok.Getter;
Expand All @@ -22,14 +24,17 @@ public class UserPost {
private Long id;

/** Ид пользователя, ссылка на user_auth (id) */
@NotNull
@Column(name = "user_id")
private Long userId;

/** Заголовок */
@NotBlank
@Column(name = "title")
private String title;

/** Текст поста */
@NotBlank
@Column(name = "text")
private String text;

Expand Down
34 changes: 34 additions & 0 deletions src/main/java/ru/job4j/api/handler/GlobalExceptionHandler.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,30 @@
package ru.job4j.api.handler;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import ru.job4j.api.dto.response.ValidationErrorResponse;
import ru.job4j.api.dto.response.Violation;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestControllerAdvice
public class GlobalExceptionHandler {

private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class.getSimpleName());

@ExceptionHandler(DataIntegrityViolationException.class)
public ResponseEntity<Object> handleDataIntegrityViolationException(Exception e, HttpServletRequest request) {
Map<String, String> details = new HashMap<>();
Expand All @@ -23,4 +34,27 @@ public ResponseEntity<Object> handleDataIntegrityViolationException(Exception e,
details.put("path", request.getRequestURI());
return ResponseEntity.badRequest().contentType(MediaType.APPLICATION_JSON).body(details);
}

@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ValidationErrorResponse onConstraintValidationException(ConstraintViolationException e) {
final List<Violation> violations = e.getConstraintViolations().stream()
.map(violation -> new Violation(
violation.getPropertyPath().toString(),
violation.getMessage()
))
.toList();
LOGGER.warn(e.getLocalizedMessage());
return new ValidationErrorResponse(violations);
}

@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ValidationErrorResponse onMethodArgumentNotValidException(MethodArgumentNotValidException e) {
final List<Violation> violations = e.getBindingResult().getFieldErrors().stream()
.map(error -> new Violation(error.getField(), error.getDefaultMessage()))
.toList();
LOGGER.warn(e.getLocalizedMessage());
return new ValidationErrorResponse(violations);
}
}
2 changes: 1 addition & 1 deletion src/main/java/ru/job4j/api/mapper/PostMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import org.mapstruct.Mapping;
import org.mapstruct.MappingConstants;
import org.mapstruct.ReportingPolicy;
import ru.job4j.api.dto.PostDto;
import ru.job4j.api.dto.response.PostDto;
import ru.job4j.api.entity.User;
import ru.job4j.api.entity.UserPost;

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/ru/job4j/api/service/PostService.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package ru.job4j.api.service;

import ru.job4j.api.dto.PostDto;
import ru.job4j.api.dto.response.PostDto;
import ru.job4j.api.dto.request.post.UserPostCreateRequest;
import ru.job4j.api.dto.request.post.UserPostImageUpdateRequest;
import ru.job4j.api.dto.request.post.UserPostUpdateRequest;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.job4j.api.dto.PostDto;
import ru.job4j.api.dto.response.PostDto;
import ru.job4j.api.dto.request.post.UserPostCreateRequest;
import ru.job4j.api.dto.request.post.UserPostImageUpdateRequest;
import ru.job4j.api.dto.request.post.UserPostUpdateRequest;
Expand Down

0 comments on commit 9fc10d2

Please sign in to comment.