Error Handling
Error handling patterns and best practices in DesQTA
Error Handling Overview
DesQTA implements comprehensive error handling at multiple levels to ensure a robust user experience and proper error recovery.
Error Handling Strategy
Error Layers
User Interface
│
▼
Component Error Boundary
│
▼
Service Error Handling
│
▼
Tauri Command Error Handling
│
▼
Rust Error Handling
Frontend Error Handling
Component-Level Errors
Use error boundaries to catch component errors:
<script lang="ts">
import ErrorBoundary from '$lib/components/ErrorBoundary.svelte';
let error = $state<Error | null>(null);
</script>
<ErrorBoundary {error}>
{#snippet children()}
<MyComponent />
{/snippet}
{#snippet fallback(error)}
<div class="p-4 border border-red-500 rounded-lg">
<h3 class="text-red-600 font-semibold">Error</h3>
<p class="text-sm text-red-500">{error.message}</p>
<button onclick={() => error = null}>Retry</button>
</div>
{/snippet}
</ErrorBoundary>
Service-Level Errors
Handle errors in services:
// src/lib/services/dataService.ts
import { logger } from '$lib/utils/logger';
import { errorService } from '$lib/services/errorService';
export const dataService = {
async loadData(): Promise<Data> {
try {
logger.logFunctionEntry('dataService', 'loadData');
const data = await invoke('get_data');
logger.logFunctionExit('dataService', 'loadData', data);
return data;
} catch (error) {
logger.error('dataService', 'loadData', 'Failed to load data', { error });
errorService.handleError(error);
throw error;
}
}
};
Error Service
Centralized error handling:
// src/lib/services/errorService.ts
import { toast } from '$lib/stores/toast';
import { logger } from '$lib/utils/logger';
export const errorService = {
handleError(error: unknown, context?: string) {
const message = this.getErrorMessage(error);
const errorObj = error instanceof Error ? error : new Error(String(error));
// Log error
logger.error('errorService', 'handleError', message, { error: errorObj, context });
// Show user-friendly message
toast.error(this.getUserMessage(error));
// Report to error tracking (if configured)
this.reportError(errorObj, context);
},
getErrorMessage(error: unknown): string {
if (error instanceof Error) return error.message;
if (typeof error === 'string') return error;
return 'An unknown error occurred';
},
getUserMessage(error: unknown): string {
const message = this.getErrorMessage(error);
// Map technical errors to user-friendly messages
if (message.includes('network')) {
return 'Network error. Please check your connection.';
}
if (message.includes('session')) {
return 'Session expired. Please log in again.';
}
return 'Something went wrong. Please try again.';
},
reportError(error: Error, context?: string) {
// Send to error tracking service
// Example: Sentry, LogRocket, etc.
}
};
Error Types
Network Errors
class NetworkError extends Error {
constructor(
message: string,
public statusCode?: number,
public response?: any
) {
super(message);
this.name = 'NetworkError';
}
}
async function fetchData() {
try {
const response = await fetch('/api/data');
if (!response.ok) {
throw new NetworkError('Request failed', response.status, response);
}
return await response.json();
} catch (error) {
if (error instanceof NetworkError) {
// Handle network error
}
throw error;
}
}
Validation Errors
class ValidationError extends Error {
constructor(
message: string,
public field?: string,
public errors?: Record<string, string[]>
) {
super(message);
this.name = 'ValidationError';
}
}
function validateData(data: any) {
const errors: Record<string, string[]> = {};
if (!data.name) {
errors.name = ['Name is required'];
}
if (errors.name || errors.email) {
throw new ValidationError('Validation failed', undefined, errors);
}
}
Business Logic Errors
class BusinessError extends Error {
constructor(
message: string,
public code: string,
public context?: Record<string, any>
) {
super(message);
this.name = 'BusinessError';
}
}
async function processAssessment(assessment: Assessment) {
if (assessment.dueDate < new Date()) {
throw new BusinessError(
'Cannot modify overdue assessment',
'ASSESSMENT_OVERDUE',
{ assessmentId: assessment.id }
);
}
}
Error Display Patterns
Toast Notifications
import { toast } from '$lib/stores/toast';
try {
await saveData();
toast.success('Saved successfully');
} catch (error) {
toast.error('Failed to save');
}
Error Messages in Forms
<script lang="ts">
let email = $state('');
let emailError = $state<string | null>(null);
function validateEmail() {
if (!email.includes('@')) {
emailError = 'Invalid email address';
return false;
}
emailError = null;
return true;
}
</script>
<Input
bind:value={email}
error={emailError}
onblur={validateEmail}
/>
Error States in Components
<script lang="ts">
import AsyncWrapper from '$lib/components/ui/AsyncWrapper';
let data = $state<any[]>([]);
let loading = $state(true);
let error = $state<Error | null>(null);
async function loadData() {
loading = true;
error = null;
try {
data = await fetchData();
} catch (e) {
error = e instanceof Error ? e : new Error('Failed to load');
} finally {
loading = false;
}
}
</script>
<AsyncWrapper {loading} {error} data={data}>
{#snippet children(data)}
{#each data as item}
<ItemComponent {item} />
{/each}
{/snippet}
</AsyncWrapper>
Error Recovery
Retry Logic
async function retry<T>(
fn: () => Promise<T>,
maxAttempts: number = 3,
delay: number = 1000
): Promise<T> {
for (let i = 0; i < maxAttempts; i++) {
try {
return await fn();
} catch (error) {
if (i === maxAttempts - 1) throw error;
await new Promise(resolve => setTimeout(resolve, delay * (i + 1)));
}
}
throw new Error('Max attempts reached');
}
// Usage
const data = await retry(() => fetchData(), 3, 1000);
Fallback Values
async function loadDataWithFallback() {
try {
return await fetchData();
} catch (error) {
logger.warn('dataService', 'loadData', 'Using fallback data', { error });
return getCachedData() || getDefaultData();
}
}
Graceful Degradation
<script lang="ts">
let data = $state<any[]>([]);
let error = $state<Error | null>(null);
async function loadData() {
try {
data = await fetchData();
} catch (e) {
error = e instanceof Error ? e : new Error('Failed to load');
// Show cached data if available
const cached = getCachedData();
if (cached) {
data = cached;
}
}
}
</script>
{#if error && data.length === 0}
<ErrorMessage {error} onRetry={loadData} />
{:else}
<DataComponent {data} />
{#if error}
<div class="text-yellow-600">
Showing cached data. Some features may be limited.
</div>
{/if}
{/if}
Backend Error Handling
Tauri Command Errors
#[tauri::command]
pub async fn risky_operation() -> Result<String, String> {
match do_something() {
Ok(result) => Ok(result),
Err(e) => {
// Log error
eprintln!("Error: {}", e);
// Return user-friendly error
Err(format!("Operation failed: {}", e))
}
}
}
Error Types in Rust
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AppError {
#[error("Network error: {0}")]
Network(String),
#[error("Validation error: {0}")]
Validation(String),
#[error("Not found: {0}")]
NotFound(String),
}
#[tauri::command]
pub async fn get_data(id: i32) -> Result<Data, String> {
let data = fetch_data(id).await
.map_err(|e| AppError::Network(e.to_string()))?;
Ok(data)
}
Error Logging
Structured Logging
import { logger } from '$lib/utils/logger';
try {
await operation();
} catch (error) {
logger.error(
'context',
'function',
'Operation failed',
{
error,
userId: user.id,
timestamp: new Date().toISOString(),
context: { /* additional context */ }
}
);
}
Error Tracking
// Report to error tracking service
function reportError(error: Error, context?: Record<string, any>) {
// Example: Sentry
Sentry.captureException(error, {
extra: context
});
// Example: Custom tracking
fetch('/api/errors', {
method: 'POST',
body: JSON.stringify({
message: error.message,
stack: error.stack,
context
})
});
}
Best Practices
1. Handle Errors at the Right Level
// ✅ Good - Handle in service
async function loadData() {
try {
return await fetchData();
} catch (error) {
// Handle and transform
throw new UserFriendlyError('Failed to load data');
}
}
// ❌ Avoid - Let errors bubble up unhandled
async function loadData() {
return await fetchData(); // Error not handled
}
2. Provide User-Friendly Messages
// ✅ Good - User-friendly
throw new Error('Unable to connect. Please check your internet connection.');
// ❌ Avoid - Technical
throw new Error('ECONNREFUSED 127.0.0.1:3000');
3. Log Errors with Context
// ✅ Good - With context
logger.error('service', 'function', 'Error message', {
error,
userId: user.id,
action: 'save_assessment'
});
// ❌ Avoid - No context
console.error(error);
4. Don't Swallow Errors
// ✅ Good - Re-throw or handle
try {
await operation();
} catch (error) {
logger.error('context', 'function', 'Failed', { error });
throw error; // Re-throw
}
// ❌ Avoid - Swallow error
try {
await operation();
} catch (error) {
// Error ignored
}
5. Use Error Boundaries
<!-- ✅ Good - Error boundary -->
<ErrorBoundary>
<ComponentThatMightError />
</ErrorBoundary>
<!-- ❌ Avoid - No error handling -->
<ComponentThatMightError />
Error Handling Checklist
- Errors handled at appropriate levels
- User-friendly error messages
- Error logging implemented
- Error boundaries in place
- Retry logic for transient errors
- Fallback values provided
- Error tracking configured
- Error recovery mechanisms
Next Steps
- Services - Service error handling
- Testing - Error testing
- Architecture - System error handling
On this page