Skip to content

Commit

Permalink
substantially improve exception handling and documentation for services
Browse files Browse the repository at this point in the history
  • Loading branch information
bthreader committed Dec 5, 2023
1 parent e00ecf5 commit e6b6530
Show file tree
Hide file tree
Showing 15 changed files with 138 additions and 65 deletions.
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -10,7 +10,7 @@

@Repository
public interface BikeRepository extends JpaRepository<BikeEntity, Long>, JpaSpecificationExecutor<BikeEntity> {
@NotNull
@NonNull
@Query("SELECT b FROM BikeEntity b JOIN FETCH b.model JOIN FETCH b.groupset")
List<BikeEntity> findAll();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 <T> 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 <T> Specification<T> from(StringFilterInput input,
SingularAttribute<T, String> fieldPath) {
validate(input);
public static <T> Specification<T> from(@NonNull StringFilterInput input,
SingularAttribute<T, String> fieldPath) throws EmptyStringFilterInputException {
validate(input, fieldPath.getName());

return (root, query, cb) -> {
Expression<String> fieldExpression = root.get(fieldPath);
Expand All @@ -30,9 +34,9 @@ public static <T> Specification<T> 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);
}
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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<BikeBrand> bikeBrands();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
}
Expand All @@ -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));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Bike> 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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -43,9 +45,9 @@ private List<Bike> 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()
Expand All @@ -68,7 +70,7 @@ public Bike updateBike(UpdateBikeInput input) {
.flatMap(name -> {
Optional<GroupsetEntity> result = groupsetRespository.findById(name);
if (result.isEmpty()) {
throw Errors.newGroupsetNotFoundError();
throw new GroupsetNotFoundException(name);
}
return result;
});
Expand All @@ -89,7 +91,7 @@ public Bike updateBike(UpdateBikeInput input) {

return entity.asBike();
})
.orElseThrow(Errors::newBikeNotFoundError);
.orElseThrow(() -> new BikeNotFoundException(input.getBikeId()));
}

@Override
Expand All @@ -99,6 +101,6 @@ public Long deleteBike(String id) {
bikeRepository.deleteById(entity.getId());
return entity.getId();
})
.orElseThrow(Errors::newBikeNotFoundError);
.orElseThrow(() -> new BikeNotFoundException(id));
}
}
Original file line number Diff line number Diff line change
@@ -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");
}
}
Original file line number Diff line number Diff line change
@@ -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");
}
}
Original file line number Diff line number Diff line change
@@ -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"));
}
}
Original file line number Diff line number Diff line change
@@ -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");
}
}
Original file line number Diff line number Diff line change
@@ -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");
}
}
Original file line number Diff line number Diff line change
@@ -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");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -12,9 +14,20 @@ public interface ModelsService {
*/
List<Model> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -31,7 +32,7 @@ public List<Model> 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())
Expand All @@ -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<String, List<Model>> modelsForBikeBrands(List<String> brandNames) {
Expand Down

0 comments on commit e6b6530

Please sign in to comment.