diff --git a/spring-boot-modules/pom.xml b/spring-boot-modules/pom.xml index 2b4a94a7a5..389dbf2d55 100644 --- a/spring-boot-modules/pom.xml +++ b/spring-boot-modules/pom.xml @@ -105,6 +105,7 @@ spring-boot-documentation spring-boot-3-url-matching spring-boot-graalvm-docker + spring-boot-validations diff --git a/spring-boot-modules/spring-boot-validations/pom.xml b/spring-boot-modules/spring-boot-validations/pom.xml new file mode 100644 index 0000000000..13044471af --- /dev/null +++ b/spring-boot-modules/spring-boot-validations/pom.xml @@ -0,0 +1,35 @@ + + 4.0.0 + + com.baeldung.spring-boot-modules + spring-boot-modules + 1.0.0-SNAPSHOT + + spring-boot-validations + spring-boot-validations + Demo of Validations in Spring Boot + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-validation + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-validations/src/main/java/com/baeldung/Application.java b/spring-boot-modules/spring-boot-validations/src/main/java/com/baeldung/Application.java new file mode 100644 index 0000000000..c0490d50c6 --- /dev/null +++ b/spring-boot-modules/spring-boot-validations/src/main/java/com/baeldung/Application.java @@ -0,0 +1,12 @@ +package com.baeldung; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/spring-boot-modules/spring-boot-validations/src/main/java/com/baeldung/controller/ValidationController.java b/spring-boot-modules/spring-boot-validations/src/main/java/com/baeldung/controller/ValidationController.java new file mode 100644 index 0000000000..d4ea9a6336 --- /dev/null +++ b/spring-boot-modules/spring-boot-validations/src/main/java/com/baeldung/controller/ValidationController.java @@ -0,0 +1,33 @@ +package com.baeldung.controller; + +import javax.validation.Valid; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import com.baeldung.dto.BooleanObject; +import com.baeldung.service.ValidationService; + +@RestController +public class ValidationController { + + @Autowired + ValidationService service; + + @PostMapping("/validateBoolean") + public ResponseEntity processBooleanObject(@RequestBody @Valid BooleanObject booleanObj) { + return ResponseEntity.ok("BooleanObject is valid"); + } + + @PostMapping("/validateBooleanAtService") + public ResponseEntity processBooleanObjectAtService() { + BooleanObject boolObj = new BooleanObject(); + boolObj.setBoolField(Boolean.TRUE); + boolObj.setTrueField(Boolean.FALSE); + service.processBoolean(boolObj); + return ResponseEntity.ok("BooleanObject is valid"); + } +} diff --git a/spring-boot-modules/spring-boot-validations/src/main/java/com/baeldung/controlleradvice/GlobalExceptionHandler.java b/spring-boot-modules/spring-boot-validations/src/main/java/com/baeldung/controlleradvice/GlobalExceptionHandler.java new file mode 100644 index 0000000000..82f0839acf --- /dev/null +++ b/spring-boot-modules/spring-boot-validations/src/main/java/com/baeldung/controlleradvice/GlobalExceptionHandler.java @@ -0,0 +1,37 @@ +package com.baeldung.controlleradvice; + +import java.util.stream.Collectors; + +import javax.validation.ConstraintViolationException; + +import org.springframework.http.HttpStatus; +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; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(value = HttpStatus.BAD_REQUEST) + public String handleValidationException(MethodArgumentNotValidException ex) { + return ex.getBindingResult() + .getFieldErrors() + .stream() + .map(e -> e.getDefaultMessage()) + .collect(Collectors.joining(",")); + } + + @ExceptionHandler(IllegalArgumentException.class) + @ResponseStatus(value = HttpStatus.BAD_REQUEST) + public String handleIllegalArugmentException(IllegalArgumentException ex) { + return ex.getMessage(); + } + + @ExceptionHandler(ConstraintViolationException.class) + @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR) + public String handleConstraintViolationException(ConstraintViolationException ex) { + return ex.getMessage(); + } +} diff --git a/spring-boot-modules/spring-boot-validations/src/main/java/com/baeldung/deserializer/BooleanDeserializer.java b/spring-boot-modules/spring-boot-validations/src/main/java/com/baeldung/deserializer/BooleanDeserializer.java new file mode 100644 index 0000000000..01a8e0eba0 --- /dev/null +++ b/spring-boot-modules/spring-boot-validations/src/main/java/com/baeldung/deserializer/BooleanDeserializer.java @@ -0,0 +1,21 @@ +package com.baeldung.deserializer; + +import java.io.IOException; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +public class BooleanDeserializer extends JsonDeserializer { + @Override + public Boolean deserialize(JsonParser parser, DeserializationContext context) throws IOException { + String value = parser.getText(); + if (value != null && value.equals("+")) { + return Boolean.TRUE; + } else if (value != null && value.equals("-")) { + return Boolean.FALSE; + } else { + throw new IllegalArgumentException("Only values accepted as Boolean are + and -"); + } + } +} \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-validations/src/main/java/com/baeldung/dto/BooleanObject.java b/spring-boot-modules/spring-boot-validations/src/main/java/com/baeldung/dto/BooleanObject.java new file mode 100644 index 0000000000..750b23fe11 --- /dev/null +++ b/spring-boot-modules/spring-boot-validations/src/main/java/com/baeldung/dto/BooleanObject.java @@ -0,0 +1,56 @@ +package com.baeldung.dto; + +import javax.validation.constraints.AssertFalse; +import javax.validation.constraints.AssertTrue; +import javax.validation.constraints.NotNull; + +import com.baeldung.deserializer.BooleanDeserializer; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +public class BooleanObject { + + @NotNull(message = "boolField cannot be null") + Boolean boolField; + + @AssertTrue(message = "trueField must have true value") + Boolean trueField; + + @NotNull(message = "falseField cannot be null") + @AssertFalse(message = "falseField must have false value") + Boolean falseField; + + @JsonDeserialize(using = BooleanDeserializer.class) + Boolean boolStringVar; + + public Boolean getBoolField() { + return boolField; + } + + public void setBoolField(Boolean boolField) { + this.boolField = boolField; + } + + public Boolean getTrueField() { + return trueField; + } + + public void setTrueField(Boolean trueField) { + this.trueField = trueField; + } + + public Boolean getFalseField() { + return falseField; + } + + public void setFalseField(Boolean falseField) { + this.falseField = falseField; + } + + public Boolean getBoolStringVar() { + return boolStringVar; + } + + public void setBoolStringVar(Boolean boolStringVar) { + this.boolStringVar = boolStringVar; + } +} diff --git a/spring-boot-modules/spring-boot-validations/src/main/java/com/baeldung/service/ValidationService.java b/spring-boot-modules/spring-boot-validations/src/main/java/com/baeldung/service/ValidationService.java new file mode 100644 index 0000000000..3fc7160bd5 --- /dev/null +++ b/spring-boot-modules/spring-boot-validations/src/main/java/com/baeldung/service/ValidationService.java @@ -0,0 +1,17 @@ +package com.baeldung.service; + +import javax.validation.Valid; + +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import com.baeldung.dto.BooleanObject; + +@Service +@Validated +public class ValidationService { + + public void processBoolean(@Valid BooleanObject booleanObj) { + // further processing + } +} diff --git a/spring-boot-modules/spring-boot-validations/src/test/java/com/baeldung/controller/ValidationControllerUnitTest.java b/spring-boot-modules/spring-boot-validations/src/test/java/com/baeldung/controller/ValidationControllerUnitTest.java new file mode 100644 index 0000000000..f05d76e3f1 --- /dev/null +++ b/spring-boot-modules/spring-boot-validations/src/test/java/com/baeldung/controller/ValidationControllerUnitTest.java @@ -0,0 +1,125 @@ +package com.baeldung.controller; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +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.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; + +import com.baeldung.service.ValidationService; + +@ExtendWith(SpringExtension.class) +@WebMvcTest(controllers = ValidationController.class) +class ValidationControllerUnitTest { + + @Autowired + private MockMvc mockMvc; + + @TestConfiguration + static class EmployeeServiceImplTestContextConfiguration { + @Bean + public ValidationService validationService() { + return new ValidationService() { + }; + } + } + + @Autowired + ValidationService service; + + @Test + void whenNullInputForBooleanField_thenHttpBadRequestAsHttpResponse() throws Exception { + String postBody = "{\"boolField\":null,\"trueField\":true,\"falseField\":false,\"boolStringVar\":\"+\"}"; + + mockMvc.perform(post("/validateBoolean").contentType("application/json") + .content(postBody)) + .andExpect(status().isBadRequest()); + } + + @Test + void whenInvalidInputForTrueBooleanField_thenErrorResponse() throws Exception { + String postBody = "{\"boolField\":true,\"trueField\":false,\"falseField\":false,\"boolStringVar\":\"+\"}"; + + String output = mockMvc.perform(post("/validateBoolean").contentType("application/json") + .content(postBody)) + .andReturn() + .getResponse() + .getContentAsString(); + + assertEquals("trueField must have true value", output); + } + + @Test + void whenInvalidInputForFalseBooleanField_thenErrorResponse() throws Exception { + String postBody = "{\"boolField\":true,\"trueField\":true,\"falseField\":true,\"boolStringVar\":\"+\"}"; + + String output = mockMvc.perform(post("/validateBoolean").contentType("application/json") + .content(postBody)) + .andReturn() + .getResponse() + .getContentAsString(); + + assertEquals("falseField must have false value", output); + } + + @Test + void whenInvalidBooleanFromJson_thenErrorResponse() throws Exception { + String postBody = "{\"boolField\":true,\"trueField\":true,\"falseField\":false,\"boolStringVar\":\"plus\"}"; + + String output = mockMvc.perform(post("/validateBoolean").contentType("application/json") + .content(postBody)) + .andReturn() + .getResponse() + .getContentAsString(); + + assertEquals("Only values accepted as Boolean are + and -", output); + } + + @Test + void whenAllBooleanFieldsValid_thenCorrectResponse() throws Exception { + String postBody = "{\"boolField\":true,\"trueField\":true,\"falseField\":false,\"boolStringVar\":\"+\"}"; + + String output = mockMvc.perform(post("/validateBoolean").contentType("application/json") + .content(postBody)) + .andReturn() + .getResponse() + .getContentAsString(); + + assertEquals("BooleanObject is valid", output); + } + + @Test + void givenAllBooleanFieldsValid_whenServiceValidationFails_thenErrorResponse() throws Exception { + mockMvc.perform(post("/validateBooleanAtService").contentType("application/json")) + .andExpect(status().isInternalServerError()); + } + + @Test + void whenNullInputForTrueBooleanField_thenCorrectResponse() throws Exception { + String postBody = "{\"boolField\":true,\"trueField\":null,\"falseField\":false,\"boolStringVar\":\"+\"}"; + + mockMvc.perform(post("/validateBoolean").contentType("application/json") + .content(postBody)) + .andExpect(status().isOk()); + } + + @Test + void whenNullInputForFalseBooleanField_thenHttpBadRequestAsHttpResponse() throws Exception { + String postBody = "{\"boolField\":true,\"trueField\":true,\"falseField\":null,\"boolStringVar\":\"+\"}"; + + String output = mockMvc.perform(post("/validateBoolean").contentType("application/json") + .content(postBody)) + .andReturn() + .getResponse() + .getContentAsString(); + + assertEquals("falseField cannot be null", output); + } +} diff --git a/spring-boot-modules/spring-boot-validations/src/test/java/com/baeldung/dto/BooleanUnitTest.java b/spring-boot-modules/spring-boot-validations/src/test/java/com/baeldung/dto/BooleanUnitTest.java new file mode 100644 index 0000000000..9ab04794c6 --- /dev/null +++ b/spring-boot-modules/spring-boot-validations/src/test/java/com/baeldung/dto/BooleanUnitTest.java @@ -0,0 +1,21 @@ +package com.baeldung.dto; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class BooleanUnitTest { + + @Test + void givenInputAsString_whenStringToBoolean_thenValidBooleanConversion() { + assertEquals(Boolean.TRUE, Boolean.valueOf("TRUE")); + assertEquals(Boolean.FALSE, Boolean.valueOf("false")); + assertEquals(Boolean.TRUE, Boolean.parseBoolean("True")); + } + + @Test + void givenInputAsboolean_whenbooleanToBoolean_thenValidBooleanConversion() { + assertEquals(Boolean.TRUE, Boolean.valueOf(true)); + assertEquals(Boolean.FALSE, Boolean.valueOf(false)); + } +}