feat(from): add form validator class with strategy design pattern

This commit is contained in:
Hugo Pointcheval 2022-07-14 16:59:24 +02:00
parent 61c6e637ec
commit 607b986848
Signed by: hugo
GPG Key ID: A9E8E9615379254F
29 changed files with 454 additions and 256 deletions

View File

@ -1 +1,4 @@
include: package:wyatt_analysis/analysis_options.yaml
include: package:wyatt_analysis/analysis_options.yaml
analyzer:
exclude: "!example/**"

View File

@ -61,10 +61,10 @@ class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
FormDataCubit _formCubit = CustomFormCubit(inputs: getNormalFormData());
FormDataCubit formCubit = CustomFormCubit(inputs: getNormalFormData());
return BlocProvider(
create: (context) => _formCubit,
create: (context) => formCubit,
child: const WidgetTree(),
);
}

View File

@ -187,9 +187,9 @@ class _CheckListInput extends StatelessWidget {
Widget build(BuildContext context) {
return BlocBuilder<FormDataCubit, FormDataState>(
builder: (context, state) {
final _input =
final input =
state.data.validatorOf<ListOption<String>>(formFieldList);
final _options = _input.value;
final options = input.value;
return Column(
mainAxisSize: MainAxisSize.min,
@ -197,33 +197,33 @@ class _CheckListInput extends StatelessWidget {
ListTile(
title: const Text('Checkbox1'),
trailing: Checkbox(
value: _options.contains('checkbox1'),
value: options.contains('checkbox1'),
onChanged: (_) {
context.read<FormDataCubit>().dataChanged(
formFieldList,
_input.select('checkbox1'),
input.select('checkbox1'),
);
}),
),
ListTile(
title: const Text('Checkbox2'),
trailing: Checkbox(
value: _options.contains('checkbox2'),
value: options.contains('checkbox2'),
onChanged: (_) {
context.read<FormDataCubit>().dataChanged(
formFieldList,
_input.select('checkbox2'),
input.select('checkbox2'),
);
}),
),
ListTile(
title: const Text('Checkbox3 (default)'),
trailing: Checkbox(
value: _options.contains('checkbox3'),
value: options.contains('checkbox3'),
onChanged: (_) {
context.read<FormDataCubit>().dataChanged(
formFieldList,
_input.select('checkbox3'),
input.select('checkbox3'),
);
}),
),
@ -239,7 +239,7 @@ class _RadioListInput extends StatelessWidget {
Widget build(BuildContext context) {
return BlocBuilder<FormDataCubit, FormDataState>(
builder: (context, state) {
final _input = state.data.validatorOf<TextString>(formFieldRadio);
final input = state.data.validatorOf<TextString>(formFieldRadio);
return Column(
mainAxisSize: MainAxisSize.min,
@ -248,7 +248,7 @@ class _RadioListInput extends StatelessWidget {
title: const Text('Radio1'),
trailing: Radio<bool>(
groupValue: true,
value: _input.value == 'radio1',
value: input.value == 'radio1',
onChanged: (_) {
context.read<FormDataCubit>().dataChanged(
formFieldRadio,
@ -260,7 +260,7 @@ class _RadioListInput extends StatelessWidget {
title: const Text('Radio2'),
trailing: Radio<bool>(
groupValue: true,
value: _input.value == 'radio2',
value: input.value == 'radio2',
onChanged: (_) {
context.read<FormDataCubit>().dataChanged(
formFieldRadio,
@ -272,7 +272,7 @@ class _RadioListInput extends StatelessWidget {
title: const Text('Radio3'),
trailing: Radio<bool>(
groupValue: true,
value: _input.value == 'radio3',
value: input.value == 'radio3',
onChanged: (_) {
context.read<FormDataCubit>().dataChanged(
formFieldRadio,
@ -378,6 +378,20 @@ class _DebugButton extends StatelessWidget {
}
}
class _ResetButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<FormDataCubit, FormDataState>(
builder: (context, state) {
return ElevatedButton(
onPressed: () => context.read<FormDataCubit>().resetForm(),
child: const Text('RESET'),
);
},
);
}
}
class SignUpForm extends StatelessWidget {
const SignUpForm({Key? key}) : super(key: key);
@ -431,6 +445,8 @@ class SignUpForm extends StatelessWidget {
_SignUpButton(),
const SizedBox(height: 8),
_DebugButton(),
const SizedBox(height: 8),
_ResetButton(),
],
),
),

View File

@ -18,40 +18,56 @@ import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart';
import 'package:wyatt_form_bloc/src/enums/enums.dart';
import 'package:wyatt_form_bloc/src/form/form.dart';
import 'package:wyatt_form_bloc/src/validators/form/every_input_validator.dart';
part 'form_data_state.dart';
abstract class FormDataCubit extends Cubit<FormDataState> {
FormDataCubit({required FormData inputs})
: super(FormDataState(data: inputs));
FormValidator validationStrategy;
FormData formCopy;
FormDataCubit({
required FormData inputs,
FormValidator validator = const EveryInputValidator(),
}) : formCopy = inputs.clone(),
validationStrategy = validator,
super(FormDataState(data: inputs));
/// Change value of a field.
///
///
/// Inputs:
/// - `field`: The key of the field to change.
/// - `dirtyValue`: The new value of the field. (Wrapped in a dirty validator)
void dataChanged(String field, FormInputValidator dirtyValue) {
final _form = state.data.clone();
void dataChanged<V>(
String field,
FormInputValidator<V, ValidationError> dirtyValue,
) {
final form = state.data.clone();
if (_form.contains(field)) {
_form.updateValidator(field, dirtyValue);
if (form.contains(field)) {
form.updateValidator(field, dirtyValue);
} else {
throw Exception('Form field $field not found');
}
emit(
state.copyWith(
data: _form,
status: _form.validate(),
data: form,
status: validationStrategy.validate(form),
),
);
}
/// Just validate the form manually. (Useful if you just update the strategy)
void validate() {
final form = state.data;
emit(state.copyWith(status: validationStrategy.validate(form)));
}
/// Update entries list.
///
///
/// Inputs:
/// - `data`: The new entries list.
/// - `operation`: The operation to perform on the entries set.
@ -59,31 +75,36 @@ abstract class FormDataCubit extends Cubit<FormDataState> {
FormData data, {
SetOperation operation = SetOperation.replace,
}) {
FormData _form = data;
FormData form = data;
switch (operation) {
case SetOperation.replace:
_form = data;
form = data;
break;
case SetOperation.difference:
_form = state.data.difference(data);
form = state.data.difference(data);
break;
case SetOperation.intersection:
_form = state.data.intersection(data);
form = state.data.intersection(data);
break;
case SetOperation.union:
_form = state.data.union(data);
form = state.data.union(data);
break;
}
emit(
state.copyWith(
data: _form,
status: _form.validate(),
data: form,
status: validationStrategy.validate(form),
),
);
}
/// Reset all form inputs
void resetForm() {
emit(FormDataState(data: formCopy));
}
/// Submit the form.
Future<void> submitForm();
}

View File

@ -16,7 +16,6 @@
part of 'form_data_cubit.dart';
@immutable
class FormDataState extends Equatable {
/// Global status of a form.
final FormStatus status;
@ -37,13 +36,11 @@ class FormDataState extends Equatable {
FormStatus? status,
FormData? data,
String? errorMessage,
}) {
return FormDataState(
}) => FormDataState(
status: status ?? this.status,
data: data ?? this.data,
errorMessage: errorMessage ?? this.errorMessage,
);
}
@override
bool? get stringify => true;

View File

@ -14,8 +14,6 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:wyatt_form_bloc/src/form/form.dart';
const Set<FormStatus> _validatedFormStatuses = <FormStatus>{
FormStatus.valid,
FormStatus.submissionInProgress,
@ -51,6 +49,8 @@ enum FormStatus {
bool get isPure => this == FormStatus.pure;
/// Indicates whether the form is completely validated.
/// This means the [FormStatus] is strictly:
/// * `FormStatus.valid`
bool get isValid => this == FormStatus.valid;
/// Indicates whether the form has been validated successfully.
@ -75,23 +75,4 @@ enum FormStatus {
/// Indicates whether the form submission has been canceled.
bool get isSubmissionCanceled => this == FormStatus.submissionCanceled;
/// Validate a list of inputs by processing them in `validate` as validators.
static FormStatus validateInputs(List<FormInput> inputs) {
return validate(
inputs
.map<FormInputValidator>((FormInput input) => input.validator)
.toList(),
);
}
/// Validate a list of validators.
static FormStatus validate(List<FormInputValidator> validators) {
return validators.every((FormInputValidator validator) => validator.pure)
? FormStatus.pure
: validators
.any((FormInputValidator validator) => validator.valid == false)
? FormStatus.invalid
: FormStatus.valid;
}
}

View File

@ -14,7 +14,14 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
enum ValidationError {
/// Generic invalid error.
invalid
// enum ValidationError {
// /// Generic invalid error.
// invalid
// }
abstract class ValidationError {}
enum ValidationStandardError implements ValidationError {
invalid,
empty
}

View File

@ -14,12 +14,15 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'dart:convert';
import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart';
import 'package:wyatt_form_bloc/src/enums/form_input_status.dart';
import 'package:wyatt_form_bloc/src/enums/form_status.dart';
import 'package:wyatt_form_bloc/src/enums/validation_error.dart';
part 'form_data.dart';
part 'form_input.dart';
part 'form_input_metadata.dart';
part 'form_input_validator.dart';
part 'form_data.dart';
part 'form_validator.dart';

View File

@ -16,7 +16,6 @@
part of 'form.dart';
@immutable
class FormData extends Equatable {
final List<FormInput> _inputs;
@ -29,7 +28,7 @@ class FormData extends Equatable {
/// Returns the input for the associated key
FormInput inputOf(String key) {
if (contains(key)) {
return _inputs.firstWhere((FormInput input) => input.key == key);
return _inputs.firstWhere((input) => input.key == key);
} else {
throw Exception('FormInput with key `$key` does not exist in form');
}
@ -46,23 +45,23 @@ class FormData extends Equatable {
}
/// Returns all associated validators as a list
List<FormInputValidator<V, E>> validators<V, E>() {
return _inputs
.map<FormInputValidator<V, E>>(
(FormInput input) => input.validator as FormInputValidator<V, E>,
)
.toList();
}
List<FormInputValidator<V, E>> validators<V, E extends ValidationError>() =>
_inputs
.map<FormInputValidator<V, E>>(
(input) => input.validator as FormInputValidator<V, E>,
)
.toList();
/// A [FormInputValidator] represents the value of a single form input field.
/// It contains information about the [FormInputStatus], value, as well
/// as validation status.
T validatorOf<T>(String key) {
return inputOf(key).validator as T;
}
T validatorOf<T>(String key) => inputOf(key).validator as T;
/// Updates validator of a given input. (perform copyWith)
void updateValidator(String key, FormInputValidator dirtyValue) {
void updateValidator<V>(
String key,
FormInputValidator<V, ValidationError> dirtyValue,
) {
if (contains(key)) {
final index = _inputs.indexOf(
inputOf(key),
@ -73,38 +72,31 @@ class FormData extends Equatable {
/// Returns a validation error if the [FormInputValidator] is invalid.
/// Returns null if the [FormInputValidator] is valid.
E? errorOf<V, E>(String key) {
return (inputOf(key).validator as FormInputValidator<V, E>).error;
}
E? errorOf<E extends ValidationError>(String key) =>
(inputOf(key).validator as FormInputValidator<dynamic, E>).error;
/// The value of the associated [FormInputValidator]. For example,
/// if you have a FormInputValidator for FirstName, the value could be 'Joe'.
V valueOf<V, E>(String key) {
return (inputOf(key).validator as FormInputValidator<V, E>).value;
}
V valueOf<V>(String key) =>
(inputOf(key).validator as FormInputValidator<V, dynamic>).value;
/// Returns `true` if the [FormInputValidator] is not valid.
/// Same as `E? errorOf<V, E>(String key) != null`
bool isNotValid(String key) {
return !inputOf(key).validator.valid;
}
bool isNotValid(String key) => !inputOf(key).validator.valid;
/// Returns all associated metadata as a list
List<FormInputMetadata<M>> metadata<M>() {
return _inputs
.map<FormInputMetadata<M>>(
(FormInput input) => input.metadata as FormInputMetadata<M>,
)
.toList();
}
List<FormInputMetadata<M>> metadata<M>() => _inputs
.map<FormInputMetadata<M>>(
(input) => input.metadata as FormInputMetadata<M>,
)
.toList();
/// Returns the metadata associated. With `M` the type of extra data.
FormInputMetadata<M> metadataOf<M>(String key) {
return inputOf(key).metadata as FormInputMetadata<M>;
}
FormInputMetadata<M> metadataOf<M>(String key) =>
inputOf(key).metadata as FormInputMetadata<M>;
/// Updates metadata of a given input. (perform copyWith)
void updateMetadata(String key, FormInputMetadata meta) {
void updateMetadata<M>(String key, FormInputMetadata<M> meta) {
if (contains(key)) {
final index = _inputs.indexOf(
inputOf(key),
@ -113,15 +105,8 @@ class FormData extends Equatable {
}
}
/// Validate self inputs.
FormStatus validate() {
return FormStatus.validateInputs(_inputs);
}
/// Check if this contains an input with the given key.
bool contains(String key) {
return _inputs.any((FormInput input) => input.key == key);
}
bool contains(String key) => _inputs.any((input) => input.key == key);
/// Makes an intersection set operation and returns newly created [FormData]
FormData intersection(FormData other) {
@ -173,11 +158,9 @@ class FormData extends Equatable {
}
/// Deeply copy this.
FormData clone() {
return FormData(
_inputs.map((FormInput input) => input.clone()).toList(),
);
}
FormData clone() => FormData(
_inputs.map((input) => input.clone()).toList(),
);
/// Export this to [Map] format.
Map<String, dynamic> toMap() {
@ -190,6 +173,9 @@ class FormData extends Equatable {
return map;
}
/// Export this to [String] format.
String toJson() => jsonEncode(toMap());
@override
bool? get stringify => true;

View File

@ -17,10 +17,11 @@
part of 'form.dart';
class FormInput extends Equatable {
class FormInput<Validator extends FormInputValidator<dynamic, ValidationError>>
extends Equatable {
final String key;
final FormInputValidator validator;
final FormInputMetadata metadata;
final Validator validator;
final FormInputMetadata<dynamic> metadata;
String get name => metadata._name ?? key;
@ -31,29 +32,25 @@ class FormInput extends Equatable {
this.metadata = const FormInputMetadata<void>(export: true),
});
FormInput copyWith({
String? key,
Validator? validator,
FormInputMetadata? metadata,
}) =>
FormInput(
key ?? this.key,
validator ?? this.validator,
metadata: metadata ?? this.metadata,
);
FormInput clone() => copyWith(
key: key,
validator: validator,
metadata: metadata,
);
@override
bool? get stringify => true;
@override
List<Object?> get props => [key, validator, metadata];
FormInput copyWith({
String? key,
FormInputValidator? validator,
FormInputMetadata? metadata,
}) {
return FormInput(
key ?? this.key,
validator ?? this.validator,
metadata: metadata ?? this.metadata,
);
}
FormInput clone() {
return copyWith(
key: key,
validator: validator,
metadata: metadata,
);
}
}

View File

@ -17,7 +17,7 @@
part of 'form.dart';
class FormInputMetadata<T> extends Equatable {
class FormInputMetadata<T extends Object?> extends Equatable {
final bool export;
final String? _name;
final T? extra;
@ -27,30 +27,27 @@ class FormInputMetadata<T> extends Equatable {
this.extra,
String? name,
}) : _name = name;
@override
bool? get stringify => true;
@override
List<Object?> get props => [export, _name, extra];
FormInputMetadata<T> copyWith({
bool? export,
String? name,
T? extra,
}) {
return FormInputMetadata<T>(
export: export ?? this.export,
name: name ?? _name,
extra: extra ?? this.extra,
);
}
}) =>
FormInputMetadata<T>(
export: export ?? this.export,
name: name ?? _name,
extra: extra ?? this.extra,
);
FormInputMetadata<T> clone() {
return copyWith(
export: export,
name: _name,
extra: extra,
);
}
FormInputMetadata<T> clone() => copyWith(
export: export,
name: _name,
extra: extra,
);
@override
bool? get stringify => true;
@override
List<Object?> get props => [export, _name, extra];
}

View File

@ -16,7 +16,7 @@
part of 'form.dart';
/// {@template form_input}
/// {@template form_input_validator}
/// A [FormInputValidator] represents the value of a single form input field.
/// It contains information about the [FormInputStatus], [value], as well
/// as validation status.
@ -25,26 +25,28 @@ part of 'form.dart';
/// [FormInputValidator] instances.
///
/// ```dart
/// enum FirstNameError { empty }
/// class FirstName extends FormInputValidator<String, FirstNameError> {
/// const FirstName.pure({String value = ''}) : super.pure(value);
/// const FirstName.dirty({String value = ''}) : super.dirty(value);
/// class Name extends FormInputValidator<String, ValidationStandardError> {
/// const Name.pure({String value = ''}) : super.pure(value);
/// const Name.dirty({String value = ''}) : super.dirty(value);
///
/// @override
/// FirstNameError? validator(String value) {
/// return value.isEmpty ? FirstNameError.empty : null;
/// ValidationStandardError? validator(String? value) {
/// if (value?.isEmpty ?? false) {
/// return ValidationStandardError.empty
/// }
/// return value == null ? ValidationStandardError.invalid : null;
/// }
/// }
/// ```
/// {@endtemplate}
@immutable
abstract class FormInputValidator<V, E> extends Equatable {
abstract class FormInputValidator<V, E extends ValidationError>
extends Equatable {
const FormInputValidator._(this.value, [this.pure = true]);
/// Constructor which create a `pure` [FormInputValidator] with a given value.
const FormInputValidator.pure(V value) : this._(value);
/// Constructor which create a `dirty` [FormInputValidator] with a
/// Constructor which create a `dirty` [FormInputValidator] with a
/// given value.
const FormInputValidator.dirty(V value) : this._(value, false);

View File

@ -0,0 +1,31 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
part of 'form.dart';
/// {@template form_validator}
/// A [FormValidator] represents the global validaton state of a Form.
/// {@endtemplate}
abstract class FormValidator {
/// {@macro form_validator}
const FormValidator();
bool isPure(FormData form) => form
.validators<dynamic, ValidationError>()
.every((validator) => validator.pure);
FormStatus validate(FormData form);
}

View File

@ -0,0 +1,40 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
/// {@template every}
/// Check and validate every input of a form
/// {@endtemplate}
class EveryInputValidator extends FormValidator {
/// {@macro every}
const EveryInputValidator() : super();
@override
FormStatus validate(FormData form) {
if (isPure(form)) {
return FormStatus.pure;
}
if (form
.validators<dynamic, ValidationError>()
.any((validator) => validator.valid == false)) {
return FormStatus.invalid;
}
return FormStatus.valid;
}
}

View File

@ -0,0 +1,41 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:wyatt_form_bloc/src/enums/validation_error.dart';
import 'package:wyatt_form_bloc/src/form/form.dart';
/// {@template regex_validator}
/// Abstract regex validator for form input.
/// {@endtemplate}
abstract class RegexValidator<E extends ValidationError>
extends FormInputValidator<String, E> {
const RegexValidator.pure() : super.pure('');
const RegexValidator.dirty([super.value = '']) : super.dirty();
RegExp get regex;
/// If the value is **not** null, but empty.
E get onEmpty;
/// If value does not conform to regex.
E get onError;
@override
E? validator(String? value) {
if (value?.isEmpty ?? false) {
return onEmpty;
}
return regex.hasMatch(value ?? '') ? null : onError;
}
}

View File

@ -0,0 +1,38 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:wyatt_form_bloc/src/enums/validation_error.dart';
import 'package:wyatt_form_bloc/src/form/form.dart';
abstract class TextValidator<E extends ValidationError>
extends FormInputValidator<String, E> {
const TextValidator.pure() : super.pure('');
const TextValidator.dirty([super.value = '']) : super.dirty();
/// If the value is **not** null, but empty.
E get onEmpty;
/// If value is null.
E get onNull;
@override
E? validator(String? value) {
if (value?.isEmpty ?? false) {
return onEmpty;
}
return value != null ? null : onNull;
}
}

View File

@ -20,7 +20,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart';
/// {@template boolean}
/// Form input for a bool input
/// {@endtemplate}
class Boolean extends FormInputValidator<bool, ValidationError> {
class Boolean extends FormInputValidator<bool, ValidationStandardError> {
/// {@macro boolean}
const Boolean.pure({bool? defaultValue = false})
: super.pure(defaultValue ?? false);
@ -29,7 +29,6 @@ class Boolean extends FormInputValidator<bool, ValidationError> {
const Boolean.dirty({bool value = false}) : super.dirty(value);
@override
ValidationError? validator(bool? value) {
return value != null ? null : ValidationError.invalid;
}
ValidationStandardError? validator(bool? value) =>
value != null ? null : ValidationStandardError.invalid;
}

View File

@ -21,7 +21,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart';
/// Form input for a confirmed password input.
/// {@endtemplate}
class ConfirmedPassword
extends FormInputValidator<String, ValidationError> {
extends FormInputValidator<String, ValidationStandardError> {
/// {@macro confirmed_password}
const ConfirmedPassword.pure({this.password = ''}) : super.pure('');
@ -33,7 +33,6 @@ class ConfirmedPassword
final String password;
@override
ValidationError? validator(String? value) {
return password == value ? null : ValidationError.invalid;
}
ValidationStandardError? validator(String? value) =>
password == value ? null : ValidationStandardError.invalid;
}

View File

@ -15,24 +15,25 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:wyatt_form_bloc/src/enums/enums.dart';
import 'package:wyatt_form_bloc/src/form/form.dart';
import 'package:wyatt_form_bloc/src/validators/inputs/base/regex_validator.dart';
/// {@template email}
/// Form input for an email input.
/// {@endtemplate}
class Email extends FormInputValidator<String, ValidationError> {
class Email extends RegexValidator<ValidationStandardError> {
/// {@macro email}
const Email.pure() : super.pure('');
const Email.pure() : super.pure();
/// {@macro email}
const Email.dirty([String value = '']) : super.dirty(value);
static final RegExp _emailRegExp = RegExp(
r'[^@ \t\r\n]+@[^@ \t\r\n]+\.[^@ \t\r\n]+',
);
const Email.dirty([super.value = '']) : super.dirty();
@override
ValidationError? validator(String? value) {
return _emailRegExp.hasMatch(value ?? '') ? null : ValidationError.invalid;
}
ValidationStandardError get onEmpty => ValidationStandardError.empty;
@override
ValidationStandardError get onError => ValidationStandardError.invalid;
@override
RegExp get regex => RegExp(
r'[^@ \t\r\n]+@[^@ \t\r\n]+\.[^@ \t\r\n]+',
);
}

View File

@ -0,0 +1,32 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:wyatt_form_bloc/src/enums/validation_error.dart';
import 'package:wyatt_form_bloc/src/validators/inputs/base/text_validator.dart';
class EnumValidator<E> extends TextValidator<ValidationStandardError> {
/// {@macro text_string}
const EnumValidator.pure() : super.pure();
/// {@macro text_string}
EnumValidator.dirty(E value) : super.dirty(value.toString());
@override
ValidationStandardError get onEmpty => ValidationStandardError.empty;
@override
ValidationStandardError get onNull => ValidationStandardError.invalid;
}

View File

@ -15,24 +15,25 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:wyatt_form_bloc/src/enums/enums.dart';
import 'package:wyatt_form_bloc/src/form/form.dart';
import 'package:wyatt_form_bloc/src/validators/inputs/base/regex_validator.dart';
/// {@template iban}
/// Form input for an IBAN input.
/// {@endtemplate}
class Iban extends FormInputValidator<String, ValidationError> {
class Iban extends RegexValidator<ValidationStandardError> {
/// {@macro iban}
const Iban.pure() : super.pure('');
const Iban.pure() : super.pure();
/// {@macro iban}
const Iban.dirty([String value = '']) : super.dirty(value);
static final RegExp _regExp = RegExp(
r'^(?:((?:IT|SM)\d{2}[A-Z]{1}\d{22})|(NL\d{2}[A-Z]{4}\d{10})|(LV\d{2}[A-Z]{4}\d{13})|((?:BG|GB|IE)\d{2}[A-Z]{4}\d{14})|(GI\d{2}[A-Z]{4}\d{15})|(RO\d{2}[A-Z]{4}\d{16})|(MT\d{2}[A-Z]{4}\d{23})|(NO\d{13})|((?:DK|FI)\d{16})|((?:SI)\d{17})|((?:AT|EE|LU|LT)\d{18})|((?:HR|LI|CH)\d{19})|((?:DE|VA)\d{20})|((?:AD|CZ|ES|MD|SK|SE)\d{22})|(PT\d{23})|((?:IS)\d{24})|((?:BE)\d{14})|((?:FR|MC|GR)\d{25})|((?:PL|HU|CY)\d{26}))$',
);
const Iban.dirty([super.value = '']) : super.dirty();
@override
ValidationError? validator(String? value) {
return _regExp.hasMatch(value ?? '') ? null : ValidationError.invalid;
}
ValidationStandardError get onEmpty => ValidationStandardError.empty;
@override
ValidationStandardError get onError => ValidationStandardError.invalid;
@override
RegExp get regex => RegExp(
r'^(?:((?:IT|SM)\d{2}[A-Z]{1}\d{22})|(NL\d{2}[A-Z]{4}\d{10})|(LV\d{2}[A-Z]{4}\d{13})|((?:BG|GB|IE)\d{2}[A-Z]{4}\d{14})|(GI\d{2}[A-Z]{4}\d{15})|(RO\d{2}[A-Z]{4}\d{16})|(MT\d{2}[A-Z]{4}\d{23})|(NO\d{13})|((?:DK|FI)\d{16})|((?:SI)\d{17})|((?:AT|EE|LU|LT)\d{18})|((?:HR|LI|CH)\d{19})|((?:DE|VA)\d{20})|((?:AD|CZ|ES|MD|SK|SE)\d{22})|(PT\d{23})|((?:IS)\d{24})|((?:BE)\d{14})|((?:FR|MC|GR)\d{25})|((?:PL|HU|CY)\d{26}))$',
);
}

View File

@ -20,7 +20,8 @@ import 'package:wyatt_form_bloc/src/form/form.dart';
/// {@template list_option}
/// Form input for a list input
/// {@endtemplate}
class ListOption<T> extends FormInputValidator<List<T>, ValidationError> {
class ListOption<T>
extends FormInputValidator<List<T>, ValidationStandardError> {
/// {@macro list_option}
const ListOption.pure({List<T>? defaultValue})
: super.pure(defaultValue ?? const []);
@ -28,7 +29,7 @@ class ListOption<T> extends FormInputValidator<List<T>, ValidationError> {
/// {@macro list_option}
const ListOption.dirty({List<T>? value}) : super.dirty(value ?? const []);
ListOption select(T? v) {
ListOption<T> select(T? v) {
if (v == null) {
return this;
}
@ -42,7 +43,8 @@ class ListOption<T> extends FormInputValidator<List<T>, ValidationError> {
}
@override
ValidationError? validator(List<T>? value) {
return value?.isNotEmpty == true ? null : ValidationError.invalid;
}
ValidationStandardError? validator(List<T>? value) =>
value?.isNotEmpty ?? false == true
? null
: ValidationStandardError.invalid;
}

View File

@ -15,22 +15,23 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:wyatt_form_bloc/src/enums/enums.dart';
import 'package:wyatt_form_bloc/src/form/form.dart';
import 'package:wyatt_form_bloc/src/validators/inputs/base/regex_validator.dart';
/// {@template name}
/// Form input for a name input.
/// {@endtemplate}
class Name extends FormInputValidator<String, ValidationError> {
class Name extends RegexValidator<ValidationStandardError> {
/// {@macro name}
const Name.pure() : super.pure('');
const Name.pure() : super.pure();
/// {@macro name}
const Name.dirty([String value = '']) : super.dirty(value);
static final RegExp _nameRegExp = RegExp(r"^([ \u00c0-\u01ffa-zA-Z'\-])+$");
const Name.dirty([super.value = '']) : super.dirty();
@override
ValidationError? validator(String? value) {
return _nameRegExp.hasMatch(value ?? '') ? null : ValidationError.invalid;
}
ValidationStandardError get onEmpty => ValidationStandardError.empty;
@override
ValidationStandardError get onError => ValidationStandardError.invalid;
@override
RegExp get regex => RegExp(r"^([ \u00c0-\u01ffa-zA-Z'\-])+$");
}

View File

@ -15,25 +15,23 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:wyatt_form_bloc/src/enums/enums.dart';
import 'package:wyatt_form_bloc/src/form/form.dart';
import 'package:wyatt_form_bloc/src/validators/inputs/base/regex_validator.dart';
/// {@template password}
/// Form input for a password input.
/// {@endtemplate}
class Password extends FormInputValidator<String, ValidationError> {
class Password extends RegexValidator<ValidationStandardError> {
/// {@macro password}
const Password.pure() : super.pure('');
const Password.pure() : super.pure();
/// {@macro password}
const Password.dirty([String value = '']) : super.dirty(value);
static final RegExp _passwordRegExp =
RegExp(r'^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$');
const Password.dirty([super.value = '']) : super.dirty();
@override
ValidationError? validator(String? value) {
return _passwordRegExp.hasMatch(value ?? '')
? null
: ValidationError.invalid;
}
ValidationStandardError get onEmpty => ValidationStandardError.empty;
@override
ValidationStandardError get onError => ValidationStandardError.invalid;
@override
RegExp get regex => RegExp(r'^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$');
}

View File

@ -15,23 +15,24 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:wyatt_form_bloc/src/enums/enums.dart';
import 'package:wyatt_form_bloc/src/form/form.dart';
import 'package:wyatt_form_bloc/src/validators/inputs/base/regex_validator.dart';
/// {@template phone}
/// Form input for a phone input.
/// {@endtemplate}
class Phone extends FormInputValidator<String, ValidationError> {
class Phone extends RegexValidator<ValidationStandardError> {
/// {@macro phone}
const Phone.pure() : super.pure('');
const Phone.pure() : super.pure();
/// {@macro phone}
const Phone.dirty([String value = '']) : super.dirty(value);
static final RegExp _phoneRegExp =
RegExp(r'^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$');
const Phone.dirty([super.value = '']) : super.dirty();
@override
ValidationError? validator(String? value) {
return _phoneRegExp.hasMatch(value ?? '') ? null : ValidationError.invalid;
}
ValidationStandardError get onEmpty => ValidationStandardError.empty;
@override
ValidationStandardError get onError => ValidationStandardError.invalid;
@override
RegExp get regex =>
RegExp(r'^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$');
}

View File

@ -15,22 +15,23 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:wyatt_form_bloc/src/enums/enums.dart';
import 'package:wyatt_form_bloc/src/form/form.dart';
import 'package:wyatt_form_bloc/src/validators/inputs/base/regex_validator.dart';
/// {@template siren}
/// Form input for a SIREN input.
/// {@endtemplate}
class Siren extends FormInputValidator<String, ValidationError> {
class Siren extends RegexValidator<ValidationStandardError> {
/// {@macro siren}
const Siren.pure() : super.pure('');
const Siren.pure() : super.pure();
/// {@macro siren}
const Siren.dirty([String value = '']) : super.dirty(value);
static final RegExp _regExp = RegExp(r'(\d{9}|\d{3}[ ]\d{3}[ ]\d{3})$');
const Siren.dirty([super.value = '']) : super.dirty();
@override
ValidationError? validator(String? value) {
return _regExp.hasMatch(value ?? '') ? null : ValidationError.invalid;
}
ValidationStandardError get onEmpty => ValidationStandardError.empty;
@override
ValidationStandardError get onError => ValidationStandardError.invalid;
@override
RegExp get regex => RegExp(r'(\d{9}|\d{3}[ ]\d{3}[ ]\d{3})$');
}

View File

@ -15,20 +15,21 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:wyatt_form_bloc/src/enums/enums.dart';
import 'package:wyatt_form_bloc/src/form/form.dart';
import 'package:wyatt_form_bloc/src/validators/inputs/base/text_validator.dart';
/// {@template text_string}
/// Form input for a text input
/// {@endtemplate}
class TextString extends FormInputValidator<String, ValidationError> {
class TextString extends TextValidator<ValidationStandardError> {
/// {@macro text_string}
const TextString.pure() : super.pure('');
const TextString.pure() : super.pure();
/// {@macro text_string}
const TextString.dirty([String value = '']) : super.dirty(value);
const TextString.dirty([super.value = '']) : super.dirty();
@override
ValidationError? validator(String? value) {
return (value?.isNotEmpty ?? false) ? null : ValidationError.invalid;
}
ValidationStandardError get onEmpty => ValidationStandardError.empty;
@override
ValidationStandardError get onNull => ValidationStandardError.invalid;
}

View File

@ -14,13 +14,16 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
export 'boolean.dart';
export 'confirmed_password.dart';
export 'email.dart';
export 'iban.dart';
export 'list_option.dart';
export 'name.dart';
export 'password.dart';
export 'phone.dart';
export 'siren.dart';
export 'text_string.dart';
export 'inputs/base/regex_validator.dart';
export 'inputs/base/text_validator.dart';
export 'inputs/boolean.dart';
export 'inputs/confirmed_password.dart';
export 'inputs/email.dart';
export 'inputs/enum_validator.dart';
export 'inputs/iban.dart';
export 'inputs/list_option.dart';
export 'inputs/name.dart';
export 'inputs/password.dart';
export 'inputs/phone.dart';
export 'inputs/siren.dart';
export 'inputs/text_string.dart';

View File

@ -9,7 +9,6 @@ environment:
dependencies:
bloc: ^8.0.3
equatable: ^2.0.3
meta: ^1.7.0
dev_dependencies:
test: ^1.21.4