···11+# Error Handling Patterns
22+33+This project standardizes error handling so UI messages stay user-friendly and
44+network failures are predictable.
55+66+## Standard Error Types
77+88+- `NetworkFailure` (in `lib/src/infrastructure/network/network_failure.dart`)
99+ - `ConnectionFailure`, `AuthFailure`, `RateLimitFailure`, `ClientFailure`,
1010+ `ServerFailure`, `DecodeFailure`
1111+- `OAuthException` (in `lib/src/infrastructure/auth/oauth_exceptions.dart`)
1212+- `ValidationError` (in `lib/src/features/dms/domain/outbox_error.dart`)
1313+1414+## UI Messaging
1515+1616+Use `errorMessage(error)` from `lib/src/core/utils/error_message.dart` instead of
1717+`error.toString()` to avoid leaking raw exception text into the UI.
1818+1919+Example:
2020+2121+```dart
2222+ErrorView(
2323+ title: 'Failed to load feed',
2424+ message: errorMessage(error),
2525+ onRetry: () => ref.read(feedContentProvider(uri).notifier).refresh(),
2626+);
2727+```
2828+2929+## Logging vs Display
3030+3131+- Log raw errors and stacks for debugging (`logger.error(...)`).
3232+- Display standardized UI messages via `errorMessage(...)`.
3333+- Prefer typed errors (e.g., `NetworkFailure`) at repository boundaries.
3434+3535+## Tests
3636+3737+Add or update tests whenever new error types are introduced or UI messaging
3838+changes, especially for networking/auth flows.
+1-1
doc/roadmap.txt
···1616- [ ] Type safety improvements:
1717 - Use freezed for critical models
1818 - Add typed JSON parsing where appropriate
1919-- [ ] Error handling standardization:
1919+- [x] Error handling standardization:
2020 - Define standard error types for common failures (network, auth,
2121 validation)
2222 - Standardize error display messages (no raw .toString() in UI)
+49
lib/src/core/utils/error_message.dart
···11+import 'package:lazurite/src/features/dms/domain/outbox_error.dart';
22+import 'package:lazurite/src/infrastructure/auth/oauth_exceptions.dart';
33+import 'package:lazurite/src/infrastructure/network/network_failure.dart';
44+55+/// Returns a user-friendly error message for UI display.
66+String errorMessage(Object? error, {String fallback = 'Something went wrong. Please try again.'}) {
77+ if (error == null) return fallback;
88+99+ if (error is NetworkFailure) {
1010+ return switch (error) {
1111+ ConnectionFailure() => 'Network error. Check your connection and try again.',
1212+ AuthFailure(:final requiresReauth) =>
1313+ requiresReauth
1414+ ? 'Session expired. Please sign in again.'
1515+ : 'Authentication failed. Please try again.',
1616+ RateLimitFailure(:final retryAfter) =>
1717+ retryAfter != null
1818+ ? 'Too many requests. Try again in ${retryAfter.inSeconds}s.'
1919+ : 'Too many requests. Try again later.',
2020+ ServerFailure() => 'Server error. Please try again later.',
2121+ ClientFailure(:final statusCode) =>
2222+ statusCode == 404
2323+ ? 'Not found. The item may have been deleted.'
2424+ : 'Request failed. Please try again.',
2525+ DecodeFailure() => 'We received an unexpected response. Please try again.',
2626+ };
2727+ }
2828+2929+ if (error is OAuthException) {
3030+ return error.errorDescription ?? 'Authorization failed. Please try again.';
3131+ }
3232+3333+ if (error is ValidationError) {
3434+ return error.message ?? 'Invalid input. Please update and retry.';
3535+ }
3636+3737+ if (error is FormatException) {
3838+ return error.message.isNotEmpty ? error.message : fallback;
3939+ }
4040+4141+ if (error is StateError) {
4242+ final message = error.message;
4343+ if (message.contains('authenticated')) {
4444+ return 'Please sign in to continue.';
4545+ }
4646+ }
4747+4848+ return fallback;
4949+}