Files
2026-07-27 14:42:23 +02:00

45 lines
2.0 KiB
Dart

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<T, E>` 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,
};
}