Target is a library for Functional Domain Modeling in Dart, the Dart implementation of target-kt, inspired by arrow-kt.
Target aims to provide a set of tools for Dart to empower users to quickly write pure, functionally
validated domain models. For this, it includes a set of atomic components: ValueFailure
, ValueObject
,
and ValueValidator
. These components can be used on their own, or in conjunction with the
included build_runner
annotation processor.
A ValueFailure
is an interface representing a failure during value validation.
abstract interface class ValueFailure<T> {
abstract final T failedValue;
}
A ValueObject
is an interface representing a validated value. By convention, value object implementations have a
private primary constructor, so that they are not instantiated outside a ValueValidator
. A value object implementation
must declare a static implementation of a value validator, of
, when used in conjunction with the annotation
processor package.
abstract interface class ValueObject<T> {
abstract final T value;
}
A ValueValidator
is an interface defining value validation functions. The primary validation function, of
, takes an
input and returns either a ValueFailure
or a ValueObject
. The value validator is also callable, delegating to of
.
By convention, the value object's private constructor is often passed to its primary constructor as a reference.
abstract class ValueValidator<I extends Object, F extends ValueFailure<I>, T extends ValueObject<I>> {
const ValueValidator();
Either<F, T> of(I input);
// ...
}
The included StringInRegexValidator
class is an example of a ValueValidator
implementation.
class StringRegexValidator<T extends ValueObject<String>>
extends ValueValidator<String, GenericValueFailure<String>, T> {
final T Function(String input) _ctor;
final RegExp regExp;
const StringRegexValidator(this._ctor, {required this.regExp});
@override
Either<GenericValueFailure<String>, T> of(String input) {
if (regExp.hasMatch(input)) {
return Right(_ctor(input));
} else {
return Left(GenericValueFailure(input));
}
}
}
The included EmailAddress
class is an example of an annotation processor compatible ValueObject
implementation.
/// A W3C HTML5 email address.
final class EmailAddress extends GenericValueObject<String> {
static const of = EmailAddressValidator(EmailAddress._);
const EmailAddress._(super.value);
}
This value object can then be used to validate an email address like so:
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 ...
return repositoryCreate(
UserParams(
emailAddress: emailAddress,
// ... passing other validated params ...
),
).then(r.bind);
});
}