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
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
## 3.0.0

### ⚠️ Breaking changes
- Minimum Dart SDK is now `>=3.13.0`
- `equatable` upgraded to `^3.0.0`: `Entity`, `Params`, `Issue` and `Codable` now extend `Equatable` instead of using a mixin, and `Codable.serializer` is now typed as `Codec<dynamic, dynamic>`

### 📦 Dependencies
- `meta` upgraded to `^1.19.0` and `test` to `^1.32.0`

### 🧹 Internal
- Analysis options migrated to `package:coolint/dart.yaml` and updated to `coolint` `3.0.0`
- Constructors migrated to the primary constructor syntax
- `unnecessary_await_in_return` lint disabled: it is deprecated since Dart 3.13 and contradicts `async_return_with_no_await`
- Documentation references fixed and the README examples updated to the current API

## 2.0.0
### ⚠️ Breaking changes
- Change codable naming and field extend
Expand Down
113 changes: 77 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ A bedrock package providing the blueprints and abstract definitions necessary to

## 🚀 Installation

Requires Dart `>=3.13.0`.

Add the package to your `pubspec.yaml`:

```yaml
dependencies:
cool_bedrock: ^1.0.0
cool_bedrock: ^3.0.0
```

Then run:
Expand All @@ -50,18 +52,60 @@ import 'package:cool_bedrock/cool_bedrock.dart';
This example demonstrates how to implement a UseCase, defining its specific Failure and parameter validation:

```dart
// 1. Define the specific Failure for this domain
sealed class FetchUserFailure extends Failure{
const FetchUserFailure();
// 1. Define the domain types: params, entity and the raw remote model.
final class FetchUserParams extends Params {
const FetchUserParams({required this.userId});

final String userId;

// Checked automatically before executing the usecase.
@override
bool get isValid => userId.isNotEmpty;

@override
List<Object?> get props => [userId];
}

final class UserRemote {
const UserRemote({this.name});

final String? name;

UserEntity toEntity() => UserEntity(name: name ?? 'Unknown');
}

final class UserEntity extends Entity {
const UserEntity({required this.name});

final String name;

@override
List<Object?> get props => [name];
}

// In this example the repository returns a Future<Either<Failure, UserRemote>>.

// 2. Define the specific Failure for this domain
sealed class FetchUserFailure extends Failure {
const FetchUserFailure({super.message});
}

final class InvalidUserFailure extends FetchUserFailure {
const InvalidUserFailure() : super(message: 'Invalid User ID provided.');

@override
List<Object?> get props => [message];
}

final class InvalidParamsUserFailure extends FetchUserFailure {
const InvalidParamsUserFailure() : super(message: 'Invalid parameters provided.');
const InvalidParamsUserFailure()
: super(message: 'Invalid parameters provided.');

@override
List<Object?> get props => [message];
}

// 2. Implement the UseCase contract
// 3. Implement the UseCase contract
final class FetchUserUseCase
extends UseCase<UserEntity, FetchUserParams, FetchUserFailure> {
const FetchUserUseCase(this.repository);
Expand All @@ -74,74 +118,71 @@ final class FetchUserUseCase

@override
Future<Either<FetchUserFailure, UserEntity>> execute(
FetchUserParams params) async {
FetchUserParams params,
) async {
// Core logic goes here. Mappers and Repositories are typically called here.
try {
final user = await repository.fetch(params.userId);
return Right(user); // Success
final result = await repository.fetch(params.userId);
return result.fold(
// Map the repository failure to a domain failure
(failure) => const Left(InvalidUserFailure()),
// Map the remote model to the entity
(remote) => Right(remote.toEntity()),
); // Success
} catch (e) {
// Map low-level errors to high-level domain failures
return const Left(InvalidUserFailure()); // Failure
}
}
}

// 4. Or use the Handler: obtain -> transform -> map errors
final class FetchUserUseCaseHandle
extends
UseCaseHandler<
UserEntity,
AuthParams,
FetchUserFailure,
RepositoryValue
> {
const FetchUserUseCaseHandle({
required LoginRepository repository,
});
extends UseCaseHandler<
UserEntity,
FetchUserParams,
FetchUserFailure,
UserRemote
> {
const FetchUserUseCaseHandle({required this.repository});

final UserRepository repository;

// Called automatically if params.isNotValid is true.
@override
FetchUserFailure onInvalidParams() => const InvalidParamsUserFailure();

//Obtain repository values. Multiple repository can be call.
// Obtain repository values. Multiple repositories can be called.
@override
Future<RepositoryValue> obtainValues(
FutureOr<UserRemote> obtainValues(
Resolver<FetchUserFailure> $,
AuthParams params,
FetchUserParams params,
) async {
final user = await $(
getValue(
() => repository.fetch(params.userId),
),
);
return user;
// getValue unwraps a Future<Either<Issue, VALUE>> and maps its errors.
return await $(getValue(() => repository.fetch(params.userId)));
}

@override
UserEntity transformation(
RepositoryValue values,
) {
if(values.name == null || values.name.isEmpty){
UserEntity transformation(UserRemote values) {
if (values.name == null || values.name!.isEmpty) {
throw const UsecaseException(InvalidUserFailure());
}
//Can throw exception of any kind and it will be control by [wrapError]
return values.toEntity()
// It can throw any exception, it will be handled by [wrapError].
return values.toEntity();
}

@override
FetchUserFailure wrapError(Object error, StackTrace stackTrace) {
return const InvalidUserFailure();
}
}

```

### 2. Execution and Error Handling

```dart
// Execution with valid parameters
final validParams = const FetchUserParams('user_123');
const validParams = FetchUserParams(userId: 'user_123');
final validResult = await fetchUserUsecase.call(validParams);

validResult.fold(
Expand Down
5 changes: 1 addition & 4 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
include: package:coolint/coolint.yaml
analyzer:
errors:
document_ignores: ignore
include: package:coolint/dart.yaml
2 changes: 1 addition & 1 deletion example/lib/data/user/user_remote.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
final class UserRemote {
const UserRemote({
const new({
this.name,
this.surname,
this.direction,
Expand Down
4 changes: 2 additions & 2 deletions example/lib/data/user/user_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ abstract base class UserRepository {
}

sealed class UserRepositoryError extends RepositoryError {
const UserRepositoryError();
const new();
}

final class NoUserFindError extends UserRepositoryError {
const NoUserFindError();
const new();
@override
List<Object?> get props => [message];
}
8 changes: 2 additions & 6 deletions example/lib/domain/entities/user_entity.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'package:cool_bedrock/cool_bedrock.dart';

final class UserEntity extends Entity {
const UserEntity({
const new({
required this.name,
required this.surname,
required this.birthday,
Expand All @@ -12,9 +12,5 @@ final class UserEntity extends Entity {
final DateTime birthday;

@override
List<Object?> get props => [
name,
surname,
birthday,
];
List<Object?> get props => [name, surname, birthday];
}
17 changes: 7 additions & 10 deletions example/lib/domain/usecase/user_information_usecase.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export 'package:example_cool_bedrock/domain/entities/user_entity.dart';
abstract base class UserInformationUsecase
extends UseCase<UserEntity, AuthParams, FetchUserFailure> {
/// {@macro example_cool_bedrock.usecase}
const UserInformationUsecase();
const new();
}

/// {@template example_cool_bedrock.usecaseHandler}
Expand All @@ -45,37 +45,34 @@ abstract base class UserInformationUsecase
abstract base class UserInformationUsecaseHandler<Remote extends Object>
extends UseCaseHandler<UserEntity, AuthParams, FetchUserFailure, Remote> {
/// {@macro example_cool_bedrock.usecaseHandler}
const UserInformationUsecaseHandler();
const new();
}

final class AuthParams extends Params {
const AuthParams({required this.userId});
const new({required this.userId});

final String userId;

@override
bool get isValid => userId.isNotEmpty;

@override
List<Object?> get props => [
userId,
];
List<Object?> get props => [userId];
}

sealed class FetchUserFailure extends Failure {
const FetchUserFailure({required super.message});
const new({required super.message});
}

final class InvalidUserFailure extends FetchUserFailure {
const InvalidUserFailure() : super(message: 'Invalid User ID provided.');
const new() : super(message: 'Invalid User ID provided.');

@override
List<Object?> get props => [message];
}

final class InvalidParamsUserFailure extends FetchUserFailure {
const InvalidParamsUserFailure()
: super(message: 'Invalid parameters provided.');
const new() : super(message: 'Invalid parameters provided.');
@override
List<Object?> get props => [message];
}
12 changes: 7 additions & 5 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// ignore_for_file: avoid_print
// ignore_for_file: avoid_print Example view

import 'package:example_cool_bedrock/data/user/user_repository.dart';
import 'package:example_cool_bedrock/domain/usecase/user_information_usecase.dart';
Expand All @@ -20,13 +20,15 @@ Future<void> main(List<String> args) async {

print('\n--- 2. UseCase Creation ---');
// A. Standard Approach (Programmer manually handles execution)
final UserInformationUsecase standardUseCase =
FetchUserUseCase(userRepository);
final UserInformationUsecase standardUseCase = FetchUserUseCase(
userRepository,
);
print('✅ Standard UseCase created.');

// B. Programmatically handled
final UserInformationUsecaseHandler handledUseCase =
FetchUserUseCaseHandle(repository: userRepository);
final UserInformationUsecaseHandler handledUseCase = FetchUserUseCaseHandle(
repository: userRepository,
);
print('✅ Handled UseCase created.');

print('\n--- 3. Scenario: Valid User (cool_bedrock) ---');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import 'package:example_cool_bedrock/src/user_information_usecase/user_remote2_e
/// {@macro example_cool_bedrock.usecase}
final class FetchUserUseCase extends UserInformationUsecase {
/// {@macro example_cool_bedrock.usecase}
const FetchUserUseCase(this.repository);
const new(this.repository);

final UserRepository repository;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import 'package:example_cool_bedrock/src/user_information_usecase/user_remote2_e
final class FetchUserUseCaseHandle
extends UserInformationUsecaseHandler<UserRemote> {
/// {@macro example_cool_bedrock.usecaseHandler}
const FetchUserUseCaseHandle({required this.repository});
const new({required this.repository});

final UserRepository repository;

Expand All @@ -24,17 +24,13 @@ final class FetchUserUseCaseHandle
AuthParams params,
) async {
final user = await $(
getValue(
() => repository.call(userId: params.userId),
),
getValue(() => repository.call(userId: params.userId)),
);
return user;
}

@override
UserEntity transformation(
UserRemote values,
) {
UserEntity transformation(UserRemote values) {
if (values.name == null || (values.name ?? '').isEmpty) {
throw const UsecaseException(InvalidUserFailure());
}
Expand Down
6 changes: 3 additions & 3 deletions example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ publish_to: none
version: 1.0.0

environment:
sdk: ">=3.0.0 <4.0.0"
sdk: ">=3.13.0 <4.0.0"

dependencies:
cool_bedrock:
path: ../

dev_dependencies:
coolint: 2.1.2
test: ^1.28.0
coolint: 3.0.0
test: ^1.32.0
Loading
Loading