Skip to content

Commit 99ced1d

Browse files
committed
Add base error and trycall function, refactored result
1 parent b2125e5 commit 99ced1d

File tree

4 files changed

+77
-123
lines changed

4 files changed

+77
-123
lines changed

lib/chopper/base_error.dart

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/// Interface for the errors that could be returned
2+
interface class BaseError {}
3+
4+
/// The device is not connected error
5+
class NoInternetError implements BaseError {
6+
/// The device is not connected error
7+
const NoInternetError();
8+
}
9+
10+
/// Http client error
11+
class ServerError implements BaseError {
12+
/// Http client error
13+
const ServerError();
14+
}
15+
16+
/// Unknown error
17+
class UnknownError implements BaseError {
18+
/// Unknown error
19+
const UnknownError();
20+
}
21+
22+
/// User not authenticated error
23+
class AuthenticationFailedError implements BaseError {
24+
/// User not authenticated error
25+
const AuthenticationFailedError();
26+
}

lib/common/result/result.dart

Lines changed: 40 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
1-
import 'package:flutter/foundation.dart';
1+
import 'dart:async';
22

3-
/// Result class to handle success, failure and loading states.
3+
import 'package:chopper/chopper.dart';
4+
import 'package:dcc_toolkit/chopper/base_error.dart';
5+
import 'package:freezed_annotation/freezed_annotation.dart';
6+
import 'package:http/http.dart';
7+
8+
/// Result class to handle success and failure states.
49
@immutable
510
sealed class Result<T> {
611
const Result();
712

813
factory Result.success(T value) => Success(value);
914

10-
factory Result.failure(Exception? exception) => Failure(exception);
11-
12-
factory Result.loading() => Loading<T>();
15+
factory Result.failure(BaseError? exception) => Failure(exception);
1316

1417
/// Returns the value if [Result] is [Success] or null otherwise.
1518
T? get getOrNull => switch (this) {
@@ -18,17 +21,14 @@ sealed class Result<T> {
1821
};
1922

2023
/// Returns the exception if [Result] is [Failure] or null otherwise.
21-
Exception? get exceptionOrNull => switch (this) {
22-
Failure(exception: final exception) => exception,
24+
BaseError? get errorOrNull => switch (this) {
25+
Failure(error: final error) => error,
2326
_ => null,
2427
};
2528

2629
/// Returns true if [Result] is [Success].
2730
bool get isSuccess => this is Success<T>;
2831

29-
/// Returns true if [Result] is [Loading].
30-
bool get isLoading => this is Loading<T>;
31-
3232
/// Returns true if [Result] is [Failure].
3333
bool get isFailure => this is Failure<T>;
3434

@@ -40,19 +40,31 @@ sealed class Result<T> {
4040
};
4141
}
4242

43-
/// Maps the value of [Result] if it is [Success] or returns the same [Result] otherwise.
44-
/// This catches any exception thrown by [transform] and returns a [Failure] with the exception.
45-
Result<R> mapCatch<R>(R Function(T value) transform) {
46-
return switch (this) {
47-
Success(value: final value) => () {
48-
try {
49-
return Result.success(transform(value));
50-
} on Exception catch (e) {
51-
return Failure<R>(e);
52-
}
53-
}(),
54-
_ => this as Result<R>,
55-
};
43+
/// Executes the given API call [fn]. When the call succeeds, [Result.success] is returned with the response.
44+
/// When the call fails the optional [onError] is executed and the exceptions are handled.
45+
/// If there is no [onError] provided, an error of type [BaseError] is returned in [Result.failure].
46+
Future<Result<S>> tryCall<S>(
47+
FutureOr<S> Function() fn, {
48+
Future<Result<S>> Function(Exception error)? onError,
49+
}) async {
50+
try {
51+
return Result.success(await fn());
52+
} on Exception catch (e) {
53+
if (onError != null) {
54+
return onError(e);
55+
}
56+
return switch (e) {
57+
ChopperHttpException() => Result.failure(
58+
switch (e.response.statusCode) {
59+
401 => const AuthenticationFailedError(),
60+
_ => const ServerError(),
61+
},
62+
),
63+
ClientException() => Result.failure(const NoInternetError()),
64+
CheckedFromJsonException() => Result.failure(const ServerError()),
65+
_ => Result.failure(const UnknownError()),
66+
};
67+
}
5668
}
5769
}
5870

@@ -80,36 +92,20 @@ final class Success<T> extends Result<T> {
8092
/// Class representing a failed result.
8193
@immutable
8294
final class Failure<T> extends Result<T> {
83-
/// Creates a [Failure] with the given [exception].
84-
const Failure(this.exception);
95+
/// Creates a [Failure] with the given [error].
96+
const Failure(this.error);
8597

8698
/// The exception of the [Result].
87-
final Exception? exception;
99+
final BaseError? error;
88100

89101
@override
90102
bool operator ==(Object other) {
91103
return identical(this, other) ||
92104
other is Failure<T> &&
93105
runtimeType == other.runtimeType &&
94-
other.exception == exception;
95-
}
96-
97-
@override
98-
int get hashCode => exception.hashCode;
99-
}
100-
101-
/// Class representing a loading result.
102-
@immutable
103-
final class Loading<T> extends Result<T> {
104-
/// Creates a [Loading].
105-
const Loading();
106-
107-
@override
108-
bool operator ==(Object other) {
109-
return identical(this, other) ||
110-
other is Loading<T> && runtimeType == other.runtimeType;
106+
other.error == error;
111107
}
112108

113109
@override
114-
int get hashCode => 1;
110+
int get hashCode => error.hashCode;
115111
}

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@ dependencies:
1414
flutter:
1515
sdk: flutter
1616
freezed_annotation: ^2.4.4
17+
http: ^1.2.2
1718
intl: ^0.19.0
1819
logging: ^1.2.0
1920

2021
dev_dependencies:
2122
flutter_test:
2223
sdk: flutter
23-
http: ^1.2.2
2424
mocktail: ^1.0.4
2525
parameterized_test: ^2.0.0
2626
very_good_analysis: ^6.0.0
Lines changed: 10 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
1+
import 'package:dcc_toolkit/chopper/base_error.dart';
12
import 'package:dcc_toolkit/common/result/result.dart';
23
import 'package:flutter_test/flutter_test.dart';
34
import 'package:parameterized_test/parameterized_test.dart';
45

56
void main() {
6-
final testExceptionResult = Failure<int>(Exception('test'));
7+
const testExceptionResult = Failure<int>(ServerError());
78
parameterizedTest('comparing 2 results should return true if equal', [
89
[const Success<int>(1), const Success<int>(1), true],
910
[const Success<int>(1), const Success<int>(2), false],
10-
[const Success<int>(1), const Loading<int>(), false],
11-
[const Success<int>(1), Failure<int>(Exception('test')), false],
12-
[const Loading<int>(), Failure<int>(Exception('test')), false],
13-
[const Loading<int>(), const Loading<int>(), true],
14-
[Failure<int>(Exception('test')), Failure<int>(Exception('test')), false],
11+
[const Success<int>(1), const Failure<int>(ServerError()), false],
1512
[testExceptionResult, testExceptionResult, true],
1613
], (Result<int> result1, Result<int> result2, bool expected) {
1714
final actual = result1 == result2;
@@ -24,29 +21,19 @@ void main() {
2421
});
2522

2623
test('getOrNull returns null for Failure', () {
27-
final result = Result<int>.failure(Exception('test'));
28-
expect(result.getOrNull, null);
29-
});
30-
31-
test('getOrNull returns null for Loading', () {
32-
final result = Result<int>.loading();
24+
final result = Result<int>.failure(const ServerError());
3325
expect(result.getOrNull, null);
3426
});
3527

3628
test('exceptionOrNull returns exception for Failure', () {
37-
final exception = Exception('test');
29+
const exception = ServerError();
3830
final result = Result<int>.failure(exception);
39-
expect(result.exceptionOrNull, exception);
31+
expect(result.errorOrNull, exception);
4032
});
4133

4234
test('exceptionOrNull returns null for Success', () {
4335
final result = Result<int>.success(1);
44-
expect(result.exceptionOrNull, null);
45-
});
46-
47-
test('exceptionOrNull returns null for Loading', () {
48-
final result = Result<int>.loading();
49-
expect(result.exceptionOrNull, null);
36+
expect(result.errorOrNull, null);
5037
});
5138

5239
test('isSuccess returns true for Success', () {
@@ -55,32 +42,12 @@ void main() {
5542
});
5643

5744
test('isSuccess returns false for Failure', () {
58-
final result = Result<int>.failure(Exception('test'));
45+
final result = Result<int>.failure(const ServerError());
5946
expect(result.isSuccess, false);
6047
});
6148

62-
test('isSuccess returns false for Loading', () {
63-
final result = Result<int>.loading();
64-
expect(result.isSuccess, false);
65-
});
66-
67-
test('isLoading returns true for Loading', () {
68-
final result = Result<int>.loading();
69-
expect(result.isLoading, true);
70-
});
71-
72-
test('isLoading returns false for Success', () {
73-
final result = Result<int>.success(1);
74-
expect(result.isLoading, false);
75-
});
76-
77-
test('isLoading returns false for Failure', () {
78-
final result = Result<int>.failure(Exception('test'));
79-
expect(result.isLoading, false);
80-
});
81-
8249
test('isFailure returns true for Failure', () {
83-
final result = Result<int>.failure(Exception('test'));
50+
final result = Result<int>.failure(const ServerError());
8451
expect(result.isFailure, true);
8552
});
8653

@@ -89,50 +56,15 @@ void main() {
8956
expect(result.isFailure, false);
9057
});
9158

92-
test('isFailure returns false for Loading', () {
93-
final result = Result<int>.loading();
94-
expect(result.isFailure, false);
95-
});
96-
9759
test('map transforms value for Success', () {
9860
final result = Result<int>.success(1);
9961
final mapped = result.map((value) => value + 1);
10062
expect(mapped.getOrNull, 2);
10163
});
10264

10365
test('map does not transform value for Failure', () {
104-
final result = Result<int>.failure(Exception('test'));
66+
final result = Result<int>.failure(const ServerError());
10567
final mapped = result.map((value) => value + 1);
10668
expect(mapped.getOrNull, null);
10769
});
108-
109-
test('map does not transform value for Loading', () {
110-
final result = Result<int>.loading();
111-
final mapped = result.map((value) => value + 1);
112-
expect(mapped.getOrNull, null);
113-
});
114-
115-
test('mapCatch transforms value for Success', () {
116-
final result = Result<int>.success(1);
117-
final mapped = result.mapCatch((value) => value + 1);
118-
expect(mapped.getOrNull, 2);
119-
});
120-
121-
test('mapCatch does not transform value for Failure', () {
122-
final result = Result<int>.failure(Exception('test'));
123-
final mapped = result.mapCatch((value) => value + 1);
124-
expect(mapped.getOrNull, null);
125-
});
126-
127-
test('mapCatch does not transform value for Loading', () {
128-
final result = Result<int>.loading();
129-
final mapped = result.mapCatch((value) => value + 1);
130-
expect(mapped.getOrNull, null);
131-
});
132-
133-
test('mapCatch catches exception and returns Failure', () {
134-
final result = Result<int>.success(1);
135-
final mapped = result.mapCatch((value) => throw Exception('test'));
136-
expect(mapped.isFailure, true);
137-
});
13870
}

0 commit comments

Comments
 (0)