Skip to content

Commit

Permalink
Merge pull request #1 from callius/maintenance/dart-3
Browse files Browse the repository at this point in the history
Feature: Dart 3 & Raise DSL
  • Loading branch information
callius authored Jun 25, 2023
2 parents 94f664b + 6373bd9 commit cd33a73
Show file tree
Hide file tree
Showing 42 changed files with 483 additions and 982 deletions.
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ The included `EmailAddress` class is an example of an annotation processor compa

```dart
/// A W3C HTML5 email address.
class EmailAddress extends GenericValueObject<String> {
final class EmailAddress extends GenericValueObject<String> {
static const of = EmailAddressValidator(EmailAddress._);
const EmailAddress._(super.value);
Expand All @@ -86,18 +86,19 @@ class EmailAddress extends GenericValueObject<String> {
This value object can then be used to validate an email address like so:

```dart
Either<UserCreateFailure, User> createUser(UserParamsDto params) {
return EmailAddress.of(params.emailAddress)
.leftMap(UserCreateFailure.invalidEmailAddress)
.flatMap(
(emailAddress) =>
Future<Either<UserCreateFailure, User>> createUser(UserParamsDto params) {
return eitherAsync((r) {
final emailAddress = EmailAddress.of(params.emailAddress)
.leftMap(UserCreateFailure.invalidEmailAddress)
.let(r.bind);
// ... validating other params ...
repositoryCreate(
return repositoryCreate(
UserParams(
emailAddress: emailAddress,
// ... passing other validated params ...
),
),
);
).then(r.bind);
});
}
```
14 changes: 13 additions & 1 deletion packages/target/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
## 0.2.0

Features:

- Add arrow-kt raise dsl in the form of `either()` and `eitherAsync()`.

Maintenance:

- Upgrade to Dart 3.
- Add `final` and `interface` class modifiers.
- Remove explicit `freezed` dependency from `target_annotation_processor`.

## 0.1.2

Features:

- Add support for enum value objects in target_annotation_processor.
- Add support for enum value objects in `target_annotation_processor`.

## 0.1.0

Expand Down
19 changes: 10 additions & 9 deletions packages/target/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ The included `EmailAddress` class is an example of an annotation processor compa

```dart
/// A W3C HTML5 email address.
class EmailAddress extends GenericValueObject<String> {
final class EmailAddress extends GenericValueObject<String> {
static const of = EmailAddressValidator(EmailAddress._);
const EmailAddress._(super.value);
Expand All @@ -86,18 +86,19 @@ class EmailAddress extends GenericValueObject<String> {
This value object can then be used to validate an email address like so:

```dart
Either<UserCreateFailure, User> createUser(UserParamsDto params) {
return EmailAddress.of(params.emailAddress)
.leftMap(UserCreateFailure.invalidEmailAddress)
.flatMap(
(emailAddress) =>
Future<Either<UserCreateFailure, User>> createUser(UserParamsDto params) {
return eitherAsync((r) {
final emailAddress = EmailAddress.of(params.emailAddress)
.leftMap(UserCreateFailure.invalidEmailAddress)
.let(r.bind);
// ... validating other params ...
repositoryCreate(
return repositoryCreate(
UserParams(
emailAddress: emailAddress,
// ... passing other validated params ...
),
),
);
).then(r.bind);
});
}
```
2 changes: 1 addition & 1 deletion packages/target/lib/src/buildable.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:dartz/dartz.dart';

abstract class Buildable<T> {
abstract interface class Buildable<T> {
/// Builds [T] from this. Analogous to a zip function on all the builder's properties passed
/// to the constructor of [T].
Option<T> build();
Expand Down
2 changes: 1 addition & 1 deletion packages/target/lib/src/length_range.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
abstract class LengthRange {
abstract interface class LengthRange {
abstract final int minLength;
abstract final int maxLength;
}
2 changes: 1 addition & 1 deletion packages/target/lib/src/modelable.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:dartz/dartz.dart';

abstract class Modelable<Failure, Model> {
abstract interface class Modelable<Failure, Model> {
/// Creates a model from this.
Either<Failure, Model> toModel();
}
2 changes: 1 addition & 1 deletion packages/target/lib/src/non_empty_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ typedef Nel<T> = NonEmptyList<T>;

/// A list which contains at least one item.
/// Loosely based on the ArrowKt implementation, optimized for Dart `const` constructors.
class NonEmptyList<T> extends DelegatingIterable<T> {
final class NonEmptyList<T> extends DelegatingIterable<T> {
const NonEmptyList._(super.base);

const NonEmptyList.fromListUnsafe(List<T> super.list);
Expand Down
130 changes: 130 additions & 0 deletions packages/target/lib/src/raise.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import 'package:dartz/dartz.dart';
import 'package:equatable/equatable.dart';

/// DSL for computing an [Either] from the given effect [block]. Based on the
/// arrow-kt implementation.
Either<Error, A> either<Error, A>(A Function(Raise<Error> r) block) {
return foldOrThrow(block, Left.new, Right.new);
}

B foldOrThrow<Error, A, B>(
A Function(Raise<Error> r) block,
B Function(Error error) recover,
B Function(A value) transform,
) {
return fold(block, (it) => throw it, recover, transform);
}

B fold<Error, A, B>(
A Function(Raise<Error> r) block,
B Function(Exception throwable) onCatch,
B Function(Error error) recover,
B Function(A value) transform,
) {
final raise = _DefaultRaise<Error>();
try {
final res = block(raise);
raise.complete();
return transform(res);
} on RaiseCancellationException<Error> catch (e) {
raise.complete();
return recover(e.raised);
} on Exception catch (e) {
raise.complete();
return onCatch(e);
}
}

/// DSL for computing an [Either] async from the given effect [block]. Based on
/// the arrow-kt implementation.
Future<Either<Error, A>> eitherAsync<Error, A>(
Future<A> Function(Raise<Error> r) block,
) {
return foldOrThrowAsync(block, Left.new, Right.new);
}

Future<B> foldOrThrowAsync<Error, A, B>(
Future<A> Function(Raise<Error> r) block,
B Function(Error error) recover,
B Function(A value) transform,
) {
return foldAsync(block, (it) => throw it, recover, transform);
}

Future<B> foldAsync<Error, A, B>(
Future<A> Function(Raise<Error> r) block,
B Function(Exception throwable) onCatch,
B Function(Error error) recover,
B Function(A value) transform,
) async {
final raise = _DefaultRaise<Error>();
try {
final res = await block(raise);
raise.complete();
return transform(res);
} on RaiseCancellationException<Error> catch (e) {
raise.complete();
return recover(e.raised);
} on Exception catch (e) {
raise.complete();
return onCatch(e);
}
}

/// Implementation of arrow-kt raise dsl.
abstract class Raise<Error> {
Never raise(Error r);

A bind<A, E extends Error>(Either<E, A> r) {
return r.fold(raise, id);
}
}

final class _DefaultRaise<Error> extends Raise<Error> {
bool _isActive = true;

bool complete() {
final result = _isActive;
_isActive = false;
return result;
}

@override
Never raise(Error r) {
if (_isActive) {
throw RaiseCancellationException(raised: r, raise: this);
} else {
throw RaiseLeakedError();
}
}
}

final class RaiseCancellationException<Error> extends Equatable
implements Exception {
final Error raised;
final Raise<Error> raise;

const RaiseCancellationException({required this.raised, required this.raise});

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

final class RaiseLeakedError extends StateError {
RaiseLeakedError()
: super(
'raise or bind was called outside of its DSL scope, and the DSL Scoped operator was leaked',
);
}

extension RaiseEnsureExtension<Error> on Raise<Error> {
void ensure(bool condition, Error Function() raise) {
if (!condition) {
this.raise(raise());
}
}

B ensureNotNull<B>(B? value, Error Function() raise) {
return value ?? this.raise(raise());
}
}
2 changes: 1 addition & 1 deletion packages/target/lib/src/value_failure.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
abstract class ValueFailure<T> {
abstract interface class ValueFailure<T> {
abstract final T failedValue;
}
2 changes: 1 addition & 1 deletion packages/target/lib/src/value_object.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
abstract class ValueObject<T> {
abstract interface class ValueObject<T> {
abstract final T value;
}
2 changes: 1 addition & 1 deletion packages/target/lib/src/value_object/email_address.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import 'package:target/src/generic_value_object.dart';
import 'package:target/src/value_validator/email_address_validator.dart';

/// A W3C HTML5 email address.
class EmailAddress extends GenericValueObject<String> {
final class EmailAddress extends GenericValueObject<String> {
static const of = EmailAddressValidator(EmailAddress._);

const EmailAddress._(super.value);
Expand Down
2 changes: 1 addition & 1 deletion packages/target/lib/src/value_object/non_negative_int.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import 'package:target/src/generic_value_object.dart';
import 'package:target/src/value_object/positive_int.dart';
import 'package:target/src/value_validator/non_negative_int_validator.dart';

class NonNegativeInt extends GenericValueObject<int> {
final class NonNegativeInt extends GenericValueObject<int> {
static const of = NonNegativeIntValidator(NonNegativeInt._);

const NonNegativeInt._(super.value);
Expand Down
2 changes: 1 addition & 1 deletion packages/target/lib/src/value_object/positive_int.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import 'package:target/src/generic_value_object.dart';
import 'package:target/src/value_object/non_negative_int.dart';
import 'package:target/src/value_validator/positive_int_validator.dart';

class PositiveInt extends GenericValueObject<int> {
final class PositiveInt extends GenericValueObject<int> {
static const of = PositiveIntValidator(PositiveInt._);

static const one = PositiveInt._(1);
Expand Down
1 change: 1 addition & 0 deletions packages/target/lib/target.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export 'src/generic_value_object.dart';
export 'src/length_range.dart';
export 'src/modelable.dart';
export 'src/non_empty_list.dart';
export 'src/raise.dart';
export 'src/value_failure.dart';
export 'src/value_object.dart';
export 'src/value_object/email_address.dart';
Expand Down
10 changes: 4 additions & 6 deletions packages/target/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
name: target
description: Functional domain modeling in Dart.
version: 0.1.2
version: 0.2.0
repository: https://github.com/callius/target-dart/tree/main/packages/target
issue_tracker: https://github.com/callius/target-dart/issues
homepage: https://github.com/callius/target-dart

environment:
sdk: '>=2.19.6 <3.0.0'
sdk: '>=3.0.5 <4.0.0'

dependencies:
collection: ^1.17.0
collection: ^1.17.2
dartz: ^0.10.1
equatable: ^2.0.5

dev_dependencies:
build_runner: null
lints: ^2.0.1
mockito: ^5.4.0
lints: ^2.1.1
test: null
Loading

0 comments on commit cd33a73

Please sign in to comment.