206 lines
7.9 KiB
Python
206 lines
7.9 KiB
Python
from __future__ import annotations
|
||
|
||
from enum import StrEnum
|
||
|
||
class AuthErrorCode(StrEnum):
|
||
"""
|
||
Stable, machine-readable error codes used across the API.
|
||
|
||
Rules:
|
||
- never rename an existing value (treat as public API)
|
||
- only add new values
|
||
"""
|
||
|
||
# Generic / defaults
|
||
AUTHENTICATION_ERROR = "AUTHENTICATION_ERROR"
|
||
AUTHENTICATION_FAILED = "AUTHENTICATION_FAILED"
|
||
FORBIDDEN = "FORBIDDEN"
|
||
HTTP_ERROR = "HTTP_ERROR"
|
||
INTERNAL_SERVER_ERROR = "INTERNAL_SERVER_ERROR"
|
||
|
||
# Auth/JWT
|
||
INVALID_TOKEN = "INVALID_TOKEN"
|
||
SESSION_ALREADY_ACTIVE = "SESSION_ALREADY_ACTIVE"
|
||
|
||
# Authorization
|
||
NOT_STAFF = "NOT_STAFF"
|
||
NOT_IN_ACTIVE_PGROUP = "NOT_IN_ACTIVE_PGROUP"
|
||
NOT_BATON_HOLDER = "NOT_BATON_HOLDER"
|
||
|
||
|
||
class DAQErrorCode(StrEnum):
|
||
BEAMLINE_BUSY = "BEAMLINE_BUSY"
|
||
MOUNTING_FAILED = "MOUNTING_FAILED"
|
||
UNMOUNTING_FAILED = "UNMOUNTING_FAILED"
|
||
LOOP_CENTERING_FAILED = "LOOP_CENTERING_FAILED"
|
||
AXC_FAILED = "AXC_FAILED"
|
||
TRANSFORMATION_INVALID = "TRANSFORMATION_INVALID"
|
||
AUTOMATION_CRITICAL = "AUTOMATION_CRITICAL"
|
||
SAMPLE_NOT_FOUND = "SAMPLE_NOT_FOUND"
|
||
DATA_COLLECTION_FAILED = "DATA_COLLECTION_FAILED"
|
||
RASTER_SCAN_FAILED = "RASTER_SCAN_FAILED"
|
||
TELL_WARNING = "TELL_WARNING"
|
||
TELL_CRITICAL = "TELL_CRITICAL"
|
||
SMARGON_UNAVAILABLE = "SMARGON_UNAVAILABLE"
|
||
TELL_UNAVAILABLE = "TELL_UNAVAILABLE"
|
||
TELL_CONNECTION_ERROR = "TELL_CONNECTION_ERROR"
|
||
TELL_MOUNT_FAILED = "TELL_MOUNT_FAILED"
|
||
TELL_BUSY = "TELL_BUSY"
|
||
MANUAL_MOUNT_REQUIRED = "MANUAL_MOUNT_REQUIRED"
|
||
SMART_MAGNET_FAULT = "SMART_MAGNET_FAULT"
|
||
JFJOCH_UNAVAILABLE = "JFJOCH_UNAVAILABLE"
|
||
AEROTECH_UNAVAILABLE = "AEROTECH_UNAVAILABLE"
|
||
AAREDB_UNAVAILABLE = "AAREDB_UNAVAILABLE"
|
||
MAGNET_POSITION_SENSOR_ERROR = "MAGNET_POSITION_SENSOR_ERROR"
|
||
|
||
|
||
_ERROR_CODE_HELP: dict[str, str] = {
|
||
# Auth/JWT
|
||
AuthErrorCode.AUTHENTICATION_ERROR: (
|
||
"Generic authentication problem. Usually means the request lacked valid credentials "
|
||
"(expired/invalid token, missing Authorization header, etc.)."
|
||
),
|
||
AuthErrorCode.AUTHENTICATION_FAILED: (
|
||
"Authentication failed during login/token creation. Typically incorrect credentials "
|
||
"or an inability to validate the user."
|
||
),
|
||
AuthErrorCode.INVALID_TOKEN: (
|
||
"The provided token could not be decoded/validated (bad signature, expired, malformed). "
|
||
"Re-authenticate to obtain a new token."
|
||
),
|
||
AuthErrorCode.SESSION_ALREADY_ACTIVE: (
|
||
"A different session currently owns control. Use “force current session” (if allowed) "
|
||
"or wait for the active session to expire/end."
|
||
),
|
||
AuthErrorCode.NOT_BATON_HOLDER: (
|
||
"A different session currently owns the baton. Request the baton or wait for active session to expire/end"),
|
||
# Authorization
|
||
AuthErrorCode.FORBIDDEN: (
|
||
"Generic permissions failure. The user is authenticated but not allowed to perform this action."
|
||
),
|
||
AuthErrorCode.NOT_STAFF: (
|
||
"This action requires staff privileges. Log in with a staff account or ask staff to perform it."
|
||
),
|
||
AuthErrorCode.NOT_IN_ACTIVE_PGROUP: (
|
||
"You are not a member of the currently active p-group. Change p-group or use an account "
|
||
"that belongs to the active group."
|
||
),
|
||
# Generic
|
||
AuthErrorCode.HTTP_ERROR: (
|
||
"Generic HTTP error wrapper. The server returned an HTTPException that wasn’t mapped to a more specific code."
|
||
),
|
||
AuthErrorCode.INTERNAL_SERVER_ERROR: (
|
||
"Unhandled server error. Check server logs for a stack trace and context."
|
||
),
|
||
DAQErrorCode.BEAMLINE_BUSY: (
|
||
"Beamline state is set to Busy by prior action. If this state persists an additional error may have occurred, "
|
||
"preventing the state from being released, this should timeout within 10 minutes."
|
||
"If this occurs please seek assistance from your local contact."
|
||
),
|
||
DAQErrorCode.MOUNTING_FAILED: (
|
||
"The requested sample could not be mounted. Check TELL/robot state, sample location, and hardware readiness."
|
||
),
|
||
DAQErrorCode.UNMOUNTING_FAILED: (
|
||
"The mounted sample could not be unmounted. Check robot state and whether the sample changer is ready."
|
||
),
|
||
DAQErrorCode.LOOP_CENTERING_FAILED: (
|
||
"Automatic loop centering failed to locate or position the sample reliably."
|
||
),
|
||
DAQErrorCode.AXC_FAILED: (
|
||
"Automatic X-ray centering failed. Verify sample visibility, alignment, and detector/beam conditions."
|
||
),
|
||
DAQErrorCode.TRANSFORMATION_INVALID: (
|
||
"The requested beamline state transition is not allowed from the current state."
|
||
),
|
||
DAQErrorCode.AUTOMATION_CRITICAL: (
|
||
"A critical failure occurred during fully automated measurement. The GUI should stop "
|
||
"dispatching further queue items and staff/operator recovery may be required."
|
||
),
|
||
DAQErrorCode.SAMPLE_NOT_FOUND: (
|
||
"The requested sample could not be found in the available sample lists."
|
||
),
|
||
DAQErrorCode.DATA_COLLECTION_FAILED: (
|
||
"Rotation/data collection failed before a valid result was produced."
|
||
),
|
||
DAQErrorCode.RASTER_SCAN_FAILED: (
|
||
"Raster scan failed before a valid result was produced."
|
||
),
|
||
DAQErrorCode.TELL_WARNING: (
|
||
"TELL reported a warning condition. The request may not have completed cleanly."
|
||
),
|
||
DAQErrorCode.TELL_CRITICAL: (
|
||
"TELL reported a critical error condition requiring operator attention."
|
||
),
|
||
DAQErrorCode.SMARGON_UNAVAILABLE: (
|
||
"Smargon communication failed or the controller is unavailable."
|
||
),
|
||
DAQErrorCode.TELL_UNAVAILABLE: (
|
||
"TELL communication failed or the service is unavailable."
|
||
),
|
||
DAQErrorCode.TELL_CONNECTION_ERROR: (
|
||
"The server could not establish or maintain a connection to TELL."
|
||
),
|
||
DAQErrorCode.TELL_MOUNT_FAILED: (
|
||
"TELL reported that the mount/unmount operation failed."
|
||
),
|
||
DAQErrorCode.TELL_BUSY: (
|
||
"A TELL command was requested while TELL was still busy processing another action."
|
||
),
|
||
DAQErrorCode.MANUAL_MOUNT_REQUIRED: (
|
||
"Automatic mounting could not proceed and manual intervention is required."
|
||
),
|
||
DAQErrorCode.SMART_MAGNET_FAULT: (
|
||
"The smart magnet system reported a fault or unsafe condition."
|
||
),
|
||
DAQErrorCode.JFJOCH_UNAVAILABLE: (
|
||
"JFJoch detector communication failed or the detector service is unavailable."
|
||
),
|
||
DAQErrorCode.AEROTECH_UNAVAILABLE: (
|
||
"Aerotech communication failed or the motion controller is unavailable."
|
||
),
|
||
DAQErrorCode.AAREDB_UNAVAILABLE: (
|
||
"AareDB communication failed or the database service is unavailable."
|
||
),
|
||
DAQErrorCode.MAGNET_POSITION_SENSOR_ERROR: (
|
||
"The magnet position sensor reported an invalid or unsafe state."
|
||
),
|
||
}
|
||
|
||
def error_code_help(code: str) -> str | None:
|
||
"""
|
||
Return a human help message for a code string, if known.
|
||
Accepts either enum value strings or raw strings.
|
||
"""
|
||
if not code:
|
||
return None
|
||
return _ERROR_CODE_HELP.get(str(code))
|
||
|
||
|
||
def export_error_code_help() -> dict[str, str]:
|
||
"""
|
||
Export help text as {"CODE": "help text", ...}
|
||
"""
|
||
return {str(k): str(v) for k, v in _ERROR_CODE_HELP.items()}
|
||
|
||
def export_error_codes_grouped() -> dict[str, dict[str, str]]:
|
||
"""
|
||
Export codes grouped by enum class name:
|
||
|
||
{
|
||
"AuthErrorCode": {"INVALID_TOKEN": "INVALID_TOKEN", ...},
|
||
"DAQErrorCode": {"BEAMLINE_BUSY": "BEAMLINE_BUSY", ...}
|
||
}
|
||
"""
|
||
enums: tuple[type[StrEnum], ...] = (AuthErrorCode, DAQErrorCode)
|
||
return {e.__name__: {c.name: str(c.value) for c in e} for e in enums}
|
||
|
||
def export_error_codes() -> dict[str, str]:
|
||
"""
|
||
Backwards-compatible, flat export used by older clients/tests/docs:
|
||
|
||
{"INVALID_TOKEN": "INVALID_TOKEN", ...}
|
||
|
||
NOTE: This intentionally exports only AuthErrorCode to avoid breaking
|
||
existing consumers that assume a flat map and/or specific keys.
|
||
"""
|
||
return {c.name: str(c.value) for c in AuthErrorCode} |