Skip to content

Commit

Permalink
works
Browse files Browse the repository at this point in the history
  • Loading branch information
marfavi committed Aug 30, 2023
1 parent e38d508 commit 846b6b0
Show file tree
Hide file tree
Showing 22 changed files with 191 additions and 211 deletions.
51 changes: 22 additions & 29 deletions lib/core/data/datasources/account_remote_data_source.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,42 +23,35 @@ class AccountRemoteDataSource {
Future<Either<NetworkFailure, AuthenticatedUser>> login(
String email,
String encodedPasscode,
) async {
return executor.executeAndMap(
() => apiV1.apiV1AccountLoginPost(
body: LoginDto(
email: email,
password: encodedPasscode,
version: ApiUriConstants.minAppVersion,
),
),
(result) => AuthenticatedUser(
email: email,
token: result.token!,
),
);
) {
return executor
.execute(
() => apiV1.apiV1AccountLoginPost(
body: LoginDto(

Check warning on line 30 in lib/core/data/datasources/account_remote_data_source.dart

View check run for this annotation

Codecov / codecov/patch

lib/core/data/datasources/account_remote_data_source.dart#L29-L30

Added lines #L29 - L30 were not covered by tests
email: email,
password: encodedPasscode,
version: ApiUriConstants.minAppVersion,
),
),
)
.map((result) => AuthenticatedUser(email: email, token: result.token!));
}

Future<Either<NetworkFailure, User>> getUser() async {
return executor.executeAndMap(
apiV2.apiV2AccountGet,
UserModel.fromResponse,
);
Future<Either<NetworkFailure, User>> getUser() {
return executor.execute(apiV2.apiV2AccountGet).map(UserModel.fromResponse);
}

Future<Either<NetworkFailure, Unit>> requestPasscodeReset(String email) {
return executor.executeAndMap(
() => apiV1.apiV1AccountForgotpasswordPost(body: EmailDto(email: email)),
(_) => unit,
final body = EmailDto(email: email);
return executor.executeAndDiscard(
() => apiV1.apiV1AccountForgotpasswordPost(body: body),

Check warning on line 47 in lib/core/data/datasources/account_remote_data_source.dart

View check run for this annotation

Codecov / codecov/patch

lib/core/data/datasources/account_remote_data_source.dart#L47

Added line #L47 was not covered by tests
);
}

Future<Either<NetworkFailure, bool>> emailExists(String email) async {
return executor.executeAndMap(
() => apiV2.apiV2AccountEmailExistsPost(
body: EmailExistsRequest(email: email),
),
(result) => result.emailExists,
);
Future<Either<NetworkFailure, bool>> emailExists(String email) {
final body = EmailExistsRequest(email: email);
return executor
.execute(() => apiV2.apiV2AccountEmailExistsPost(body: body))
.map((result) => result.emailExists);
}
}
67 changes: 28 additions & 39 deletions lib/core/network/network_request_executor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import 'package:coffeecard/utils/firebase_analytics_event_logging.dart';
import 'package:fpdart/fpdart.dart';
import 'package:logger/logger.dart';

typedef _NetworkRequest<Result> = Future<Response<Result>> Function();
part 'network_request_executor_mapping.dart';

typedef _NetworkRequest<BodyType> = Future<Response<BodyType>> Function();
typedef _ExecutorResult<R> = Future<Either<NetworkFailure, R>>;

class NetworkRequestExecutor {
final Logger logger;
Expand All @@ -15,42 +18,15 @@ class NetworkRequestExecutor {
required this.firebaseLogger,
});

/// Executes the network [request] and returns the result as an [Either].
Future<Either<NetworkFailure, R>> execute<R>(_NetworkRequest<R> request) {
return _execute(request);
}

/// Executes the network [request] and returns the result as an [Either],
/// where the Right value of type [ResponseType] is transformed to an [R]
/// using the given [transformer].
Future<Either<NetworkFailure, R>> executeAndMap<R, ResponseType>(
_NetworkRequest<ResponseType> request,
R Function(ResponseType) transformer,
) async {
final result = await execute(request);
return result.map(transformer);
}

/// Executes the network [request] and returns the result as an [Either],
/// where the Right value is an [Iterable] of [ResponseType], where each
/// element herein is transformed to an [R] using the given [transformer].
/// Executes a network request and returns an [Either].
///
/// The result is returned as a [List].
// TODO(marfavi): return Iterable instead of List?
Future<Either<NetworkFailure, List<R>>> executeAndMapAll<R, ResponseType>(
_NetworkRequest<Iterable<ResponseType>> request,
R Function(ResponseType) transformer,
) {
return executeAndMap(
request,
(items) => items.map(transformer).toList(),
);
}

/// Executes a network request and returns the result as an [Either].
Future<Either<NetworkFailure, Result>> _execute<Result>(
Future<Response<Result>> Function() request,
) async {
/// If the request fails, a [NetworkFailure] is returned in a [Left].
/// If the request succeeds, the response body of type
/// [Body] is returned in a [Right].
///
/// If the response body type is empty or dynamic, use [executeAndDiscard]
/// instead, which always returns [Unit] in a [Right] if the request succeeds.
_ExecutorResult<Body> execute<Body>(_NetworkRequest<Body> request) async {
try {
final response = await request();

Expand All @@ -59,20 +35,33 @@ class NetworkRequestExecutor {
_logResponse(response);
return Left(ServerFailure.fromResponse(response));
}

return Right(response.body as Result);
return Right(response.body as Body);
} on Exception catch (e) {
// could not connect to backend for whatever reason
logger.e(e.toString());
return const Left(ConnectionFailure());
}
}

/// Executes the network [request] and returns the result as an [Either].
///
/// If the request fails, a [NetworkFailure] is returned in a [Left].
/// If the request succeeds, [Unit] is returned in a [Right] and
/// the orignial response body is discarded.
///
/// This method is useful as it allows to discard the response body when its
/// type is dynamic, e.g. when it is expected to be empty. This avoids having
/// to provide dummy values for dynamic response bodies in Mockito tests.
_ExecutorResult<Unit> executeAndDiscard<B>(_NetworkRequest<B> request) async {
final result = await execute(request);
return result.map((_) => unit);

Check warning on line 57 in lib/core/network/network_request_executor.dart

View check run for this annotation

Codecov / codecov/patch

lib/core/network/network_request_executor.dart#L55-L57

Added lines #L55 - L57 were not covered by tests
}

/// Logs the response to the console and to Firebase.
///
/// Does not log 401 responses to Firebase since these are expected when
/// the user is not logged in.
void _logResponse<T>(Response<T> response) {
void _logResponse<Body>(Response<Body> response) {
logger.e(response.toString());

if (response.statusCode != 401) {
Expand Down
19 changes: 19 additions & 0 deletions lib/core/network/network_request_executor_mapping.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
part of 'network_request_executor.dart';

extension ExecutorMapX<R> on _ExecutorResult<R> {
/// If the result of the [Future] is a [Right], maps the value to a [C].
_ExecutorResult<C> map<C>(C Function(R) mapper) async {
final result = await this;
return result.map(mapper);
}
}

extension ExecutorMapAllX<R> on _ExecutorResult<Iterable<R>> {
/// If the result of the [Future] is a [Right],
/// maps all values to a [List] of [C].
// TODO(marfavi): return Iterable instead of List?
_ExecutorResult<List<C>> mapAll<C>(C Function(R) mapper) async {
final result = await this;
return result.map((items) => items.map(mapper).toList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,9 @@ class EnvironmentRemoteDataSource {
final CoffeecardApiV2 apiV2;
final NetworkRequestExecutor executor;

Future<Either<NetworkFailure, Environment>> getEnvironmentType() async {
return executor.executeAndMap(
apiV2.apiV2AppconfigGet,
Environment.fromAppConfig,
);
Future<Either<NetworkFailure, Environment>> getEnvironmentType() {
return executor
.execute(apiV2.apiV2AppconfigGet)
.map(Environment.fromAppConfig);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,18 @@ class LeaderboardRemoteDataSource {
LeaderboardFilter category,
int top,
) {
return executor.executeAndMap(
() => apiV2.apiV2LeaderboardTopGet(preset: category.label, top: top),
(entries) => entries.map(LeaderboardUserModel.fromDTO).toList(),
);
return executor
.execute(
() => apiV2.apiV2LeaderboardTopGet(preset: category.label, top: top),

Check warning on line 32 in lib/features/leaderboard/data/datasources/leaderboard_remote_data_source.dart

View check run for this annotation

Codecov / codecov/patch

lib/features/leaderboard/data/datasources/leaderboard_remote_data_source.dart#L32

Added line #L32 was not covered by tests
)
.mapAll(LeaderboardUserModel.fromDTO);
}

Future<Either<NetworkFailure, LeaderboardUser>> getLeaderboardUser(
LeaderboardFilter category,
) {
return executor.executeAndMap(
() => apiV2.apiV2LeaderboardGet(preset: category.label),
LeaderboardUserModel.fromDTO,
);
return executor
.execute(() => apiV2.apiV2LeaderboardGet(preset: category.label))
.map(LeaderboardUserModel.fromDTO);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,8 @@ class OccupationRemoteDataSource {
});

Future<Either<NetworkFailure, List<OccupationModel>>> getOccupations() {
return executor.executeAndMap(
api.apiV1ProgrammesGet,
(occupations) => occupations.map(OccupationModel.fromDTOV1).toList(),
);
return executor
.execute(api.apiV1ProgrammesGet)
.mapAll(OccupationModel.fromDTOV1);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,16 @@ class OpeningHoursRemoteDataSource {

/// Check if the cafe is open.
Future<Either<Failure, bool>> isOpen() {
return executor.executeAndMap(
() async => api.apiOpenShortKeyGet(shortKey: shortkey),
(result) => result.open,
);
return executor
.execute(() => api.apiOpenShortKeyGet(shortKey: shortkey))
.map((result) => result.open);
}

/// Get the opening hours of the cafe, including today's opening hours and
/// the opening hours for the next 7 days.
Future<Either<Failure, OpeningHours>> getOpeningHours() async {
return executor.executeAndMap(
() => api.apiShiftsShortKeyGet(shortKey: shortkey),
OpeningHoursModel.fromDTO,
);
Future<Either<Failure, OpeningHours>> getOpeningHours() {
return executor
.execute(() => api.apiShiftsShortKeyGet(shortKey: shortkey))
.map(OpeningHoursModel.fromDTO);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,9 @@ class ProductRemoteDataSource {
required this.executor,
});

Future<Either<NetworkFailure, List<Product>>> getProducts() async {
return executor.executeAndMap(
apiV1.apiV1ProductsGet,
(products) => products.map(ProductModel.fromDTO).toList(),
);
Future<Either<NetworkFailure, List<Product>>> getProducts() {
return executor
.execute(apiV1.apiV1ProductsGet)
.mapAll(ProductModel.fromDTO);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,23 @@ class PurchaseRemoteDataSource {
Future<Either<NetworkFailure, InitiatePurchase>> initiatePurchase(
int productId,
PaymentType paymentType,
) async {
return executor.executeAndMap(
() => apiV2.apiV2PurchasesPost(
body: InitiatePurchaseRequest(
productId: productId,
paymentType: paymentTypeToJson(paymentType),
),
),
InitiatePurchaseModel.fromDto,
);
) {
return executor
.execute(
() => apiV2.apiV2PurchasesPost(
body: InitiatePurchaseRequest(

Check warning on line 28 in lib/features/purchase/data/datasources/purchase_remote_data_source.dart

View check run for this annotation

Codecov / codecov/patch

lib/features/purchase/data/datasources/purchase_remote_data_source.dart#L27-L28

Added lines #L27 - L28 were not covered by tests
productId: productId,
paymentType: paymentTypeToJson(paymentType),

Check warning on line 30 in lib/features/purchase/data/datasources/purchase_remote_data_source.dart

View check run for this annotation

Codecov / codecov/patch

lib/features/purchase/data/datasources/purchase_remote_data_source.dart#L30

Added line #L30 was not covered by tests
),
),
)
.map(InitiatePurchaseModel.fromDto);
}

/// Get a purchase by its purchase id
Future<Either<NetworkFailure, SinglePurchase>> getPurchase(
int purchaseId,
) async {
return executor.executeAndMap(
() => apiV2.apiV2PurchasesIdGet(id: purchaseId),
SinglePurchaseModel.fromDto,
);
Future<Either<NetworkFailure, SinglePurchase>> getPurchase(int purchaseId) {
return executor
.execute(() => apiV2.apiV2PurchasesIdGet(id: purchaseId))
.map(SinglePurchaseModel.fromDto);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,16 @@ class ReceiptRemoteDataSource {
});

/// Retrieves all of the users used receipts
Future<Either<Failure, List<Receipt>>> getUsersUsedTicketsReceipts() async {
return executor.executeAndMapAll(
() => apiV2.apiV2TicketsGet(includeUsed: true),
SwipeReceiptModel.fromTicketResponse,
);
Future<Either<Failure, List<Receipt>>> getUsersUsedTicketsReceipts() {
return executor
.execute(() => apiV2.apiV2TicketsGet(includeUsed: true))
.mapAll(SwipeReceiptModel.fromTicketResponse);
}

/// Retrieves all of the users purchase receipts
Future<Either<Failure, List<Receipt>>> getUserPurchasesReceipts() async {
return executor.executeAndMapAll(
apiV2.apiV2PurchasesGet,
PurchaseReceiptModel.fromSimplePurchaseResponse,
);
Future<Either<Failure, List<Receipt>>> getUserPurchasesReceipts() {
return executor
.execute(apiV2.apiV2PurchasesGet)
.mapAll(PurchaseReceiptModel.fromSimplePurchaseResponse);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ class RegisterRemoteDataSource {
String email,
String encodedPasscode,
int occupationId,
) async {
return executor.executeAndMap(
) {
return executor.executeAndDiscard(
() => apiV2.apiV2AccountPost(
body: RegisterAccountRequest(
name: name,
Expand All @@ -27,7 +27,6 @@ class RegisterRemoteDataSource {
programmeId: occupationId,
),
),
(_) => unit,
);
}
}
Loading

0 comments on commit 846b6b0

Please sign in to comment.