fpformz 0.1.3 copy "fpformz: ^0.1.3" to clipboard
fpformz: ^0.1.3 copied to clipboard

Functional input validation library based on Fpdart, which is inspired by Formz.

License: MIT ci pub package

FPFormz #

Functional input validation library based on Fpdart, which is inspired by Formz.

Features #

For the most part, FPFormz is similar to the original Formz library, with a few notable differences:

  • FPFormz allows specifying different types for input and validated values, which can be convenient when using non-string type values (e.g. int, enum, value class, etc.).

  • It exposes validated values and errors as functional constructs such as Either or Option, making it easier to manipulate them declaratively.

  • It also provides a way to write validation logic as mixins, which you can combine to handle more complex use cases.

Installation #

You can install PFFormz by adding the following entry in your pubsec.yaml:

# pubspec.yaml
dependencies:
  fpformz: ^0.1.3

Getting Started #

FormInput And Its Derivatives #

To define a validatable input, you need to write a class that extends FormInput<V, I, E> whose generic parameters correspond to the type of resulting value, input value, and potential errors, respectively:

class AgeInput extends FormInput<int, String, ValidationError> {
  const AgeInput.pristine(name, value) : super.pristine(name, value);

  const AgeInput.dirty(name, value) : super.dirty(name, value);

  Either<ValidationError, int> validate(String value) =>
      Either.tryCatch(() => int.parse(value),
              (e, s) => ValidationError(name, '$name should be a number.'));
}

After declaring your input, you can use either pristine or dirty constructor to create an instance:

void example() {
  // To create an unmodified ('pristine') input instance:
  final age = AgeInput.pristine('age', '');

  // Or you can create a modified ('dirty') input instance as below:
  final editedAge = AgeInput.dirty('age', '23');

  print(age.isPristine); // returns 'true'
  print(editedAge.isPristine); // returns 'false'
}

You can access validation information either as a functional construct, or as a nullable:

void example() {
  print(editedAge.isValid); // returns true
  print(editedAge.result); // returns Right(23)
  print(editedAge.resultOrNull); // returns 23
  print(editedAge.error); // returns None
  print(editedAge.errorOrNull); // returns null

  print(age.isValid); // returns false
  print(age.result); // returns Left(ValidationError)
  print(age.resultOrNull); // returns null
  print(age.error); // returns Some(ValidationError)
  print(age.errorOrNull); // returns ValidationError
}

And because most input components treat the user input as a String instance, you can simplify the type signature by extending from StringFormInput:

class NameInput extends StringFormInput<String, ValidationError> {
  const NameInput.pristine(name, value) : super.pristine(name, value);

  const NameInput.dirty(name, value) : super.dirty(name, value);

  @override
  String convert(String value) => value;

  @override
  Either<ValidationError, String> validate(String value) =>
      value.isEmpty
          ? Either.left(ValidationError(name, 'The name cannot be empty.'))
          : super.validate(value);
}

Form #

Like with Formz, you can create a form class to host multiple input fields and validate them together:

class RegistrationForm extends Form {

  final NameInput name;
  final EmailInput email;

  const RegistrationForm({
    this.name = const NameInput.pristine('name', ''),
    this.email = const EmailInput.pristine('email', '')
  });

  @override
  get inputs => [name, email];
}

Then you can validate it using a similar API like that of FormInput:

void example() {
  final form = RegistrationForm();

  print(form.isPristine); // returns true
  print(form.isValid); // returns false

  print(form.result); // it 'short circuits' at the first error encountered
  print(form.errors); // but you can get all errors this way. 
}

Form.result returns a map of all validated input values which you can use to invoke a service method:

void example() {
  final form = RegistrationForm();

  final params = form.resultOrNull;

  service.register(params[form.email.name], params[form.password.name]);

  // Or even like this, provided that the input names match those of the parameters:  
  Function.apply(service.register, [], params);
}

Mixins #

You can also write reusable validation logic as a mixin:

@immutable
mixin NonEmptyString<V> on FormInput<V, String, ValidationError> {
  ValidationError get whenEmpty => ValidationError(name, 'Please enter $name.');

  @override
  Either<ValidationError, V> validate(String value) =>
      value.isEmpty ? Either.left(whenEmpty) : super.validate(value);
}

And build a concrete input field by adding them to either BaseFormInput or StringFormInput as shown below:

class EmailInput extends StringFormInput<Email, ValidationError>
    with EmailString, NonEmptyString {
  const EmailInput.pristine(name, value) : super.pristine(name, value);

  const EmailInput.dirty(name, value) : super.dirty(name, value);

  @override
  Email convert(String value) => Email.parse(value);
}

It's recommended to split each validation logic into a separate mixin rather than putting all into an input class to maximise code reuse and achieve separation of concerns (i.e. the 'S' in SOLID principles).

FPFormz also ships with a small collection of ready-to-use mixins like NonEmptyString , StringShorterThan, which might be expanded in future versions.

Additional Information #

You can find more code examples in our test cases.

2
likes
130
pub points
15%
popularity

Publisher

unverified uploader

Functional input validation library based on Fpdart, which is inspired by Formz.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (LICENSE)

Dependencies

fpdart, meta

More

Packages that depend on fpformz