116 lines
4.1 KiB
Python
116 lines
4.1 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"
|
||
|
||
_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."
|
||
)
|
||
}
|
||
|
||
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} |