Error Handling
Error Handling
Section titled “Error Handling”Errors flow outward through the Clean Architecture layers. Each layer converts errors from the layer below. The key invariant: internal details never reach the frontend.
Error Flow
Section titled “Error Flow”Domain Application Commands Frontend───────────── ──────────────────── ────────────────── ──────────────────DomainError → PageRepositoryError → CommandError → parseCommandError() WorkspaceRepoError (sanitized) (TypeScript) TagRepositoryError ...| Layer | Error Type(s) | Responsibility |
|---|---|---|
| Domain | DomainError | Pure validation — invalid state, constraint violations |
| Application | PageRepositoryError, TagRepoError | Repository-specific errors with technical detail |
| Commands | CommandError | User-facing, sanitized, transport-agnostic |
| Frontend | Parsed strings | Human-readable messages for toast/dialog display |
Domain Errors
Section titled “Domain Errors”DomainError (in crates/domain/src/error.rs) has two variants — InvalidState and Validation — derived via
thiserror. Application-layer From implementations convert them into repository errors:
impl From<domain::DomainError> for PageRepositoryError { fn from(err: domain::DomainError) -> Self { match err { domain::DomainError::InvalidState(msg) => PageRepositoryError::InvalidData(msg), domain::DomainError::Validation(msg) => PageRepositoryError::Validation(msg), } }}CommandError Enum
Section titled “CommandError Enum”CommandError (crates/commands/src/error.rs) is serialized as a tagged enum
(#[serde(tag = "type", content = "data")]) so all transports — Tauri IPC, HTTP bridge, MCP — produce the same
JSON shape.
| Variant | When to use |
|---|---|
NotFound | Entity lookup returned nothing |
Validation | User-correctable input error |
Internal | System or infrastructure failure |
NoWorkspace | Command requires an open workspace but none set |
Permission | Caller lacks the required capability |
Unauthenticated | User must sign in |
HTTP bridge variant-to-status mapping: NotFound → 404, Validation → 400, Internal → 500,
NoWorkspace → 409, Permission → 403, Unauthenticated → 401.
Sanitization
Section titled “Sanitization”crates/commands/src/sanitize.rs provides the bridge between technical errors and CommandError.
UserFacingError trait — every repository error type implements this:
user_message()— returns a human-readable string (never leaks paths, SQL, or Rust internals)is_validation_error()—truewhen the user can fix the input and retryis_expected()—truefor normal-flow errors (NotFound, AlreadyExists); controls log level
Sanitization functions used at command boundaries:
sanitize_error(e, "context")— always producesCommandError::Internal; logs atwarn!for expected errors,error!for unexpected onessanitize_validation_error(e, "context")— routes toCommandError::ValidationorCommandError::Internalbased onis_validation_error()sanitize_pass_through(message, fallback)— passes through safe messages; replaces messages containing paths, SQL errors, or Rust internals with the fallback string
Frontend Error Handling
Section titled “Frontend Error Handling”apps/desktop/src-react/lib/errors.ts provides two functions:
parseCommandError(err: unknown): string — extracts a human-readable message from any error shape returned
by invoke(). Handles all CommandError variants by type tag. Falls back to "An unexpected error occurred"
for unrecognized shapes.
isAuthRequiredError(err: unknown): boolean — returns true for Permission and Unauthenticated
variants; used to trigger the sign-in dialog instead of a toast.
try { await invoke("create_page", { title, parentSlug });} catch (err) { if (isAuthRequiredError(err)) { openSignInDialog(); } else { toast.error(parseCommandError(err)); }}Key Source Files
Section titled “Key Source Files”| File | Purpose |
|---|---|
crates/domain/src/error.rs | DomainError enum |
crates/commands/src/error.rs | CommandError enum + HTTP/From impls |
crates/commands/src/sanitize.rs | UserFacingError trait + sanitization functions |
crates/commands/src/validation.rs | Boundary validation (title, slug, UUID, path) |
apps/desktop/src-react/lib/errors.ts | Frontend error parsing |
Related
Section titled “Related”- Overview — Architecture layers and dependencies
- Domain Rules — Business invariants enforced in Rust
Was this page helpful?
Thanks for your feedback!