Files
Client/lib/api/http_errors.dart
T

66 lines
2.5 KiB
Dart

import 'dart:async';
import 'dart:io';
import 'package:http/http.dart' as http;
import 'errors/app_exception.dart';
import 'errors/auth_exception.dart';
import 'errors/network_exception.dart';
import 'errors/not_found_exception.dart';
import 'errors/server_exception.dart';
/// Runs [send] and converts transport-level failures (socket/timeout/client
/// errors) into a [NetworkException] tagged with [label] (e.g. `Talk <uri>`).
/// Passes through whatever [send] produces, including `null` for the base-class
/// request hooks that may skip the call.
Future<http.Response?> sendGuarded(
String label,
Future<http.Response>? Function() send,
) async {
try {
return await send();
} on SocketException catch (e) {
throw NetworkException(technicalDetails: '$label: ${e.message}');
} on TimeoutException catch (e) {
throw NetworkException.timeout(technicalDetails: '$label: $e');
} on http.ClientException catch (e) {
throw NetworkException(technicalDetails: '$label: ${e.message}');
}
}
/// Collapses whitespace and caps an HTTP error body at 500 chars so it can be
/// embedded in an [AppException]'s technical details without dumping headers.
String previewBody(String body) {
final collapsed = body.replaceAll(RegExp(r'\s+'), ' ').trim();
return collapsed.length > 500 ? '${collapsed.substring(0, 500)}…' : collapsed;
}
/// Builds a `<label> -> HTTP <status>[ body=<preview>]` technical detail line.
String httpErrorDetail(String label, String body, int status) {
final preview = previewBody(body);
return preview.isEmpty
? '$label -> HTTP $status'
: '$label -> HTTP $status body=$preview';
}
/// The [AppException] matching a non-2xx HTTP [status], carrying [detail] as
/// technical details: 401/403 map to auth errors, 404 to not-found, everything
/// else to a generic server error.
AppException exceptionForStatus(int status, String detail) => switch (status) {
401 => AuthException.unauthorized(technicalDetails: detail),
403 => AuthException.forbidden(technicalDetails: detail),
404 => NotFoundException(technicalDetails: detail),
_ => ServerException(statusCode: status, technicalDetails: detail),
};
/// Throws [exceptionForStatus].
Never throwForStatus(int status, String detail) =>
throw exceptionForStatus(status, detail);
/// Throws [exceptionForStatus] unless [response] is 2xx.
void ensureSuccess(String label, http.Response response) {
final status = response.statusCode;
if (status >= 200 && status < 300) return;
throwForStatus(status, httpErrorDetail(label, response.body, status));
}