gmana_value_objects 0.0.5
gmana_value_objects: ^0.0.5 copied to clipboard
Production-ready value objects with configurable validation for Email, Password, Text, Number, and Money
gmana_value_objects #
Production-ready domain value objects with configurable validation for Email,
Password, Text, Number, and Money types, built on top of gmana.
gmana_value_objects is pure Dart and framework independent. Use it in CLI
apps, Dart servers, Flutter apps, or shared domain packages when you want typed
values, Either-based validation, and rich error models.
Use gmana for low-level rules and field validators. Use
gmana_value_objects when input should become a typed domain value before it
moves deeper into your application.
For complete API examples, see doc/api.md. For ecommerce money modeling, see doc/money.md.
Installation #
dependencies:
gmana_value_objects: ^0.0.5
Or install it from the command line:
dart pub add gmana_value_objects
Features #
- Typed value objects for
Email,Password,TextValue,NumberValue, andMoney. - Pure validators for each type when you want
Either<ValidationError, T>without constructing a value object. - Sealed error hierarchies for exhaustive
switchhandling. - Stable
ValidationError.codevalues for logs, APIs, analytics, and UI keys. - Configurable validation presets for common application constraints.
- Default English validation messages with a small interface for i18n.
- Currency-aware money stored as exact integer minor units.
Quick Start #
import 'package:gmana_value_objects/gmana_value_objects.dart';
final email = Email('user@example.com');
if (email.isValid) {
print(email.valueOrNull); // user@example.com
} else {
print(email.errorOrNull?.code);
}
Every value object exposes:
| API | Meaning |
|---|---|
value |
Full Either<ValidationError, T> validation result. |
isValid / isInvalid |
Boolean validation state. |
valueOrNull |
Valid typed value, or null. |
errorOrNull |
Validation error, or null. |
isSensitive |
true for sensitive objects such as Password. |
Email #
final email = Email('USER@Example.COM');
print(email.valueOrNull); // user@example.com
final strictEmail = Email(
'user@tempmail.com',
config: EmailValidationConfig.strict(),
);
switch (strictEmail.errorOrNull) {
case EmailDisposableDomain(:final domain):
print('Disposable domain: $domain');
case null:
print('Valid email');
default:
print('Invalid email');
}
Email validation supports format checks, max lengths, disposable domains, and custom blocked domains.
Password #
final password = Password(
'MyP@ssw0rd!2026',
config: PasswordValidationConfig.strict(),
);
print(password.isSensitive); // true
print(password.toString()); // Password(valid)
Password validation supports min/max length, ASCII-only rules, common password checks, predictable sequence checks, and complexity scoring.
Text #
final username = TextValue(
'john_doe',
config: TextValidationConfig.username(),
);
final title = TextValue(
'Hello World',
config: TextValidationConfig(
minLength: 5,
maxLength: 50,
pattern: r'^[a-zA-Z\s]+$',
blacklistedWords: {'spam', 'banned'},
),
);
Text validation supports trimming, empty/whitespace rules, length bounds, regular expressions, allowed characters, blacklisted words, and common presets.
Number #
final age = NumberValue('25', config: NumberValidationConfig.age());
final price = NumberValue('19.99', config: NumberValidationConfig.price());
final quantity = NumberValue.fromNum(
10,
config: NumberValidationConfig.positiveInteger(),
);
Number validation supports min/max bounds, integer-only mode, negative controls, decimal-place limits, finite number checks, and presets for age, rating, percentage, prices, and integer values.
Money #
Money stores exact integer minor units with currency metadata, so arithmetic
does not depend on floating-point decimal storage.
final unitPrice = Money.fromDecimalString('19.99', Currency.usd);
final shipping = Money.fromDecimalString('5.00', Currency.usd);
final total = unitPrice * 2 + shipping;
final discounted = total.applyDiscountPercent(10);
print(unitPrice.minorUnits); // 1999
print(discounted.formatted); // $40.48
Money supports:
- exact minor-unit storage, such as cents for USD
- zero, major/minor, decimal string, numeric, and
MoneyAmountconstructors - same-currency arithmetic and comparison
- half-up rounding for multiplication and percentages
- proportional allocation without losing remainders
- deterministic display strings and API decimal strings
MoneyValidatorforEither-based form and pipeline validation
final result = MoneyValidator(MoneyValidationConfig.ecommerce())
.validate('1,234.56', currency: 'USD');
result.fold(
(error) => print(DefaultValidationErrorMessages().getMessage(error)),
(amount) => print(amount.formattedWithCode), // USD 1234.56
);
Default Messages #
final messages = DefaultValidationErrorMessages();
final email = Email('invalid');
if (email.errorOrNull case final error?) {
print(messages.getMessage(error)); // Invalid email format
}
For app-specific localization, switch on ValidationError subclasses directly:
String localize(ValidationError error) {
return switch (error) {
EmailEmpty() => 'Email is required',
EmailInvalidFormat() => 'Enter a valid email',
PasswordTooShort(:final minLength) =>
'Use at least $minLength characters',
_ => DefaultValidationErrorMessages().getMessage(error),
};
}
Composing With gmana #
This package re-exports Either, Left, and Right from gmana, so value
object validation can compose with your own domain failures.
import 'package:gmana_value_objects/gmana_value_objects.dart' as vo;
sealed class Failure {}
final class ValidationFailure extends Failure {
ValidationFailure(this.error);
final vo.ValidationError error;
}
final class AppEmail {
const AppEmail._(this.value);
factory AppEmail(String input) {
return AppEmail._(
vo.Email(input).value.mapLeft(ValidationFailure.new),
);
}
final vo.Either<Failure, String> value;
}