This package includes code generators that augment classes that are meant to be used as business entities:

  • Business rules validation
  • copyWith method
  • equivalence (== / hashCode)
  • Builder class

Getting Started

Validations

Start by adding the @validatableannotation to the class for which you want validation code to be generated:

/// ensure the library has the part statement.
part 'recipe.data.dart';

@validatable
class Recipe {

    final String title;

    const Recipe({required this.title});
}

This will generate a Validator class that will contain a validation method for each of the properties of the class. By default each validation method will return null, as examplified:

/// This is an example of a generated validator class.
class RecipeValidator implements Validator {
  const RecipeValidator();

  ValidationError? validateTitle(String value) {
    return null;
  }

  @override
  ErrorList validate(covariant Recipe entity) {
    var errors = <ValidationError>[];
    ValidationError? error;
    if ((error = validateTitle(entity.title)) != null) {
      errors.add(error!);
    }

    return ErrorList(errors);
  }

  @override
  void validateThrowing(covariant Recipe entity) {
    var errors = validate(entity);
    if (errors.validationErrors.isNotEmpty) throw errors;
  }

Annotate each field with a rule that you want to apply to that field:

/// ensure the library has the part statement.
part 'ingredient.data.dart';

@validatable
class Ingredient {

  @StringLength(minLength: 10)
  final String description;

  @StringLength(maxLength: 10)
  final String? notes;

  @StringLength(minLength: 2)
  final String? tag;

  @DoubleRange(minValue: 10, maxValue: 20)
  final double quantity;

  @Range(minValue: 10)
  final Decimal precision;

  @Range(minValue: 10, maxValue: 20)
  final int intQuantity;

  @Range(minValue: 10, maxValue: 20)
  final int? nintQuantity;

  @Range(minValue: 10, maxValue: 20)
  @required
  final int? rInt;

  Ingredient({
    required this.description,
    required this.quantity,
    required this.precision,
    required this.intQuantity,
    this.notes,
    this.tag,
    this.nintQuantity,
    this.rInt,
  });
}

Builder

Add a @builder annotation:

/// ensure the library has the part statement.
part 'recipe.data.dart';

@builder
class Recipe {
  final String title;

  final String? description;
  
  Recipe({
    required this.title,
    this.description,
  });
}

This will generate a non-immutable builder class:

class RecipeBuilder implements Builder<Recipe> {
  String title;
  String? description;

  RecipeBuilder({
    required this.title,
    this.description,
  });

  factory RecipeBuilder.fromRecipe(Recipe entity) {
    return RecipeBuilder(
      title: entity.title,
      description: entity.description,
    );
  }

  @override
  Recipe build() {
    var entity = Recipe(
      title: title,
      description: description,
    );
    RecipeValidator().validateThrowing(entity);
    return entity;
  }
}

copyWith and equivalence

Add a @builder annotation:

/// ensure the library has the part statement.
part 'recipe.data.dart';

@data
class Recipe with _$Recipe {
  final String title;

  final String? description;
  
  Recipe({
    required this.title,
    this.description,
  });
}

This will generate a mixin that adds the copyWith method and the == and hashCode operators:

// **************************************************************************
// DataGenerator
// **************************************************************************

mixin _$Recipe {
  String get title;
  String? get description;

  Recipe copyWith({
    String? title,
    CopyValue<String>? description,
  }) {
    return Recipe(
      title: title ?? this.title,
      description: description == null ? this.description : description.value,
    );
  }

  @override
  int get hashCode => Object.hash(
    runtimeType,
    title,
    description,
  );

  @override
  bool operator ==(Object other) {
    return identical(this, other) ||
        (other.runtimeType == runtimeType &&
            other is Recipe &&
            (identical(other.title, title) || other.title == title) &&
            (identical(other.description, description) ||
                other.description == description));
  }
}

Context

This package is part of a set of loosely integrated packages that constitute the Dartaculous Framework.