Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Feature: Omit Response in service #545

Merged
merged 4 commits into from
Jan 5, 2024
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
13 changes: 13 additions & 0 deletions chopper/lib/src/chopper_http_exception.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import 'package:chopper/src/response.dart';

/// An exception thrown when a [Response] is unsuccessful < 200 or > 300.
class ChopperHttpException implements Exception {
ChopperHttpException(this.response);

final Response response;

@override
String toString() {
return 'Could not fetch the response for ${response.base.request}. Status code: ${response.statusCode}, error: ${response.error}';
}
}
15 changes: 15 additions & 0 deletions chopper/lib/src/response.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:typed_data';

import 'package:chopper/src/chopper_http_exception.dart';
import 'package:equatable/equatable.dart' show EquatableMixin;
import 'package:http/http.dart' as http;
import 'package:meta/meta.dart';
Expand Down Expand Up @@ -76,6 +77,20 @@ base class Response<BodyType> with EquatableMixin {
}
}

/// Returns the response body if [Response] [isSuccessful] and [body] is not null.
/// Otherwise it throws an [HttpException] with the response status code and error object.
/// If the error object is an [Exception], it will be thrown instead.
BodyType get bodyOrThrow {
if (isSuccessful && body != null) {
return body!;
} else {
if (error is Exception) {
throw error!;
}
throw ChopperHttpException(this);
}
}

@override
List<Object?> get props => [
base,
Expand Down
49 changes: 0 additions & 49 deletions chopper/test/base_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import 'package:http/testing.dart';
import 'package:test/test.dart';
import 'package:transparent_image/transparent_image.dart';

import 'fixtures/error_fixtures.dart';
import 'fixtures/example_enum.dart';
import 'test_service.dart';
import 'test_service_base_url.dart';
Expand Down Expand Up @@ -1650,52 +1649,4 @@ void main() {

httpClient.close();
});

group('Response error casting test', () {
test('Response is succesfull, [returns null]', () {
final base = http.Response('Foobar', 200);

final response = Response(base, 'Foobar');

final result = response.errorWhereType<FooErrorType>();

expect(result, isNull);
});

test('Response is unsuccessful and has no error object, [returns null]',
() {
final base = http.Response('Foobar', 400);

final response = Response(base, '');

final result = response.errorWhereType<FooErrorType>();

expect(result, isNull);
});

test(
'Response is unsuccessful and has error object of different type, [returns null]',
() {
final base = http.Response('Foobar', 400);

final response = Response(base, '', error: 'Foobar');

final result = response.errorWhereType<FooErrorType>();

expect(result, isNull);
});

test(
'Response is unsuccessful and has error object of specified type, [returns error as ErrorType]',
() {
final base = http.Response('Foobar', 400);

final response = Response(base, 'Foobar', error: FooErrorType());

final result = response.errorWhereType<FooErrorType>();

expect(result, isNotNull);
expect(result, isA<FooErrorType>());
});
});
}
21 changes: 21 additions & 0 deletions chopper/test/chopper_http_exception_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import 'package:chopper/src/chopper_http_exception.dart';
import 'package:chopper/src/response.dart';
import 'package:http/http.dart' as http;
import 'package:test/test.dart';

void main() {
test('ChopperHttpException toString prints available information', () {
final request = http.Request('GET', Uri.parse('http://localhost:8000'));
final base = http.Response('Foobar', 400, request: request);
final response = Response(base, 'Foobar', error: 'FooError');

final exception = ChopperHttpException(response);

final result = exception.toString();

expect(
result,
'Could not fetch the response for GET http://localhost:8000. Status code: 400, error: FooError',
);
});
}
1 change: 1 addition & 0 deletions chopper/test/ensure_build_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ void main() {
gitDiffPathArguments: [
'test/test_service.chopper.dart',
'test/test_service_variable.chopper.dart',
'test/test_without_response_service.chopper.dart',
'test/test_service_base_url.chopper.dart',
],
);
Expand Down
114 changes: 114 additions & 0 deletions chopper/test/response_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import 'package:chopper/src/chopper_http_exception.dart';
import 'package:chopper/src/response.dart';
import 'package:http/http.dart' as http;
import 'package:test/test.dart';

import 'fixtures/error_fixtures.dart';

void main() {
group('Response error casting test', () {
test('Response is succesfull, [returns null]', () {
final base = http.Response('Foobar', 200);

final response = Response(base, 'Foobar');

final result = response.errorWhereType<FooErrorType>();

expect(result, isNull);
});

test('Response is unsuccessful and has no error object, [returns null]',
() {
final base = http.Response('Foobar', 400);

final response = Response(base, '');

final result = response.errorWhereType<FooErrorType>();

expect(result, isNull);
});

test(
'Response is unsuccessful and has error object of different type, [returns null]',
() {
final base = http.Response('Foobar', 400);

final response = Response(base, '', error: 'Foobar');

final result = response.errorWhereType<FooErrorType>();

expect(result, isNull);
});

test(
'Response is unsuccessful and has error object of specified type, [returns error as ErrorType]',
() {
final base = http.Response('Foobar', 400);

final response = Response(base, 'Foobar', error: FooErrorType());

final result = response.errorWhereType<FooErrorType>();

expect(result, isNotNull);
expect(result, isA<FooErrorType>());
});
});

group('bodyOrThrow tests', () {
test('Response is successful and has body, [bodyOrThrow returns body]', () {
final base = http.Response('Foobar', 200);
final response = Response(base, {'Foo': 'Bar'});

final result = response.bodyOrThrow;

expect(result, isNotNull);
expect(result, {'Foo': 'Bar'});
});

test(
'Response is unsuccessful and has Exception as error, [bodyOrThrow throws error]',
() {
final base = http.Response('Foobar', 400);
final response = Response(base, '', error: Exception('Error occurred'));

expect(() => response.bodyOrThrow, throwsA(isA<Exception>()));
});

test(
'Response is unsuccessful and has non-exception object as error, [bodyOrThrow throws error]',
() {
final base = http.Response('Foobar', 400);
final response = Response(base, '', error: 'Error occurred');

expect(() => response.bodyOrThrow, throwsA(isA<ChopperHttpException>()));
});

test(
'Response is unsuccessful and has no error, [bodyOrThrow throws ChopperHttpException]',
() {
final base = http.Response('Foobar', 400);
final response = Response(base, '');

expect(() => response.bodyOrThrow, throwsA(isA<ChopperHttpException>()));
});

test(
'Response is successful and has no body, [bodyOrThrow throws ChopperHttpException]',
() {
final base = http.Response('Foobar', 200);
final Response<String> response = Response(base, null);

expect(() => response.bodyOrThrow, throwsA(isA<ChopperHttpException>()));
});

test('Response is successful and has void body, [bodyOrThrow returns void]',
() {
final base = http.Response('Foobar', 200);
// Ignoring void checks for testing purposes
//ignore: void_checks
final Response<void> response = Response(base, '');

expect(() => response.bodyOrThrow, returnsNormally);
});
});
}
4 changes: 2 additions & 2 deletions chopper/test/test_service.chopper.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion chopper/test/test_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ abstract class HttpTestService extends ChopperService {
});

@Get(path: 'https://test.com')
Future fullUrl();
Future<Response> fullUrl();

@Get(path: '/list/string')
Future<Response<List<String>>> listString();
Expand Down
4 changes: 2 additions & 2 deletions chopper/test/test_service_variable.chopper.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion chopper/test/test_service_variable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ abstract class HttpTestServiceVariable extends ChopperService {
});

@Get(path: 'https://test.com')
Future fullUrl();
Future<Response> fullUrl();

@Get(path: '/list/string')
Future<Response<List<String>>> listString();
Expand Down
Loading