{"id":15177526,"url":"https://github.com/flutterando/lucid_validation","last_synced_at":"2025-10-06T15:30:31.444Z","repository":{"id":254539615,"uuid":"846773809","full_name":"Flutterando/lucid_validation","owner":"Flutterando","description":"A Dart/Flutter package for building strongly typed validation rules inspired by FluentValidation and created by the Flutterando community.","archived":false,"fork":false,"pushed_at":"2024-12-19T12:38:02.000Z","size":498,"stargazers_count":7,"open_issues_count":1,"forks_count":5,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-01-16T22:03:36.045Z","etag":null,"topics":["dart","fluent","fluentvalidation","flutter","validation"],"latest_commit_sha":null,"homepage":"https://pub.dev/packages/lucid_validation","language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Flutterando.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-08-23T23:41:16.000Z","updated_at":"2024-12-19T12:37:28.000Z","dependencies_parsed_at":"2024-08-27T14:19:11.249Z","dependency_job_id":"9a295612-503a-4511-8c79-c6e6c8b44e5f","html_url":"https://github.com/Flutterando/lucid_validation","commit_stats":{"total_commits":40,"total_committers":3,"mean_commits":"13.333333333333334","dds":0.35,"last_synced_commit":"d5b1dd816195b293563b498064089da20cab1dd7"},"previous_names":["flutterando/lucid_validation"],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Flutterando%2Flucid_validation","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Flutterando%2Flucid_validation/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Flutterando%2Flucid_validation/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Flutterando%2Flucid_validation/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Flutterando","download_url":"https://codeload.github.com/Flutterando/lucid_validation/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":235532972,"owners_count":19005230,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dart","fluent","fluentvalidation","flutter","validation"],"created_at":"2024-09-27T14:40:19.212Z","updated_at":"2025-10-06T15:30:31.427Z","avatar_url":"https://github.com/Flutterando.png","language":"Dart","readme":"# LucidValidation\n\n**LucidValidation** is a pure Dart package for building strongly typed validation rules, inspired by FluentValidation.\nCreated by the Flutterando community, this package offers a fluent and extensible API for validations, both in\nfrontend (with Flutter) and backend applications.\n\n## Features\n\n- Strongly typed validation rules.\n- Fluent API for defining validations.\n- Extensible with custom validators.\n- Consistent usage across backend and frontend (Flutter).\n\n## Installation\n\nExecute a `pub.add` command:\n\n```sh\ndart pub add lucid_validation\n```\n\n## Basic Usage\n\nFirst, make a model:\n\n```dart\nclass UserModel {\n  String email;\n  String password;\n  int age;\n  DateTime dateOfBirth;\n\n  UserModel({\n    required this.email,\n    required this.password,\n    required this.age,\n    required this.dateOfBirth,\n  });\n\n}\n```\n\nAfter that, create a `LucidValidator` class and extends to `LucidValidator`:\n\n```dart\nimport 'package:lucid_validation/lucid_validation.dart';\n\nclass UserValidator extends LucidValidator\u003cUserModel\u003e {\n  UserValidator() {\n    final now = DateTime.now();\n\n    ruleFor((user) =\u003e user.email, key: 'email')\n        .notEmpty()\n        .validEmail();\n\n    ruleFor((user) =\u003e user.password, key: 'password') //\n        .notEmpty()\n        .minLength(8, message: 'Must be at least 8 characters long')\n        .mustHaveLowercase()\n        .mustHaveUppercase()\n        .mustHaveNumbers()\n        .mustHaveSpecialCharacter();\n\n    ruleFor((user) =\u003e user.age, key: 'age')\n        .min(18, message: 'Minimum age is 18 years');\n\n    ruleFor((user) =\u003e user.dateOfBirth, key: 'dateOfBirth')\n        .lessThan(DateTime(now.year - 18, now.month, now.day));\n  }\n}\n\n```\n\nNow, just validate!\n\n```dart\n\nvoid main() {\n  final user = UserModel(email: 'test@example.com', password: 'Passw0rd!', age: 25);\n  final validator = UserValidator();\n\n  final result = validator.validate(user);\n\n  if (result.isValid) {\n    print('User is valid');\n  } else {\n    print('Validation errors: \\${result.exceptions.map((e) =\u003e e.message).join(', ')}');\n  }\n}\n```\n\nNote, the validate method returns a list of errors with all validation exceptions.\n\n### Available Validations\n\nHere’s a complete list of available validators you can use:\n\n- **must**: custom validation.\n- **mustWith**: custom validation with entity.\n- **equalTo**: checks if value is equal to another value.\n- **greaterThan**: Checks if number is greater than minimum value.\n- **lessThan**: Checks if the number is less than max value.\n- **notEmpty**: Checks if a string is not empty.\n- **matchesPattern**: Checks if the a string matches the pattern (Regex).\n- **range**: Checks whether a number is within the range of a minimum and maximum value.\n- **validEmail**: Checks if a string is a valid email address.\n- **minLength**: Checks if a string has a minimum length.\n- **maxLength**: Checks if a string does not exceed a maximum length.\n- **mustHaveLowercase**: Checks if a string contains at least one lowercase letter.\n- **mustHaveUppercase**: Checks if a string contains at least one uppercase letter.\n- **mustHaveNumbers**: Checks if a string contains at least one number.\n- **mustHaveSpecialCharacter**: Checks if a string contains at least one special character.\n- **min**: Checks if a number is greater than or equal to a minimum value.\n- **max**: Checks if a number is less than or equal to a maximum value.\n- **isNull**: Checks if a value is null.\n- **isNotNull**: Checks if a value is not null.\n- **isEmpty**: Checks if a string is empty.\n- **validCPF**: Checks if a string is a valid CPF (for use in Brazil).\n- **validCNPJ**: Checks if a string is a valid CNPJ (for use in Brazil).\n- **validCEP**: Checks if a string is a valid CEP (for use in Brazil).\n- **validCPFOrCNPJ**: Checks if a string is a valid CPF or CNPJ (for use in Brazil).\n- **validCredCard**: Checks if a string is a valid Credit Card.\n- **greaterThanOrEqualTo**: Checks if the datetime value is greater than or equal to a specified minimum datetime.\n- **greaterThan**: Checks if the datetime value is greater than a specified minimum datetime.\n- **lessThanOrEqualTo**: Checks if the datetime value is less than or equal to a specified maximum datetime.\n- **lessThan**: Checks if the datetime value is less than a specified maximum datetime.\n- **inclusiveBetween**: Checks if the datetime value is between two datetime values, including both bounds.\n- **exclusiveBetween**: Checks if the datetime value is between two datetime values, excluding both bounds.\n- **validPhoneBR:** Check if the strig is a valid brazilian phone number (xx9xxxxxxxx)\n- **validPhoneWithCountryCodeBR:** Check if the strig is a valid brazilian phone number with DDI (55xx9xxxxxxxx)\n- **hasNoSequentialRepeatedCharacters:** Checks if a string does not contain a sequence of repeated characters.\n- **hasNoSequentialCharacters:** Checks if a string does not contain sequential characters.\n\n**Observation: for almost all validators, there is an equivalent with the `OrNull` suffix. Example: `validEmailOrNull`**\n\n## Usage with Flutter\n\nIf you’re using the `lucid_validation` package in a Flutter app, integrating with `TextFormField` is straightforward.\n\nUse the `byField(entity, 'key')` for this:\n\n```dart\nimport 'package:flutter/material.dart';\nimport 'package:lucid_validation/lucid_validation.dart';\n\nclass LoginForm extends StatelessWidget {\n  final validator = CredentialsValidation();\n  final credentials = CredentialsModel();\n\n  @override\n  Widget build(BuildContext context) {\n    return Form(\n      child: Column(\n        children: [\n          TextFormField(\n            decoration: const InputDecoration(hintText: 'Email'),\n            validator: validator.byField(credentials, 'email'),\n          ),\n          TextFormField(\n            decoration: const InputDecoration(hintText: 'Password'),\n            validator: validator.byField(credentials, 'password'),\n            obscureText: true,\n          ),\n        ],\n      ),\n    );\n  }\n}\n```\n\n## Cascate Mode\n\nCascadeMode in LucidValidation controls the behavior of rule execution when a validation failure occurs for a property.\nBy default, the validation rules continue to execute even if a previous rule for the same property fails. However, you\ncan change this behavior using the CascadeMode.\n\n### Available Modes\n\n`CascadeMode.continueExecution (Default)`: All validation rules for a property are executed, even if one fails. This\nmode is useful when you want to collect all validation errors at once.\n\n`CascadeMode.stopOnFirstFailure`: Stops executing further validation rules for a property as soon as a failure is\ndetected. This is useful when you want to prevent unnecessary validation checks after an error has been found.\n\nYou can apply CascadeMode to your validation chain using the cascaded method:\n\n```dart\n return notEmpty() //\n    .minLength(5, message: 'Must be at least 8 characters long')\n    .mustHaveLowercase()\n    .mustHaveUppercase()\n    .mustHaveNumbers()\n    .mustHaveSpecialCharacter()\n    .cascade(CascadeMode.stopOnFirstFailure); // change cascade mode\n```\n\n## When condition\n\nAdds a conditional execution rule for the validation logic based on the given [condition].\n\nThe `when` method allows you to specify a condition that must be met for the validation rules\nwithin this builder to be executed. If the condition is not met, the validation rules areskipped,\nand the property is considered valid by default.\n\nThis is particularly useful for scenarios where certain validation rules should only apply\nunder specific circumstances, such as when a certain property is set to a particular value.\n\n[condition] is a function that takes the entire entity and returns a boolean indicating whether\nthe validation rules should be applied.\n\nExample:\n\n```dart\nruleFor((user) =\u003e user.phoneNumber, key: 'phoneNumber')\n    .when((user) =\u003e user.requiresPhoneNumber)\n    .isEmpty()\n    .must((value) =\u003e value.length == 10, 'Phone number must be 10 digits', 'phone_length');\n```\n\nIn the example above, the phone number validation rules are only applied if the user's `requiresPhoneNumber`\nproperty is true. If the condition is false, the phone number field will be considered valid,and the\nassociated rules will not be executed.\n\n## Complex Validations\n\nWhen working with complex models that contain nested objects, it’s often necessary to apply validation rules not only to\nthe parent model but also to its nested properties. The `setValidator` method allows you to integrate a\nnested `LucidValidator` within another validator, enabling a modular and scalable approach to validation.\n\nSee this example:\n\n```dart\n// Models\nclass Customer {\n  String name;\n  Address address;\n\n  Customer({\n    required this.name,\n    required this.address,\n  });\n}\n\nclass Address {\n  String country;\n  String postcode;\n\n  Address({\n    required this.country,\n    required this.postcode,\n  });\n}\n\n```\n\nNow, we can create two validators, `CustomerValidator` and `AddressValidator`.\nUse `setValidator` to integrate `AddressValidor` into `CustomerValidator`;\n\n```dart\nclass AddressValidator extends LucidValidator\u003cAddress\u003e {\n  AddressValidator() {\n    ruleFor((address) =\u003e address.country, key: 'country') //\n        .notEmpty();\n\n    ruleFor((address) =\u003e address.postcode, key: 'postcode') //\n        .notEmpty();\n  }\n}\n\nclass CustomerValidator extends LucidValidator\u003cCustomer\u003e {\n  final addressValidator = AddressValidator();\n\n  CustomerValidator() {\n    ruleFor((customer) =\u003e customer.name, key: 'name') //\n        .notEmpty();\n\n    ruleFor((customer) =\u003e customer.address, key: 'address') //\n        .setValidator(addressValidator);\n  }\n}\n```\n\nAfter that, execute a validation normaly:\n\n```dart\n\nvar customer = Customer(\n  name: 'John Doe',\n  address: Address(\n    country: 'Brazil',\n    postcode: '12345-678',\n  ),\n);\n\nfinal validator = CustomerValidator();\n\nvar result = validator.validate(customer);\nexpect(result.isValid, isTrue);\n```\n\nYou can use `byField` using nested params syntax:\n\n```dart\n\nfinal validator = CustomerValidator();\n\nfinal postCodeValidator = validator.byField(customer, 'address.postcode')();\nexpect(postCodeValidator, null); // is valid\n\n```\n\nThere are several ways to customize or internationalize the failure message in validation.\n\nAll validations have the `message` parameter for customization, with the possibility of receiving arguments to make the\nmessage more dynamic.\n\n```dart\n  ruleFor((entity) =\u003e entity.name, key: 'name')\n    .isEmpty(message: \"'{PropertyName}' can not be empty.\")\n```\n\nPlease note that the `{PropertyName}` is an exclusive parameter of the `isEmpty` validation that will be internally\nchanged to the validation's `key`, which in this case is `name`.\nEach validation can have different parameters such as `{PropertyValue}` or `{ComparisonValue}`, so please check the\ndocumentation of each one to know the available parameters.\n\n### Validating Lists with `setEach`\n\nWhen your model contains a list of nested objects—like a list of students or items in a cart—it's essential to validate\neach element of that list individually. The setEach method allows you to apply a specific validator to every item in a\nlist.\n\nThis enables fine-grained error reporting, including support for indexing errors to show exactly which item failed\nvalidation.\n\n```dart\nclass Classroom {\n  String className;\n  TeacherModel teacher;\n  List\u003cStudentModel\u003e students;\n\n  Classroom({\n    required this.className,\n    required this.teacher,\n    required this.students,\n  });\n}\n\nclass StudentModel {\n  String name;\n  String email;\n\n  StudentModel({\n    required this.name,\n    required this.email,\n  });\n}\n\nclass TeacherModel {\n  String name;\n\n  TeacherModel({required this.name});\n}\n\n```\n\nNow let's define validators for each class:\n\n```dart\nclass StudentValidator extends LucidValidator\u003cStudentModel\u003e {\n  StudentValidator() {\n    ruleFor((s) =\u003e s.name, key: 'name').notEmpty();\n    ruleFor((s) =\u003e s.email, key: 'email').validEmail();\n  }\n}\n\nclass TeacherModelValidator extends LucidValidator\u003cTeacherModel\u003e {\n  TeacherModelValidator() {\n    ruleFor((t) =\u003e t.name, key: 'name').notEmpty();\n  }\n}\n\nclass ClassroomValidator extends LucidValidator\u003cClassroom\u003e {\n  ClassroomValidator() {\n    ruleFor((c) =\u003e c.className, key: 'className').notEmpty();\n    ruleFor((c) =\u003e c.teacher, key: 'teacher').setValidator(TeacherModelValidator());\n    ruleFor((c) =\u003e c.students, key: 'students').setEach(StudentValidator());\n  }\n}\n```\n\nNow we validate a complex model:\n\n```dart\n\nfinal model = Classroom(\n  className: '', // invalid name\n  teacher: TeacherModel(name: ''), // invalid name\n  students: [\n    StudentModel(name: '', email: 'valid@email.com'), // invalid name\n    StudentModel(name: 'Student 2', email: ''), // invalid email\n    StudentModel(name: 'Student 3', email: 'ok@email'), // valid\n  ],\n);\n\nfinal validator = ClassroomValidator();\nfinal result = validator.validate(model);\nfinal exceptions = result.exceptions;\n```\n\nThe resulting exceptions will contain:\n\n```dart\nexpect(exceptions[0].key, \"className\");\nexpect(exceptions[0].entity, \"Classroom\");\n\nexpect(exceptions[1].key, \"name\");\nexpect(exceptions[1].entity, \"TeacherModel\");\n\nexpect(exceptions[2].key, \"name\");\nexpect(exceptions[2].entity, \"StudentModel\");\nexpect(exceptions[2].index, 0); // first student\n\nexpect(exceptions[3].key, \"email\");\nexpect(exceptions[3].entity, \"StudentModel\");\nexpect(exceptions[3].index, 1); // second student\n```\n\n#### Benefits\n\n- **Modular validation** — Each validator handles only its own logic.\n- **Index tracking** — Automatically adds the .index field to help identify which item failed.\n- **Clear error structure** — Ideal for rendering errors in forms or APIs.\n\n### Default Messages\n\nBy default, validation messages are in English, but you can change the language in the global properties\nof `LucidValidation`.\n\n```dart\nLucidValidation.global.culture = Culture('pt', 'BR');\n```\n\nIf you’d like to contribute a translation of `LucidValidation’s` default messages, please open a pull request that adds\na language file to the project.\n\nYou can also customize the default messages by overriding the `LanguageManager`:\n\n```dart\nclass CustomLanguageManager extends LanguageManager {\n  CustomLanguageManager() {\n    addTranslation(Culture('pt', 'PR'), Language.code.equalTo, 'Custom message here');\n  }\n}...\n// change manager\nLucidValidation.global.languageManager =\n\nCustomLanguageManager();\n\n```\n\n## Flutter Configuration\n\nYou can create a `Delegate` to automate internationalization directly in Flutter.\n\nTo create a `Delegate` follow these steps:\n\n```dart\nclass LucidLocalizationDelegate extends LocalizationsDelegate\u003cCulture\u003e {\n  const LucidLocalizationDelegate();\n\n  static final delegate = LucidLocalizationDelegate();\n\n  @override\n  bool isSupported(Locale locale) {\n    return LucidValidation.global.languageManager.isSupported(\n      locale.languageCode,\n      locale.countryCode,\n    );\n  }\n\n  @override\n  Future\u003cCulture\u003e load(Locale locale) async {\n    print(locale);\n    final culture = Culture(locale.languageCode, locale.countryCode ?? '');\n    LucidValidation.global.culture = culture;\n    return culture;\n  }\n\n  @override\n  bool shouldReload(LocalizationsDelegate\u003cCulture\u003e old) {\n    return true;\n  }\n}\n```\n\nNow just add it to the `MaterialApp` or `CupertinoApp`:\n\n```dart\n  @override\nWidget build(BuildContext context) {\n  return MaterialApp(\n      supportedLocales: const [\n        Locale('en', 'US'),\n        Locale('pt', 'BR'),\n      ],\n      localizationsDelegates: [\n        LucidLocalizationDelegate.delegate,\n        //\n        GlobalMaterialLocalizations.delegate,\n        GlobalWidgetsLocalizations.delegate,\n\n      ],\n      ...\n  );\n}\n```\n\n## Creating Custom Rules\n\nYou can easily extend the functionality of `LucidValidator` by creating your own custom rules using `extensions`. Here’s\nan example of how to create a validation for phone numbers:\n\n```dart\nextension CustomValidPasswordValidator on SimpleValidationBuilder\u003cString\u003e {\n  SimpleValidationBuilder\u003cString\u003e customValidPassword() {\n    return notEmpty()\n        .minLength(8)\n        .mustHaveLowercase()\n        .mustHaveUppercase()\n        .mustHaveNumbers()\n        .mustHaveSpecialCharacter();\n  }\n}\n\nextension CustomValidPhoneValidator on SimpleValidationBuilder\u003cString\u003e {\n  SimpleValidationBuilder\u003cString\u003e customValidPhone({\n    String code = 'validPhone',\n    required String message,\n  }) {\n    return use((value, entity) {\n      final regex = RegExp(\n        r'^\\(?(\\d{2})\\)?\\s?9?\\d{4}-?\\d{4}$',\n        caseSensitive: false,\n      );\n\n      if (regex.hasMatch(value)) {\n        return null;\n      }\n\n      return ValidationException(\n        message: message,\n        code: code,\n        key: key,\n        entity: extractClassName(entity.toString()),\n      );\n    });\n  }\n}\n```\n\nUse directly!\n\n```dart\n\nruleFor((user) =\u003e user.phone, key: 'password') //\n  .customValidPassword();\n\nruleFor((user) =\u003e user.phone, key: 'phone') //\n  .customValidPhone();\n```\n\n## Contributing\n\nFeel free to open issues or pull requests on the [GitHub repository](https://github.com/Flutterando/lucid_validation) if\nyou find any issues or have suggestions for improvements.\n\n## License\n\nThis package is available under the MIT License. See the `LICENSE` file for more details.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflutterando%2Flucid_validation","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fflutterando%2Flucid_validation","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflutterando%2Flucid_validation/lists"}