mirror of
https://github.com/spring-projects/spring-petclinic.git
synced 2025-12-27 19:07:28 +00:00
refactor: reduce PetController complexity by extracting validation logic
Signed-off-by: Mickael-7 <mickael.albuquerque@upe.br>
This commit is contained in:
parent
3e1ce239f4
commit
9b820120ce
3 changed files with 133 additions and 26 deletions
|
|
@ -15,15 +15,12 @@
|
|||
*/
|
||||
package org.springframework.samples.petclinic.owner;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.ModelMap;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
|
@ -53,9 +50,12 @@ class PetController {
|
|||
|
||||
private final PetTypeRepository types;
|
||||
|
||||
public PetController(OwnerRepository owners, PetTypeRepository types) {
|
||||
private final PetValidationService petValidationService;
|
||||
|
||||
public PetController(OwnerRepository owners, PetTypeRepository types, PetValidationService petValidationService) {
|
||||
this.owners = owners;
|
||||
this.types = types;
|
||||
this.petValidationService = petValidationService;
|
||||
}
|
||||
|
||||
@ModelAttribute("types")
|
||||
|
|
@ -106,13 +106,7 @@ class PetController {
|
|||
public String processCreationForm(Owner owner, @Valid Pet pet, BindingResult result,
|
||||
RedirectAttributes redirectAttributes) {
|
||||
|
||||
if (StringUtils.hasText(pet.getName()) && pet.isNew() && owner.getPet(pet.getName(), true) != null)
|
||||
result.rejectValue("name", "duplicate", "already exists");
|
||||
|
||||
LocalDate currentDate = LocalDate.now();
|
||||
if (pet.getBirthDate() != null && pet.getBirthDate().isAfter(currentDate)) {
|
||||
result.rejectValue("birthDate", "typeMismatch.birthDate");
|
||||
}
|
||||
this.petValidationService.validateForCreation(pet, owner, result);
|
||||
|
||||
if (result.hasErrors()) {
|
||||
return VIEWS_PETS_CREATE_OR_UPDATE_FORM;
|
||||
|
|
@ -133,20 +127,8 @@ class PetController {
|
|||
public String processUpdateForm(Owner owner, @Valid Pet pet, BindingResult result,
|
||||
RedirectAttributes redirectAttributes) {
|
||||
|
||||
String petName = pet.getName();
|
||||
|
||||
// checking if the pet name already exists for the owner
|
||||
if (StringUtils.hasText(petName)) {
|
||||
Pet existingPet = owner.getPet(petName, false);
|
||||
if (existingPet != null && !Objects.equals(existingPet.getId(), pet.getId())) {
|
||||
result.rejectValue("name", "duplicate", "already exists");
|
||||
}
|
||||
}
|
||||
|
||||
LocalDate currentDate = LocalDate.now();
|
||||
if (pet.getBirthDate() != null && pet.getBirthDate().isAfter(currentDate)) {
|
||||
result.rejectValue("birthDate", "typeMismatch.birthDate");
|
||||
}
|
||||
// Delegated validation to service - CCN reduced from 7 to 3
|
||||
this.petValidationService.validateForUpdate(pet, owner, result);
|
||||
|
||||
if (result.hasErrors()) {
|
||||
return VIEWS_PETS_CREATE_OR_UPDATE_FORM;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.samples.petclinic.owner;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.Errors;
|
||||
|
||||
/**
|
||||
* Service responsible for Pet validation logic. Extracts complex validation rules from
|
||||
* the controller layer. Follows Single Responsibility Principle (SRP) from SOLID.
|
||||
*
|
||||
* @author Nathan Dalbert
|
||||
* @author Paulo Henrique
|
||||
* @author Mickael de Albuquerque
|
||||
* @author Igor Rego
|
||||
*/
|
||||
@Service
|
||||
public class PetValidationService {
|
||||
|
||||
/**
|
||||
* Validates pet name for duplicates during creation.
|
||||
* @param pet the pet being created
|
||||
* @param owner the owner of the pet
|
||||
* @param errors validation errors container
|
||||
* @return true if validation passes, false otherwise
|
||||
*/
|
||||
public boolean validatePetNameForCreation(Pet pet, Owner owner, Errors errors) {
|
||||
if (!StringUtils.hasText(pet.getName())) {
|
||||
return true; // Let @Valid handle blank names
|
||||
}
|
||||
|
||||
if (pet.isNew() && owner.getPet(pet.getName(), true) != null) {
|
||||
errors.rejectValue("name", "duplicate", "already exists");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates pet name for duplicates during update.
|
||||
* @param pet the pet being updated
|
||||
* @param owner the owner of the pet
|
||||
* @param errors validation errors container
|
||||
* @return true if validation passes, false otherwise
|
||||
*/
|
||||
public boolean validatePetNameForUpdate(Pet pet, Owner owner, Errors errors) {
|
||||
if (!StringUtils.hasText(pet.getName())) {
|
||||
return true; // Let @Valid handle blank names
|
||||
}
|
||||
|
||||
Pet existingPet = owner.getPet(pet.getName(), false);
|
||||
if (existingPet != null && !Objects.equals(existingPet.getId(), pet.getId())) {
|
||||
errors.rejectValue("name", "duplicate", "already exists");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that birth date is not in the future.
|
||||
* @param pet the pet being validated
|
||||
* @param errors validation errors container
|
||||
* @return true if validation passes, false otherwise
|
||||
*/
|
||||
public boolean validateBirthDate(Pet pet, Errors errors) {
|
||||
if (pet.getBirthDate() == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
LocalDate currentDate = LocalDate.now();
|
||||
if (pet.getBirthDate().isAfter(currentDate)) {
|
||||
errors.rejectValue("birthDate", "typeMismatch.birthDate");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs all validations for pet creation.
|
||||
* @param pet the pet being created
|
||||
* @param owner the owner of the pet
|
||||
* @param errors validation errors container
|
||||
* @return true if all validations pass, false otherwise
|
||||
*/
|
||||
public boolean validateForCreation(Pet pet, Owner owner, Errors errors) {
|
||||
boolean isNameValid = validatePetNameForCreation(pet, owner, errors);
|
||||
boolean isBirthDateValid = validateBirthDate(pet, errors);
|
||||
return isNameValid && isBirthDateValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs all validations for pet update.
|
||||
* @param pet the pet being updated
|
||||
* @param owner the owner of the pet
|
||||
* @param errors validation errors container
|
||||
* @return true if all validations pass, false otherwise
|
||||
*/
|
||||
public boolean validateForUpdate(Pet pet, Owner owner, Errors errors) {
|
||||
boolean isNameValid = validatePetNameForUpdate(pet, owner, errors);
|
||||
boolean isBirthDateValid = validateBirthDate(pet, errors);
|
||||
return isNameValid && isBirthDateValid;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -46,7 +46,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
|||
* @author Wick Dynex
|
||||
*/
|
||||
@WebMvcTest(value = PetController.class,
|
||||
includeFilters = @ComponentScan.Filter(value = PetTypeFormatter.class, type = FilterType.ASSIGNABLE_TYPE))
|
||||
includeFilters = { @ComponentScan.Filter(value = PetTypeFormatter.class, type = FilterType.ASSIGNABLE_TYPE),
|
||||
@ComponentScan.Filter(value = PetValidationService.class, type = FilterType.ASSIGNABLE_TYPE) })
|
||||
@DisabledInNativeImage
|
||||
@DisabledInAotMode
|
||||
class PetControllerTests {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue