Skip to content

errors

Canonical error contract for Drafter.

This module defines the single normalized error envelope used across the Python server lifecycle, the Python bridge runtime, and (via JSON) the TypeScript bootstrap/debug UI.

Every failure should ultimately be describable as an ErrorDetails:

  • id: stable, code-like identifier (example: request.route_not_found).
  • category: one of CATEGORIES.
  • severity: one of SEVERITIES.
  • message: technically concise, accurate message.
  • details: developer-focused free-text details.
  • data: structured, JSON-safe details (nested dicts/lists), for renderers that can display them richly. Well-known keys: route_call (the generated route call string, e.g. guess(state, 5)), route_call_exact (whether that call string is the fully-bound call or a best-effort approximation), request (the structured request description).
  • friendly_title: short page-title-style label for the failure.
  • friendly_message: plain-language explanation for novice programmers.
  • friendly_steps: concrete "what to try next" suggestions for students.
  • traceback: optional traceback/stack text.
  • context: correlation info (route, request_id, response_id, dom_id, phase).
  • status_code: symbolic, HTTP-name-style status string (one of STATUSES); the precise failure is identified by id/category.
  • recoverable: whether the application can continue after this error.

Status codes are a small, fixed set of symbolic strings with coarse HTTP-like meaning (see STATUSES):

  • ok: Successful response.
  • bad_request: The request itself was malformed (e.g. argument parsing failed).
  • not_found: No matching route was found.
  • error: A server-side failure (route execution, payload handling, bridge, system, config, or runtime error).

The fine-grained distinction between failures lives in the envelope id (for example payload.rendering_failed) and category; the status is only a coarse outcome bucket used for responses and telemetry display.

CATEGORIES = (CATEGORY_SYSTEM, CATEGORY_REQUEST, CATEGORY_PAYLOAD, CATEGORY_BRIDGE, CATEGORY_CONFIG, CATEGORY_RUNTIME) module-attribute

All valid error categories.

CATEGORY_BRIDGE = 'bridge' module-attribute

Python/JavaScript bridge failure.

CATEGORY_CONFIG = 'config' module-attribute

Configuration failure.

CATEGORY_PAYLOAD = 'payload' module-attribute

Payload rendering or handling failure.

CATEGORY_REQUEST = 'request' module-attribute

Request handling failure (routing, argument parsing).

CATEGORY_RUNTIME = 'runtime' module-attribute

Runtime failure while executing user code.

CATEGORY_SYSTEM = 'system' module-attribute

Internal system failure.

DEFAULT_TERMINAL_ERROR_TITLE = 'Drafter could not start' module-attribute

Title used for a terminal error whose exception carries none.

SEVERITIES = (SEVERITY_INFO, SEVERITY_WARNING, SEVERITY_ERROR, SEVERITY_CRITICAL) module-attribute

All valid error severities.

SEVERITY_CRITICAL = 'critical' module-attribute

Failure the application cannot recover from.

SEVERITY_ERROR = 'error' module-attribute

Failure of the current operation.

SEVERITY_INFO = 'info' module-attribute

Informational event, not a failure.

SEVERITY_WARNING = 'warning' module-attribute

Recoverable problem worth surfacing.

STATUSES = (STATUS_OK, STATUS_BAD_REQUEST, STATUS_NOT_FOUND, STATUS_ERROR) module-attribute

All valid symbolic status codes.

STATUS_BAD_REQUEST = 'bad_request' module-attribute

The request itself was malformed (e.g. argument parsing failed).

STATUS_ERROR = 'error' module-attribute

A server-side failure (route execution, payload, bridge, system, etc.).

STATUS_NOT_FOUND = 'not_found' module-attribute

No matching route was found.

STATUS_OK = 'ok' module-attribute

Successful response.

TERMINAL_WIDTH = 78 module-attribute

Column width used when printing a student-facing error to the terminal.

ErrorDetails(id, category, message, severity=SEVERITY_ERROR, details='', data=dict(), friendly_title='', friendly_message='', friendly_steps=(), traceback=None, context=Correlation(), status_code=None, recoverable=True) dataclass

Bases: Exception

A container for details about an error, warning, or other negative event.

The envelope carries two tiers of description: message (with details and traceback) is the technically concise, accurate tier, while friendly_message and friendly_steps are the student-friendly tier, normally filled in by drafter.data.error_explainer.explain. Renderers display both tiers; empty friendly fields mean "not provided" and renderers fall back to generic wording.

Attributes:

Name Type Description
id str

Stable, code-like identifier (example: request.route_not_found).

category str

One of CATEGORIES.

message str

Technically concise, accurate message.

severity str

One of SEVERITIES (default error).

details str

Developer-focused free-text details.

data dict[str, Any]

Structured, JSON-safe details (sanitized through json_safe on construction). Well-known keys: route_call, route_call_exact, request (see the module docstring).

friendly_title str

Short page-title-style label for the failure (empty when not provided).

friendly_message str

Plain-language explanation for novice programmers (empty when not provided).

friendly_steps tuple[str, ...]

Concrete "what to try next" suggestions for students (empty when not provided).

traceback str | None

Optional traceback/stack text.

context Correlation

Correlation context for the error.

status_code str | None

Symbolic status string (one of STATUSES); defaults to STATUS_ERROR.

recoverable bool

Whether the application can continue after this error.

__post_init__()

Initialize the Exception base and validate category, severity, and status.

to_json()

Converts the ErrorDetails instance to a JSON-serializable dictionary.

Returns:

Type Description
dict[str, Any]

A dictionary representation of the envelope, with the correlation context serialized via its own to_json.

StudentFacingError(message, *, friendly='', steps=(), title='')

Bases: ValueError

A ValueError that carries its own student-friendly explanation.

Drafter code that raises errors students commonly trigger (bad component arguments, invalid names, and so on) can use this class to supply the friendly tier right at the raise site. str(exception) stays the technically concise message; the friendly text rides along on the friendly_message/friendly_steps attributes, which the central error explainer (drafter.data.error_explainer.explain) picks up with the highest precedence when the exception is wrapped into an envelope.

Parameters:

Name Type Description Default
message str

Technically concise, accurate message (becomes str(self)).

required
friendly str

Plain-language explanation for novice programmers.

''
steps tuple[str, ...] | list[str]

Concrete "what to try next" suggestions.

()
title str

Short page-title-style label for the failure.

''

envelope_from_exception(exception, error_id, category, *, message=None, details='', data=None, friendly_title=None, friendly_message=None, friendly_steps=None, severity=SEVERITY_ERROR, context=None, status_code=None, recoverable=True)

Build a canonical envelope from a raised exception.

Parameters:

Name Type Description Default
exception BaseException

The exception being normalized.

required
error_id str

Stable, code-like identifier for this failure.

required
category str

One of CATEGORIES.

required
message str | None

Technically concise message; defaults to str(exception).

None
details str

Developer-focused free-text details.

''
data dict[str, Any] | None

Structured, JSON-safe details (sanitized on construction).

None
friendly_title str | None

Student-friendly title; when None, one is derived from the exception via the central error explainer.

None
friendly_message str | None

Student-friendly explanation; when None, one is derived from the exception via the central error explainer.

None
friendly_steps tuple[str, ...] | None

Student-friendly fix suggestions; when None, they are derived from the exception via the central error explainer.

None
severity str

One of SEVERITIES.

SEVERITY_ERROR
context Correlation | None

Correlation context.

None
status_code str | None

Symbolic status string; defaults to STATUS_ERROR.

None
recoverable bool

Whether the application can continue after this error.

True

Returns:

Type Description
ErrorDetails

An ErrorDetails envelope populated from the exception, with the formatted traceback attached when available. If traceback formatting itself fails, the traceback is silently omitted (set to None) rather than masking the original error.

format_student_facing_error(error)

Lay out a student-facing error for the terminal.

Used when a StudentFacingError escapes on the host (for example, the development server's port is in use, or the main file cannot be found) where there is no browser error page to show it in. The friendly explanation comes first, then the numbered steps, then the concise technical message as a final "Details" line.

Parameters:

Name Type Description Default
error StudentFacingError

The error to present.

required

Returns:

Type Description
str

The text to print, with a leading and trailing blank line.

Example
error = StudentFacingError(
    "Port 8000 is already in use.",
    title="Port already in use",
    friendly="Another program is using port 8000.",
    steps=("Stop the other program.", "Or choose another port."),
)
print(format_student_facing_error(error))

json_safe(value, _depth=0)

Convert an arbitrary value into a JSON-safe structure.

Scalars pass through; dicts/lists/tuples are converted recursively (dict keys become strings); objects offering a to_json() method are converted through it; dataclass instances become field dictionaries; anything else (or anything nested too deeply) becomes its repr string, so the result is always serializable and never raises.

Parameters:

Name Type Description Default
value Any

The value to sanitize.

required
_depth int

Internal recursion depth counter.

0

Returns:

Type Description
Any

A structure containing only None, bool, int, float, str, list, and dict values.