Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 37 additions & 13 deletions packages/powersync_core/lib/src/exceptions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,12 @@ class SyncResponseException implements Exception {
static SyncResponseException _fromResponseBody(
http.BaseResponse response, String body) {
final decoded = convert.jsonDecode(body);
final details = _stringOrFirst(decoded['error']?['details']) ?? body;
final details = switch (decoded['error']) {
final Map<String, Object?> details => _errorDescription(details),
_ => null,
} ??
body;

final message = '${response.reasonPhrase ?? "Request failed"}: $details';
return SyncResponseException(response.statusCode, message);
}
Expand All @@ -73,6 +78,37 @@ class SyncResponseException implements Exception {
);
}

/// Extracts an error description from an error resonse looking like
/// `{"code":"PSYNC_S2106","status":401,"description":"Authentication required","name":"AuthorizationError"}`.
static String? _errorDescription(Map<String, Object?> raw) {
final code = raw['code']; // Required, string
final description = raw['description']; // Required, string

final name = raw['name']; // Optional, string
final details = raw['details']; // Optional, string

if (code is! String || description is! String) {
return null;
}

final fullDescription = StringBuffer(code);
if (name is String) {
fullDescription.write('($name)');
}

fullDescription
..write(': ')
..write(description);

if (details is String) {
fullDescription
..write(', ')
..write(details);
}

return fullDescription.toString();
}

int statusCode;
String description;

Expand All @@ -84,18 +120,6 @@ class SyncResponseException implements Exception {
}
}

String? _stringOrFirst(Object? details) {
if (details == null) {
return null;
} else if (details is String) {
return details;
} else if (details case [final String first, ...]) {
return first;
} else {
return null;
}
}

class PowersyncNotReadyException implements Exception {
/// @nodoc
PowersyncNotReadyException(this.message);
Expand Down
56 changes: 56 additions & 0 deletions packages/powersync_core/test/exceptions_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import 'dart:convert';

import 'package:http/http.dart';
import 'package:powersync_core/src/exceptions.dart';
import 'package:test/test.dart';

void main() {
group('SyncResponseException', () {
const errorResponse =
'{"error":{"code":"PSYNC_S2106","status":401,"description":"Authentication required","name":"AuthorizationError"}}';

test('fromStreamedResponse', () async {
final exc = await SyncResponseException.fromStreamedResponse(
StreamedResponse(Stream.value(utf8.encode(errorResponse)), 401));

expect(exc.statusCode, 401);
expect(exc.description,
'Request failed: PSYNC_S2106(AuthorizationError): Authentication required');
});

test('fromResponse', () {
final exc =
SyncResponseException.fromResponse(Response(errorResponse, 401));
expect(exc.statusCode, 401);
expect(exc.description,
'Request failed: PSYNC_S2106(AuthorizationError): Authentication required');
});

test('with description', () {
const errorResponse =
'{"error":{"code":"PSYNC_S2106","status":401,"description":"Authentication required","name":"AuthorizationError", "details": "Missing authorization header"}}';

final exc =
SyncResponseException.fromResponse(Response(errorResponse, 401));
expect(exc.statusCode, 401);
expect(exc.description,
'Request failed: PSYNC_S2106(AuthorizationError): Authentication required, Missing authorization header');
});

test('malformed', () {
const malformed =
'{"message":"Route GET:/foo/bar not found","error":"Not Found","statusCode":404}';

final exc = SyncResponseException.fromResponse(Response(malformed, 401));
expect(exc.statusCode, 401);
expect(exc.description,
'Request failed: {"message":"Route GET:/foo/bar not found","error":"Not Found","statusCode":404}');

final exc2 = SyncResponseException.fromResponse(Response(
'not even json', 500,
reasonPhrase: 'Internal server error'));
expect(exc2.statusCode, 500);
expect(exc2.description, 'Internal server error');
});
});
}
Loading