diff --git a/lib/api/api_request.dart b/lib/api/api_request.dart deleted file mode 100644 index 2cdf97c..0000000 --- a/lib/api/api_request.dart +++ /dev/null @@ -1 +0,0 @@ -class ApiRequest {} diff --git a/lib/api/errors/error_mapper.dart b/lib/api/errors/error_mapper.dart index 7fb4a9d..9cafa3a 100644 --- a/lib/api/errors/error_mapper.dart +++ b/lib/api/errors/error_mapper.dart @@ -6,6 +6,7 @@ import 'package:http/http.dart' as http; import 'package:nextcloud/nextcloud.dart'; import '../api_error.dart'; +import '../http_errors.dart'; import '../marianumcloud/talk/talk_error.dart'; import 'app_exception.dart'; import 'auth_exception.dart'; @@ -59,9 +60,8 @@ AppException? _dioToAppException(DioException error) { /// status plus a trimmed body preview (same format as the Talk API errors). AppException _dynamiteToAppException(DynamiteApiException error) { final status = error.statusCode; - final body = error.body.replaceAll(RegExp(r'\s+'), ' ').trim(); - final preview = body.length > 500 ? '${body.substring(0, 500)}…' : body; - final detail = body.isEmpty ? 'HTTP $status' : 'HTTP $status body=$preview'; + final preview = previewBody(error.body); + final detail = preview.isEmpty ? 'HTTP $status' : 'HTTP $status body=$preview'; switch (status) { case 401: return AuthException.unauthorized(technicalDetails: detail); diff --git a/lib/api/http_errors.dart b/lib/api/http_errors.dart new file mode 100644 index 0000000..f45f770 --- /dev/null +++ b/lib/api/http_errors.dart @@ -0,0 +1,53 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:http/http.dart' as http; + +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 `). +/// Passes through whatever [send] produces, including `null` for the base-class +/// request hooks that may skip the call. +Future sendGuarded( + String label, + Future? 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 `