diff --git a/service-course-api/src/main/java/servicecourse/repo/BikeRepository.java b/service-course-api/src/main/java/servicecourse/repo/BikeRepository.java index d5ca6da..f20ac32 100644 --- a/service-course-api/src/main/java/servicecourse/repo/BikeRepository.java +++ b/service-course-api/src/main/java/servicecourse/repo/BikeRepository.java @@ -1,6 +1,6 @@ package servicecourse.repo; -import org.jetbrains.annotations.NotNull; +import lombok.NonNull; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Query; @@ -10,7 +10,7 @@ @Repository public interface BikeRepository extends JpaRepository, JpaSpecificationExecutor { - @NotNull + @NonNull @Query("SELECT b FROM BikeEntity b JOIN FETCH b.model JOIN FETCH b.groupset") List findAll(); } diff --git a/service-course-api/src/main/java/servicecourse/repo/common/StringFilterSpecification.java b/service-course-api/src/main/java/servicecourse/repo/common/StringFilterSpecification.java index 7f6f7eb..1fd7548 100644 --- a/service-course-api/src/main/java/servicecourse/repo/common/StringFilterSpecification.java +++ b/service-course-api/src/main/java/servicecourse/repo/common/StringFilterSpecification.java @@ -2,22 +2,26 @@ import jakarta.persistence.criteria.Expression; import jakarta.persistence.metamodel.SingularAttribute; +import lombok.NonNull; import org.springframework.data.jpa.domain.Specification; import servicecourse.generated.types.StringFilterInput; -import servicecourse.services.Errors; +import servicecourse.services.exceptions.EmptyStringFilterInputException; import java.util.List; import java.util.Optional; public class StringFilterSpecification { /** - * @param input the gql input - * @param fieldPath the path from the root (of type {@code T}) to the {@literal String} + * @param input the details of the filter to apply to the field + * @param fieldPath the path from the root entity (of type {@code T}) to the {@literal String} * attribute to apply the filter to + * @param the entity for which {@code fieldPath} is an attribute + * @return a specification ready to apply on the entity of type {@code T} + * @throws EmptyStringFilterInputException if the input is empty */ - public static Specification from(StringFilterInput input, - SingularAttribute fieldPath) { - validate(input); + public static Specification from(@NonNull StringFilterInput input, + SingularAttribute fieldPath) throws EmptyStringFilterInputException { + validate(input, fieldPath.getName()); return (root, query, cb) -> { Expression fieldExpression = root.get(fieldPath); @@ -30,9 +34,9 @@ public static Specification from(StringFilterInput input, }; } - private static void validate(StringFilterInput input) { + private static void validate(StringFilterInput input, String fieldPath) { if (input.getContains() == null && input.getIn() == null && input.getEquals() == null) { - throw Errors.emptyStringFilterInput(); + throw new EmptyStringFilterInputException(fieldPath); } } diff --git a/service-course-api/src/main/java/servicecourse/services/Errors.java b/service-course-api/src/main/java/servicecourse/services/Errors.java deleted file mode 100644 index 1f719b0..0000000 --- a/service-course-api/src/main/java/servicecourse/services/Errors.java +++ /dev/null @@ -1,31 +0,0 @@ -package servicecourse.services; - -import java.util.NoSuchElementException; - -public class Errors { - public static RuntimeException newModelNotFoundError() { - return new NoSuchElementException("Model not found"); - } - - public static RuntimeException newGroupsetNotFoundError() { - return new NoSuchElementException("Groupset not found"); - } - - public static RuntimeException newBikeNotFoundError() { - return new NoSuchElementException("Bike not found"); - } - - public static RuntimeException newBikeBrandNotFoundError() { - return new NoSuchElementException("Bike brand not found"); - } - - public static RuntimeException newBikeBrandAlreadyExistsError() { - return new IllegalArgumentException("Bike brand already exists"); - } - - /** No fields specified on StringFilterInput, must specify at least one */ - public static RuntimeException emptyStringFilterInput() { - return new IllegalStateException( - "No fields specified on StringFilterInput, must specify at least one"); - } -} diff --git a/service-course-api/src/main/java/servicecourse/services/bikebrands/BikeBrandsService.java b/service-course-api/src/main/java/servicecourse/services/bikebrands/BikeBrandsService.java index 61f9716..aede690 100644 --- a/service-course-api/src/main/java/servicecourse/services/bikebrands/BikeBrandsService.java +++ b/service-course-api/src/main/java/servicecourse/services/bikebrands/BikeBrandsService.java @@ -2,15 +2,27 @@ import servicecourse.generated.types.BikeBrand; import servicecourse.generated.types.CreateBikeBrandInput; +import servicecourse.services.exceptions.BikeBrandAlreadyExistsException; +import servicecourse.services.exceptions.BikeBrandNotFoundException; import java.util.List; public interface BikeBrandsService { - /** @return the newly persisted bike brand */ - BikeBrand createBikeBrand(CreateBikeBrandInput input); + /** + * @param input the details of the new bike brand + * @return the newly persisted bike brand, throws otherwise + * @throws BikeBrandAlreadyExistsException if the bike brand name specified in {@code input} + * already exists + */ + BikeBrand createBikeBrand(CreateBikeBrandInput input) throws BikeBrandAlreadyExistsException; - /** @return the name of the deleted bike brand if it existed, throws otherwise */ - String deleteBikeBrand(String name); + /** + * @param name the name of the bike brand to delete + * @return the name of the deleted bike brand if it existed, throws otherwise + * @throws BikeBrandNotFoundException if a bike brand with the provided {@code name} doesn't + * exist + */ + String deleteBikeBrand(String name) throws BikeBrandNotFoundException; /** @return all bike brands */ List bikeBrands(); diff --git a/service-course-api/src/main/java/servicecourse/services/bikebrands/BikeBrandsServiceImpl.java b/service-course-api/src/main/java/servicecourse/services/bikebrands/BikeBrandsServiceImpl.java index 0062812..d15a2cf 100644 --- a/service-course-api/src/main/java/servicecourse/services/bikebrands/BikeBrandsServiceImpl.java +++ b/service-course-api/src/main/java/servicecourse/services/bikebrands/BikeBrandsServiceImpl.java @@ -9,7 +9,8 @@ import servicecourse.generated.types.CreateBikeBrandInput; import servicecourse.repo.BikeBrandEntity; import servicecourse.repo.BikeBrandRepository; -import servicecourse.services.Errors; +import servicecourse.services.exceptions.BikeBrandAlreadyExistsException; +import servicecourse.services.exceptions.BikeBrandNotFoundException; import java.util.List; import java.util.stream.Collectors; @@ -24,7 +25,9 @@ public class BikeBrandsServiceImpl implements BikeBrandsService { public BikeBrand createBikeBrand(CreateBikeBrandInput input) { // Validate that the brand doesn't already exist bikeBrandRepository.findById(input.getName()) - .ifPresent((entity) -> { throw Errors.newBikeBrandAlreadyExistsError(); }); + .ifPresent((entity) -> { + throw new BikeBrandAlreadyExistsException(input.getName()); + }); return bikeBrandRepository.save(BikeBrandEntity.ofName(input.getName())).asBikeBrand(); } @@ -35,7 +38,7 @@ public String deleteBikeBrand(String name) { return bikeBrandRepository.findById(name).map((entity) -> { bikeBrandRepository.deleteById(name); return name; - }).orElseThrow(Errors::newBikeBrandNotFoundError); + }).orElseThrow(() -> new BikeBrandNotFoundException(name)); } /** diff --git a/service-course-api/src/main/java/servicecourse/services/bikes/BikesService.java b/service-course-api/src/main/java/servicecourse/services/bikes/BikesService.java index f03e580..4809451 100644 --- a/service-course-api/src/main/java/servicecourse/services/bikes/BikesService.java +++ b/service-course-api/src/main/java/servicecourse/services/bikes/BikesService.java @@ -4,18 +4,36 @@ import servicecourse.generated.types.BikesFilterInput; import servicecourse.generated.types.CreateBikeInput; import servicecourse.generated.types.UpdateBikeInput; +import servicecourse.services.exceptions.BikeNotFoundException; +import servicecourse.services.exceptions.GroupsetNotFoundException; +import servicecourse.services.exceptions.ModelNotFoundException; import java.util.List; public interface BikesService { List bikes(BikesFilterInput filter); - /** @return the newly persisted bike if successful, throws otherwise */ - Bike createBike(CreateBikeInput input); + /** + * @param input the details of the new bike + * @return the newly persisted bike if successful, throws otherwise + * @throws ModelNotFoundException if the model specified in {@code input} doesn't exist + * @throws GroupsetNotFoundException if the groupset specified in {@code input} doesn't exist + */ + Bike createBike(CreateBikeInput input) throws ModelNotFoundException, GroupsetNotFoundException; - /** @return the updated bike if successful, throws otherwise */ - Bike updateBike(UpdateBikeInput input); + /** + * @param input the details of an update to a bike + * @return the updated bike if successful, throws otherwise + * @throws BikeNotFoundException if the bike ID provided in {@code input} doesn't exist + * @throws GroupsetNotFoundException if a groupset is specified in {@code input} but it doesn't + * exist + */ + Bike updateBike(UpdateBikeInput input) throws BikeNotFoundException, GroupsetNotFoundException; - /** @return the ID of the deleted bike if it existed, throws otherwise */ - Long deleteBike(String id); + /** + * @param id the ID of the bike to delete + * @return the ID of the deleted bike if it existed, throws otherwise + * @throws BikeNotFoundException if a bike with the provided {@code id} doesn't exist + */ + Long deleteBike(String id) throws BikeNotFoundException; } diff --git a/service-course-api/src/main/java/servicecourse/services/bikes/BikesServiceImpl.java b/service-course-api/src/main/java/servicecourse/services/bikes/BikesServiceImpl.java index 05d6e1f..77fc596 100644 --- a/service-course-api/src/main/java/servicecourse/services/bikes/BikesServiceImpl.java +++ b/service-course-api/src/main/java/servicecourse/services/bikes/BikesServiceImpl.java @@ -7,7 +7,9 @@ import servicecourse.generated.types.CreateBikeInput; import servicecourse.generated.types.UpdateBikeInput; import servicecourse.repo.*; -import servicecourse.services.Errors; +import servicecourse.services.exceptions.BikeNotFoundException; +import servicecourse.services.exceptions.GroupsetNotFoundException; +import servicecourse.services.exceptions.ModelNotFoundException; import servicecourse.services.models.ModelId; import java.util.List; @@ -43,9 +45,9 @@ private List bikes() { @Override public Bike createBike(CreateBikeInput input) { ModelEntity modelEntity = modelRepository.findById(ModelId.deserialize(input.getModelId())) - .orElseThrow(Errors::newModelNotFoundError); + .orElseThrow(() -> new ModelNotFoundException(input.getModelId())); GroupsetEntity groupsetEntity = groupsetRespository.findById(input.getGroupsetName()) - .orElseThrow(Errors::newGroupsetNotFoundError); + .orElseThrow(() -> new GroupsetNotFoundException(input.getGroupsetName())); BikeEntity newBike = new BikeEntity(); newBike.apply(CreateBikeParams.builder() @@ -68,7 +70,7 @@ public Bike updateBike(UpdateBikeInput input) { .flatMap(name -> { Optional result = groupsetRespository.findById(name); if (result.isEmpty()) { - throw Errors.newGroupsetNotFoundError(); + throw new GroupsetNotFoundException(name); } return result; }); @@ -89,7 +91,7 @@ public Bike updateBike(UpdateBikeInput input) { return entity.asBike(); }) - .orElseThrow(Errors::newBikeNotFoundError); + .orElseThrow(() -> new BikeNotFoundException(input.getBikeId())); } @Override @@ -99,6 +101,6 @@ public Long deleteBike(String id) { bikeRepository.deleteById(entity.getId()); return entity.getId(); }) - .orElseThrow(Errors::newBikeNotFoundError); + .orElseThrow(() -> new BikeNotFoundException(id)); } } diff --git a/service-course-api/src/main/java/servicecourse/services/exceptions/BikeBrandAlreadyExistsException.java b/service-course-api/src/main/java/servicecourse/services/exceptions/BikeBrandAlreadyExistsException.java new file mode 100644 index 0000000..cd66e24 --- /dev/null +++ b/service-course-api/src/main/java/servicecourse/services/exceptions/BikeBrandAlreadyExistsException.java @@ -0,0 +1,7 @@ +package servicecourse.services.exceptions; + +public class BikeBrandAlreadyExistsException extends IllegalArgumentException { + public BikeBrandAlreadyExistsException(String brandName) { + super("Bike brand with name '" + brandName + "' already exists"); + } +} diff --git a/service-course-api/src/main/java/servicecourse/services/exceptions/BikeBrandNotFoundException.java b/service-course-api/src/main/java/servicecourse/services/exceptions/BikeBrandNotFoundException.java new file mode 100644 index 0000000..1226d26 --- /dev/null +++ b/service-course-api/src/main/java/servicecourse/services/exceptions/BikeBrandNotFoundException.java @@ -0,0 +1,9 @@ +package servicecourse.services.exceptions; + +import java.util.NoSuchElementException; + +public class BikeBrandNotFoundException extends NoSuchElementException { + public BikeBrandNotFoundException(String brandName) { + super("Bike brand with name '" + brandName + "' not found"); + } +} diff --git a/service-course-api/src/main/java/servicecourse/services/exceptions/BikeNotFoundException.java b/service-course-api/src/main/java/servicecourse/services/exceptions/BikeNotFoundException.java new file mode 100644 index 0000000..c7d27c2 --- /dev/null +++ b/service-course-api/src/main/java/servicecourse/services/exceptions/BikeNotFoundException.java @@ -0,0 +1,9 @@ +package servicecourse.services.exceptions; + +import java.util.NoSuchElementException; + +public class BikeNotFoundException extends NoSuchElementException { + public BikeNotFoundException(String bikeId) { + super(("Bike with ID '" + bikeId + "' not found")); + } +} diff --git a/service-course-api/src/main/java/servicecourse/services/exceptions/EmptyStringFilterInputException.java b/service-course-api/src/main/java/servicecourse/services/exceptions/EmptyStringFilterInputException.java new file mode 100644 index 0000000..23aff90 --- /dev/null +++ b/service-course-api/src/main/java/servicecourse/services/exceptions/EmptyStringFilterInputException.java @@ -0,0 +1,8 @@ +package servicecourse.services.exceptions; + +public class EmptyStringFilterInputException extends IllegalArgumentException { + public EmptyStringFilterInputException(String fieldPath) { + super("No fields specified on the StringFilterInput for path '" + fieldPath + + "', please specify at least one"); + } +} diff --git a/service-course-api/src/main/java/servicecourse/services/exceptions/GroupsetNotFoundException.java b/service-course-api/src/main/java/servicecourse/services/exceptions/GroupsetNotFoundException.java new file mode 100644 index 0000000..2ea3969 --- /dev/null +++ b/service-course-api/src/main/java/servicecourse/services/exceptions/GroupsetNotFoundException.java @@ -0,0 +1,9 @@ +package servicecourse.services.exceptions; + +import java.util.NoSuchElementException; + +public class GroupsetNotFoundException extends NoSuchElementException { + public GroupsetNotFoundException(String groupsetName) { + super("Groupset with name '" + groupsetName + "' not found"); + } +} diff --git a/service-course-api/src/main/java/servicecourse/services/exceptions/ModelNotFoundException.java b/service-course-api/src/main/java/servicecourse/services/exceptions/ModelNotFoundException.java new file mode 100644 index 0000000..32ddb58 --- /dev/null +++ b/service-course-api/src/main/java/servicecourse/services/exceptions/ModelNotFoundException.java @@ -0,0 +1,9 @@ +package servicecourse.services.exceptions; + +import java.util.NoSuchElementException; + +public class ModelNotFoundException extends NoSuchElementException { + public ModelNotFoundException(String modelId) { + super("Model with ID '" + modelId + "' not found"); + } +} diff --git a/service-course-api/src/main/java/servicecourse/services/models/ModelsService.java b/service-course-api/src/main/java/servicecourse/services/models/ModelsService.java index e3bc449..7203f33 100644 --- a/service-course-api/src/main/java/servicecourse/services/models/ModelsService.java +++ b/service-course-api/src/main/java/servicecourse/services/models/ModelsService.java @@ -2,6 +2,8 @@ import servicecourse.generated.types.CreateModelInput; import servicecourse.generated.types.Model; +import servicecourse.services.exceptions.BikeBrandNotFoundException; +import servicecourse.services.exceptions.ModelNotFoundException; import java.util.List; import java.util.Map; @@ -12,9 +14,20 @@ public interface ModelsService { */ List findByBrandName(String brandName); - Model createModel(CreateModelInput input); + /** + * @param input the details of the new model + * @return the newly persisted model if successful, throws otherwise + * @throws BikeBrandNotFoundException if the bike brand specified in {@code input} doesn't + * exist + */ + Model createModel(CreateModelInput input) throws BikeBrandNotFoundException; - String deleteModel(String id); + /** + * @param id the ID of the model to delete + * @return the ID of the deleted model if it existed, throws otherwise + * @throws ModelNotFoundException if a model with the provided {@code id} doesn't exist + */ + String deleteModel(String id) throws ModelNotFoundException; /** * @return a map; bike brand name -> models for that brand diff --git a/service-course-api/src/main/java/servicecourse/services/models/ModelsServiceImpl.java b/service-course-api/src/main/java/servicecourse/services/models/ModelsServiceImpl.java index 2a8d261..4e5973f 100644 --- a/service-course-api/src/main/java/servicecourse/services/models/ModelsServiceImpl.java +++ b/service-course-api/src/main/java/servicecourse/services/models/ModelsServiceImpl.java @@ -7,7 +7,8 @@ import servicecourse.repo.BikeBrandRepository; import servicecourse.repo.ModelEntity; import servicecourse.repo.ModelRepository; -import servicecourse.services.Errors; +import servicecourse.services.exceptions.BikeBrandNotFoundException; +import servicecourse.services.exceptions.ModelNotFoundException; import java.util.List; import java.util.Map; @@ -31,7 +32,7 @@ public List findByBrandName(String brandName) { public Model createModel(CreateModelInput input) { // Validate the brand already exists bikeBrandRepository.findById(input.getBrandName()) - .orElseThrow(Errors::newBikeBrandNotFoundError); + .orElseThrow(() -> new BikeBrandNotFoundException(input.getBrandName())); ModelEntity newModel = ModelEntity.builder() .name(input.getName()) @@ -49,7 +50,7 @@ public String deleteModel(String id) { modelRepository.deleteById(entity.getId()); return ModelId.serialize(entity.getId()); }) - .orElseThrow(Errors::newModelNotFoundError); + .orElseThrow(() -> new ModelNotFoundException(id)); } public Map> modelsForBikeBrands(List brandNames) {