Skip to content

Add support for async validations #16

@CodingSoot

Description

@CodingSoot

Right now, all validation methods are synchronous, and there is no way to make them async.

@Modddel(
  // ...
)
class Age extends SingleValueObject<InvalidAge, ValidAge> with _$Age {
  Age._();

  factory Age(int value) {
    return _$Age._create(
      value: value,
    );
  }

  // ⚠️ This can't be async
  @override
  Option<AgeLegalFailure> validateLegal(age) {
    if (age.value < 18) {
      return some(const AgeLegalFailure.minor());
    }
    return none();
  }
}

Given that factory constructors can't be async, I only see three options :

Option 1 : Use the builder pattern

We generate a "Builder" that you should instantiate and then call an async method (for example runValidations) to get your modddel instance.

final builder = AgeBuilder(20);

final age = await builder.runValidations();
  • Advantages :
    • Ability to lazily validate a modddel (Although this can be easily accomplished by the user using a package like lazy_evaluation or any other way).
  • Disadvantages :
    • Boilerplate code +++
    • Different syntax for instantiating a modddel with sync validations versus async validations

Option 2 : Initialize the modddel manually

We add an init method to the modddel that the user should call, and its returned value is the initialized modddel.

final age = await Age(20).init();
  • Advantages :
    • Less boilerplate code
  • Disadvantages :
    • ⚠️ RUNTIME ERRORS : If the user forgets to call init or accidentally uses the instance created with the factory constructor (Age(20)) , there will be runtime errors that may be hard to debug
    • Again : different syntax for instantiating a sync modddel versus async

Option 3 : Ditch the factory constructors for async methods

We replace the factory constructors with static methods :

  // Instead of a factory constructor : 
  factory Age(int value) {
    return _$Age._create(
      value: value,
    );
  }

  // We use a static method
  static Age create(int value) {
     // To accompany this change, this static method is no longer private
    // (it never needed to be private since the mixin `_$Age` is private)
    return _$Age.create(
      value: value,
    );
  }

The name of the method should be create for solo modddels, and create{UnionCaseName} for unions (ex for a Weather union : createSunny, createRainy...).

Then, these static methods can easily be made async :

  // Change the return type to a future, and optionally add the async keyword if needed 
  static Future<Age> create(int value) {
    return _$Age.create(
      value: value,
    );
  }

And then for making an instance of the modddel :

// If sync : 
final age = Age.create(20);

// If async :
final age = await Age.create(20);
  • Advantages :
  • Disadvantages :
    • BREAKING CHANGE
    • Static methods can't be unnamed like factory constructors (Age.create(20) vs Age(20)).
    • Static methods don't preserve generic type information (so if you have generics, you need to forward them in a verbose way)
    • Usually, you create an instance with a constructor, and not a static method, so this might be a little less elegant

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestfeedback wantedSuggestions and opinions are needed

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions