Skip to content
Draft
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
21 changes: 19 additions & 2 deletions core/nurse_scheduling/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@

yaml = YAML(typ='safe')


class InputError(ValueError):
"""Raised when user-provided input fails validation (HTTP 400 candidate)."""
pass

def _load_yaml(content: bytes) -> Dict[str, Any]:
"""Load YAML from bytes content.

Expand All @@ -48,5 +53,17 @@ def load_data(content: bytes) -> NurseSchedulingData:
Returns:
NurseSchedulingData: The validated scheduling data
"""
data = _load_yaml(content)
return NurseSchedulingData(**data)
try:
data = _load_yaml(content)
except Exception as e:
raise InputError(f"Invalid YAML: {e}") from e
if not isinstance(data, dict):
raise InputError(
"Invalid YAML input: expected a mapping (key-value object) at the top level, "
f"but got {type(data).__name__}."
)
try:
return NurseSchedulingData(**data)
except TypeError as e:
# e.g. unexpected/missing keyword arguments from a malformed mapping
raise InputError(f"Invalid scheduling data: {e}") from e
8 changes: 8 additions & 0 deletions core/nurse_scheduling/serve.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from fastapi.responses import StreamingResponse
from fastapi.middleware.cors import CORSMiddleware
from . import scheduler, exporter
from .loader import InputError

import sentry_sdk
sentry_sdk.init(
Expand Down Expand Up @@ -146,6 +147,13 @@ async def optimize_and_export_xlsx(
solver=solver,
)

except InputError as e:
# Bad user input -> HTTP 400
logging.warning(f"Invalid input for optimization: {str(e)}")
raise HTTPException(
status_code=400,
detail=f"Invalid input: {str(e)}"
)
except Exception as e:
# TODO(security): Returning the error message to the client may be a security risk
logging.error(f"Error during optimization: {str(e)}")
Expand Down
Loading