Return error when sending incomplete data to PUT API endpoint

Signed-off-by: Litchi Pi <litchi.pi@proton.me>
This commit is contained in:
Litchi Pi 2025-07-23 08:40:30 +02:00
commit e5d5c28f71
3 changed files with 27 additions and 6 deletions

View file

@ -46,3 +46,9 @@ class UserLockedOut(Exception): ...
class MissingClaimException(Exception): ... class MissingClaimException(Exception): ...
class IncompleteData(Exception):
"""
This exception is raised when a user sends incomplete data to the API
"""
pass

View file

@ -84,6 +84,9 @@ class RecipeController(BaseRecipeController):
elif thrownType == exceptions.NoEntryFound: elif thrownType == exceptions.NoEntryFound:
self.logger.error("No Entry Found on recipe controller action") self.logger.error("No Entry Found on recipe controller action")
raise HTTPException(status_code=404, detail=ErrorResponse.respond(message="No Entry Found")) raise HTTPException(status_code=404, detail=ErrorResponse.respond(message="No Entry Found"))
elif thrownType == exceptions.IncompleteData:
self.logger.error("Incomplete data provided to API route")
raise HTTPException(status_code=400, detail=ErrorResponse.respond(message="Some data were missing on the body of this API request"))
elif thrownType == sqlalchemy.exc.IntegrityError: elif thrownType == sqlalchemy.exc.IntegrityError:
self.logger.error("SQL Integrity Error on recipe controller action") self.logger.error("SQL Integrity Error on recipe controller action")
raise HTTPException(status_code=400, detail=ErrorResponse.respond(message="Recipe already exists")) raise HTTPException(status_code=400, detail=ErrorResponse.respond(message="Recipe already exists"))
@ -360,9 +363,15 @@ class RecipeController(BaseRecipeController):
@router.put("/{slug}") @router.put("/{slug}")
def update_one(self, slug: str, data: Recipe): def update_one(self, slug: str, data: Recipe):
"""Updates a recipe by existing slug and data.""" """
Updates a recipe by existing slug and data.
Requires all the fields of the recipe to be passed in the body
If you wish to update parts of the recipe only, use a PATCH request to this route
"""
try: try:
recipe = self.service.update_one(slug, data) recipe = self.service.update_one(slug, data, strict=True)
except Exception as e: except Exception as e:
self.handle_exceptions(e) self.handle_exceptions(e)
@ -387,7 +396,7 @@ class RecipeController(BaseRecipeController):
lambda: defaultdict(list) lambda: defaultdict(list)
) )
for recipe in data: for recipe in data:
r = self.service.update_one(recipe.id, recipe) # type: ignore r = self.service.update_one(recipe.id, recipe, strict=True) # type: ignore
updated_by_group_and_household[r.group_id][r.household_id].append(r) updated_by_group_and_household[r.group_id][r.household_id].append(r)
all_updated: list[Recipe] = [] all_updated: list[Recipe] = []

View file

@ -364,7 +364,7 @@ class RecipeService(RecipeServiceBase):
return new_recipe return new_recipe
def _pre_update_check(self, slug_or_id: str | UUID, new_data: Recipe) -> Recipe: def _pre_update_check(self, slug_or_id: str | UUID, new_data: Recipe, strict: bool) -> Recipe:
""" """
gets the recipe from the database and performs a check to see if the user can update the recipe. gets the recipe from the database and performs a check to see if the user can update the recipe.
If the user can't update the recipe, an exception is raised. If the user can't update the recipe, an exception is raised.
@ -377,6 +377,7 @@ class RecipeService(RecipeServiceBase):
Args: Args:
slug_or_id (str | UUID): recipe slug or id slug_or_id (str | UUID): recipe slug or id
new_data (Recipe): the new recipe data new_data (Recipe): the new recipe data
strict: if the data provided must contain ALL fields, or can be incomplete
Raises: Raises:
exceptions.PermissionDenied (403) exceptions.PermissionDenied (403)
@ -384,6 +385,11 @@ class RecipeService(RecipeServiceBase):
recipe = self.get_one(slug_or_id) recipe = self.get_one(slug_or_id)
if strict:
for field in new_data.__class__.model_fields:
if getattr(new_data, field) is None:
raise exceptions.IncompleteData("Incomplete recipe")
if recipe is None or recipe.settings is None: if recipe is None or recipe.settings is None:
raise exceptions.NoEntryFound("Recipe not found.") raise exceptions.NoEntryFound("Recipe not found.")
@ -396,8 +402,8 @@ class RecipeService(RecipeServiceBase):
return recipe return recipe
def update_one(self, slug_or_id: str | UUID, update_data: Recipe) -> Recipe: def update_one(self, slug_or_id: str | UUID, update_data: Recipe, strict: bool) -> Recipe:
recipe = self._pre_update_check(slug_or_id, update_data) recipe = self._pre_update_check(slug_or_id, update_data, strict)
new_data = self.group_recipes.update(recipe.slug, update_data) new_data = self.group_recipes.update(recipe.slug, update_data)
self.check_assets(new_data, recipe.slug) self.check_assets(new_data, recipe.slug)