Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add putUsers request validator #293

Merged
merged 2 commits into from
Jun 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.zufar.icedlatte.user.exception;

public class PutUsersBadRequestException extends RuntimeException {

public PutUsersBadRequestException(String errorMessages) {
super(String.format("PutUsersRequest parameters are incorrect. Error messages are [ %s ].", errorMessages));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.zufar.icedlatte.common.exception.handler.ErrorDebugMessageCreator;
import com.zufar.icedlatte.common.exception.dto.ApiErrorResponse;
import com.zufar.icedlatte.user.exception.InvalidOldPasswordException;
import com.zufar.icedlatte.user.exception.PutUsersBadRequestException;
import com.zufar.icedlatte.user.exception.UserNotFoundException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -49,6 +50,14 @@ public ApiErrorResponse handleInvalidOldPasswordException(final InvalidOldPasswo
return apiErrorResponse;
}

@ExceptionHandler({PutUsersBadRequestException.class})
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ApiErrorResponse handlePutUsersBadRequestException(final PutUsersBadRequestException exception) {
ApiErrorResponse apiErrorResponse = apiErrorResponseCreator.buildResponse(exception, HttpStatus.BAD_REQUEST);
log.warn("Handle user's invalid property exception: failed: message: {}, debugMessage: {}",
apiErrorResponse.message(), errorDebugMessageCreator.buildErrorDebugMessage(exception));
return apiErrorResponse;

@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ApiErrorResponse handleValidationExceptions(MethodArgumentNotValidException ex) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package com.zufar.icedlatte.user.validator;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.zufar.icedlatte.openapi.dto.AddressDto;
import com.zufar.icedlatte.user.exception.PutUsersBadRequestException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.lang.reflect.Field;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class PutUsersRequestValidator {

private final int MIN_LENGTH = 2;
private final int MAX_LENGTH = 128;

public void validate(String firstName,
String lastName,
String phoneNumber,
String birthDate,
JsonObject addressDto) {
StringBuilder errorMessages = new StringBuilder();

StringBuilder firstNameParameterMessages = validateNameParameter(firstName, "First name");
errorMessages.append(firstNameParameterMessages);

StringBuilder secondNameParameterMessages = validateNameParameter(lastName, "Last name");
errorMessages.append(secondNameParameterMessages);

StringBuilder phoneNumberParameterMessages = validatePhoneParameter(phoneNumber);
errorMessages.append(phoneNumberParameterMessages);
Sunagatov marked this conversation as resolved.
Show resolved Hide resolved

StringBuilder birthDateParameterMessages = validateBirthDateParameter(birthDate);
errorMessages.append(birthDateParameterMessages);

StringBuilder addressJSONParameterMessages = validateAddressJSONParameter(addressDto);
errorMessages.append(addressJSONParameterMessages);

if (!errorMessages.isEmpty()) {
throw new PutUsersBadRequestException(errorMessages.toString());
}
}

private StringBuilder validateNameParameter(String name, String parameterTypeForErrorMessage) {
StringBuilder errorMessages = new StringBuilder();
if (name == null) {
String errorMessage = String.format("%s is the mandatory attribute.", parameterTypeForErrorMessage);
errorMessages.append(createErrorMessage(errorMessage));
} else if (name.length() < MIN_LENGTH || name.length() > MAX_LENGTH) {
String errorMessage = String.format("%s should have a length between %d and %d characters.", parameterTypeForErrorMessage, MIN_LENGTH, MAX_LENGTH);
errorMessages.append(createErrorMessage(errorMessage));
}
return errorMessages;
}

private StringBuilder validatePhoneParameter(String phoneNumber) {
StringBuilder errorMessages = new StringBuilder();
if (phoneNumber != null) {
String phoneTemplateRegexp = "^\\+?[1-9]\\d{1,14}$";
if (!phoneNumber.matches(phoneTemplateRegexp)) {
String errorMessage = "Phone should contain only digits. The first symbol is allowed to be \"+\".";
errorMessages.append(createErrorMessage(errorMessage));
}
}
return errorMessages;
}

private StringBuilder validateBirthDateParameter(String birthDate) {
StringBuilder errorMessages = new StringBuilder();
if (birthDate != null) {
try {
DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE;
LocalDate localDate = LocalDate.parse(birthDate, formatter);
} catch (DateTimeParseException exception) {
errorMessages.append(createErrorMessage("Birth date should be in format YYYY-MM-DD."));
}
}
return errorMessages;
}

private StringBuilder validateAddressJSONParameter(JsonObject addressJsonObject) {
StringBuilder errorMessages = new StringBuilder();
if (addressJsonObject != null) {
List<Field> allFields = List.of(AddressDto.class.getDeclaredFields());
List<String> allFieldNames = allFields.stream()
.map(field -> getFieldNameFromDeclaredField(field.getName()))
.collect(Collectors.toList());

Set<Entry<String, JsonElement>> entries = addressJsonObject.entrySet();
for (Entry<String, JsonElement> entry : entries) {
if (!allFieldNames.contains(entry.getKey())) {
String errorMessage = String.format("The field `%s` in the JSON string is not defined in the `AddressDto` properties. JSON: %s", entry.getKey(), addressJsonObject.toString());
errorMessages.append(createErrorMessage(errorMessage));
}
}
// all fields are required and primitive
for (String name : allFieldNames) {
JsonElement jsonElement = addressJsonObject.get(name);
if (jsonElement == null || jsonElement.isJsonNull()) {
String errorMessage = String.format("The required field `%s` is not found in the JSON string: %s", name, addressJsonObject.toString());
errorMessages.append(createErrorMessage(errorMessage));
} else if (!jsonElement.isJsonPrimitive()) {
String errorMessage = String.format("Expected the field `%s` to be a primitive type in the JSON string but got `%s`", name, jsonElement.toString());
errorMessages.append(createErrorMessage(errorMessage));
}
}
}
return errorMessages;
}

private String createErrorMessage(String errorMessage) {
return String.format(" Error: { %s }. ", errorMessage);
}

private String getFieldNameFromDeclaredField(String declaredFieldName) {
return declaredFieldName.substring(declaredFieldName.lastIndexOf(".") + 1);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.zufar.icedlatte.user.validate;

import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.zufar.icedlatte.user.exception.PutUsersBadRequestException;
import com.zufar.icedlatte.user.validator.PutUsersRequestValidator;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;


public class PutUsersRequestValidatorTest {

private PutUsersRequestValidator validator;

@BeforeEach
void setUp() {
validator = new PutUsersRequestValidator();
}

@Test
void shouldNotThrowExceptionWhenAllParametersAreCorrect() {
String firstName = "name";
String lastName = "surname";
String phoneNumber = "+7900000000";
String birthDate = "2000-12-01";
String addressJSONAsString = "{\n" +
"\"country\": \"Country\",\n" +
"\"city\": \"City\",\n" +
"\"line\": \"Line\",\n" +
"\"postcode\": \"00000\"\n" +
"}";
JsonObject addressJSON = new JsonParser().parse(addressJSONAsString).getAsJsonObject();

assertDoesNotThrow(() -> validator.validate(firstName, lastName, phoneNumber, birthDate, addressJSON));
}

@Test
void shouldNotThrowExceptionWhenHasOnlyRequiredParameters() {
String firstName = "name";
String lastName = "surname";

assertDoesNotThrow(() -> validator.validate(firstName, lastName, null, null, null));
}

@Test
void shouldThrowPutUserBadRequestExceptionWhenParametersAreIncorrect() {
String firstName = null;
String lastName = "s";
String phoneNumber = "+7900000000b";
String birthDate = "2000-12-011";
String addressJSONAsString = "{\n" +
"\"country\": [\"Country\", \"Another country\"],\n" +
"\"town\": \"City\",\n" +
"\"postcode\": \"00000\"\n" +
"}";
JsonObject addressJSON = new JsonParser().parse(addressJSONAsString).getAsJsonObject();
String expectedMessage = String.format("PutUsersRequest parameters are incorrect. Error messages are [ " +
" Error: { First name is the mandatory attribute. }. " +
" Error: { Last name should have a length between 2 and 128 characters. }. " +
" Error: { Phone should contain only digits. The first symbol is allowed to be \"+\". }. " +
" Error: { Birth date should be in format YYYY-MM-DD. }. " +
" Error: { The field `town` in the JSON string is not defined in the `AddressDto` properties. JSON: %s }. " +
" Error: { Expected the field `country` to be a primitive type in the JSON string but got `[\"Country\",\"Another country\"]` }. " +
" Error: { The required field `city` is not found in the JSON string: %s }. " +
" Error: { The required field `line` is not found in the JSON string: %s }. " +
" ].", addressJSON.toString(), addressJSON.toString(), addressJSON.toString());

PutUsersBadRequestException thrownException = assertThrows(PutUsersBadRequestException.class, () -> validator.validate(firstName, lastName, phoneNumber, birthDate, addressJSON));


assertEquals(expectedMessage, thrownException.getMessage());
}
}
Loading