import 'package:freezed_annotation/freezed_annotation.dart'; part 'api_exception.freezed.dart'; /// Typed errors thrown by [NodeMasterApiClient]/repositories. Riverpod's /// `AsyncValue` (via `FutureProvider`/`AsyncNotifier`) already catches /// thrown exceptions into `AsyncValue.error`, so this is the result-type /// boundary — no separate `Result` wrapper is layered on top. @freezed sealed class ApiException with _$ApiException implements Exception { /// DNS/connection failure — couldn't reach the host at all. const factory ApiException.network(String message) = ApiNetworkException; /// Connect/send/receive timeout. const factory ApiException.timeout() = ApiTimeoutException; /// The server responded with a 4xx/5xx and (usually) a `{"error": msg}` /// body. const factory ApiException.server(int statusCode, String message) = ApiServerException; /// The response body wasn't the JSON shape a model expected. const factory ApiException.parse(String message) = ApiParseException; /// Request was cancelled (e.g. the active connection changed mid-flight). const factory ApiException.cancelled() = ApiCancelledException; /// Anything else. const factory ApiException.unknown(String message) = ApiUnknownException; } extension ApiExceptionUserMessage on ApiException { /// Short, user-facing copy — screens can use this directly or switch on /// the exception type themselves for more specific handling. String get userMessage => switch (this) { ApiNetworkException() => "Can't reach this node — check the URL and that it's reachable.", ApiTimeoutException() => 'The node took too long to respond.', ApiServerException(:final statusCode, :final message) => statusCode == 404 ? 'Not found.' : 'Server error ($statusCode): $message', ApiParseException() => 'Received an unexpected response from the node.', ApiCancelledException() => 'Cancelled.', ApiUnknownException(:final message) => message, }; }