diff --git a/CHANGELOG.md b/CHANGELOG.md
index 165a771..2732955 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,15 @@
+## 1.2.0
+
+* Added 21 new validators inspired by validator.js: `isLowercase`, `isUppercase`,
+ `isHexadecimal`, `isOctal`, `isDecimal`, `isFloat`, `isMongoId`, `isMD5`,
+ `isPort`, `isSemVer`, `isSlug`, `isMACAddress`, `isLatLong`, `isJWT`, `isFQDN`,
+ `isBase64`, `isByteLength`, `isStrongPassword`, `isIn`, `matches` and `contains`.
+* Added a new sanitizers module (`package:flutter_validators/sanitizers.dart`):
+ `trim`, `ltrim`, `rtrim`, `escape`, `unescape`, `blacklist`, `whitelist`,
+ `stripLow`, `normalizeEmail`, `toBoolean`, `toInt`, `toFloat` and `toDate`.
+* Extended the `Validator` form class with methods for every new validator.
+* Added test coverage for all new validators and sanitizers.
+
## 1.1.0
* Major upgrade! Added 9 new standard validators (URL, UUID, Date, Numeric, Alpha, IP, Hex Color, Credit Card, Length).
diff --git a/README.md b/README.md
index f249ef4..30751b4 100644
--- a/README.md
+++ b/README.md
@@ -18,20 +18,64 @@
- Inspired by validator.js · 20+ validators · Works with Flutter Forms out of the box
+ Inspired by validator.js · 40+ validators & sanitizers · Works with Flutter Forms out of the box
---
-A pure Dart package with 20+ string validators and sanitizers, from emails and URLs to credit cards and UUIDs. Use them as simple functions, convenient `String` extensions, or plug them directly into Flutter's `TextFormField` with the built-in `Validator` class. Zero dependencies, fully tested.
+**Flutter Validators** is a pure Dart package with **40+ string validators** and **13 sanitizers** — from emails and URLs to credit cards, UUIDs, JWTs and strong-password checks. Every validator works three ways:
+
+- as a **top-level function** — `isEmail('foo@bar.com')`
+- as a **`String` extension** — `'foo@bar.com'.isEmail`
+- as a **Flutter form validator** — `Validator.email()` plugs straight into `TextFormField`
+
+Zero runtime dependencies. Fully tested. Works with both Dart and Flutter.
+
+---
+
+## 📚 Table of Contents
+
+- [✨ Features](#-features)
+- [📦 Installation](#-installation)
+- [🚀 Quick Start](#-quick-start)
+- [🧩 Validators](#-validators)
+ - [Contact and Web](#contact-and-web)
+ - [Numbers](#numbers)
+ - [Text and Format](#text-and-format)
+ - [Encoding and Data](#encoding-and-data)
+ - [Identifiers and Crypto](#identifiers-and-crypto)
+ - [Security](#security)
+- [🧹 Sanitizers](#-sanitizers)
+ - [Trimming](#trimming)
+ - [HTML Escaping](#html-escaping)
+ - [Character Filtering](#character-filtering)
+ - [Type Conversion](#type-conversion)
+ - [Email Normalization](#email-normalization)
+- [📝 Flutter Form Integration](#-flutter-form-integration)
+- [💡 Behavior Notes and FAQ](#-behavior-notes-and-faq)
+- [🤝 Contributing](#-contributing)
+- [📄 License](#-license)
+
+---
+
+## ✨ Features
+
+- **40+ validators** covering email, URL, numbers, encodings, identifiers, crypto hashes and more.
+- **13 sanitizers** for trimming, HTML escaping, character filtering and type conversion.
+- **Three usage styles** — top-level functions, `String` extensions, and Flutter form validators — pick whatever reads best.
+- **First-class Flutter form support** via the `Validator` class, which returns `String? Function(String?)` closures with customizable error messages.
+- **Pure Dart, zero runtime dependencies** — lightweight and safe to add to any project.
+- **Fully tested** — every validator and sanitizer has dedicated test coverage.
---
## 📦 Installation
+Add the package to your `pubspec.yaml`:
+
```yaml
dependencies:
- flutter_validators: ^1.1.0
+ flutter_validators: ^1.2.0
```
Then run:
@@ -44,121 +88,433 @@ dart pub get
## 🚀 Quick Start
+Import the package:
+
```dart
import 'package:flutter_validators/flutter_validators.dart';
```
-### Use as String Extensions
+**As `String` extensions** — the most concise style:
```dart
-'foo@bar.com'.isEmail; // true
-'https://google.com'.isURL; // true
+'foo@bar.com'.isEmail; // true
+'https://google.com'.isURL; // true
'4111111111111111'.isCreditCard; // true
-'abc123'.isAlphanumeric; // true
+'abc123'.isAlphanumeric; // true
```
-### Use as Top-Level Functions
+**As top-level functions** — handy when the value isn't a literal:
```dart
-isEmail('foo@bar.com'); // true
+isEmail('foo@bar.com'); // true
isURL('https://google.com'); // true
-isIP('192.168.1.1'); // true
+isIP('192.168.1.1'); // true
```
----
-
-## 📝 Flutter Form Integration
-
-The `Validator` class returns `String? Function(String?)` closures — exactly what `TextFormField.validator` expects. Each method accepts a custom `errorMessage`.
+**As Flutter form validators** — drop straight into `TextFormField`:
```dart
-Form(
- child: Column(
- children: [
- TextFormField(
- decoration: const InputDecoration(labelText: 'Email'),
- validator: Validator.email(errorMessage: 'Enter a valid email'),
- ),
- TextFormField(
- decoration: const InputDecoration(labelText: 'Website'),
- validator: Validator.url(),
- ),
- TextFormField(
- decoration: const InputDecoration(labelText: 'Age'),
- validator: Validator.numeric(errorMessage: 'Must be a number'),
- ),
- ],
- ),
+TextFormField(
+ validator: Validator.email(errorMessage: 'Enter a valid email'),
)
```
-> **Tip:** Use `Validator.required()` alongside other validators to enforce non-empty fields.
-
-See the [`example/`](example/) directory for a complete working app.
-
---
-## 📋 All Validators
+## 🧩 Validators
+
+Every validator is available **both** as a top-level function and as a `String` extension. Parameterized validators accept their options as named/positional arguments.
-Every validator is available **both** as a top-level function and as a `String` extension.
+### Contact and Web
| Validator | Extension | Description |
|---|---|---|
| `isEmail(str)` | `str.isEmail` | Valid email address |
| `isURL(str)` | `str.isURL` | Valid HTTP/HTTPS URL |
-| `isIP(str, [version])` | `str.isIP` / `str.isIPv4` / `str.isIPv6` | Valid IP address (v4 or v6) |
-| `isUUID(str)` | `str.isUUID` | Valid UUID |
-| `isCreditCard(str)` | `str.isCreditCard` | Credit card number (Luhn algorithm) |
-| `isDate(str)` | `str.isDate` | Parseable date string |
-| `isJson(str)` | `str.isJson` | Valid JSON |
-| `isInt(str)` | `str.isInt` | Valid integer |
-| `isNumeric(str)` | `str.isNumeric` | Valid number (int or float) |
+| `isFQDN(str)` | `str.isFQDN` | Fully qualified domain name |
+| `isPhone(str)` | `str.isPhone` | Phone number (international & US formats) |
+| `isLatLong(str)` | `str.isLatLong` | `latitude,longitude` coordinate pair |
+
+```dart
+'user@example.com'.isEmail; // true
+'https://dart.dev'.isURL; // true
+'sub.example.co.uk'.isFQDN; // true
+'localhost'.isFQDN; // false (no TLD)
+'(123) 456-7890'.isPhone; // true
+'40.7128,-74.0060'.isLatLong; // true
+```
+
+### Numbers
+
+| Validator | Extension | Description |
+|---|---|---|
+| `isInt(str)` | `str.isInt` | Integer (positive or negative) |
+| `isNumeric(str)` | `str.isNumeric` | Number (integer or float) |
+| `isFloat(str, {min, max})` | `str.isFloat({min, max})` | Finite float, optionally within a range |
+| `isDecimal(str)` | `str.isDecimal` | Decimal number |
+| `isHexadecimal(str)` | `str.isHexadecimal` | Hexadecimal number |
+| `isOctal(str)` | `str.isOctal` | Octal number |
+| `isPort(str)` | `str.isPort` | Port number (0–65535) |
+
+```dart
+'42'.isInt; // true
+'3.14'.isNumeric; // true
+'1.5'.isFloat(); // true
+'5'.isFloat(min: 0, max: 2); // false (out of range)
+'.5'.isDecimal; // true
+'deadBEEF'.isHexadecimal; // true
+'0o17'.isOctal; // true
+'8080'.isPort; // true
+'65536'.isPort; // false (out of range)
+```
+
+### Text and Format
+
+| Validator | Extension | Description |
+|---|---|---|
| `isAlpha(str)` | `str.isAlpha` | Letters only (a–z, A–Z) |
| `isAlphanumeric(str)` | `str.isAlphanumeric` | Letters and numbers only |
| `isAscii(str)` | `str.isAscii` | ASCII characters only |
+| `isLowercase(str)` | `str.isLowercase` | Entirely lowercase |
+| `isUppercase(str)` | `str.isUppercase` | Entirely uppercase |
+| `isLength(str, min, [max])` | `str.isLength(min, [max])` | Length within a range |
+| `isByteLength(str, min, [max])` | `str.isByteLength(min, [max])` | UTF-8 byte length within a range |
+| `isSlug(str)` | `str.isSlug` | URL slug (`my-blog-post`) |
+| `isIn(str, values)` | `str.isIn(values)` | One of an allowed set of values |
+| `matches(str, pattern)` | `str.matches(pattern)` | Matches a `Pattern` / `RegExp` |
+| `contains(str, seed, {ignoreCase, minOccurrences})` | — | Contains a substring |
+| `equals(str, comparison)` | `str.equals(comparison)` | Exact (case-sensitive) string match |
+
+```dart
+'Hello'.isAlpha; // true
+'abc123'.isAlphanumeric; // true
+'héllo'.isAscii; // false
+'hello'.isLowercase; // true
+'abc'.isLength(2, 5); // true
+'é'.isByteLength(2, 2); // true ('é' is 2 bytes in UTF-8)
+'my-blog-post'.isSlug; // true
+'red'.isIn(['red', 'green', 'blue']); // true
+'abc123'.matches(RegExp(r'\d+')); // true
+'foo'.equals('foo'); // true
+
+// `contains` is a top-level function only (see Behavior Notes)
+contains('hello world', 'world'); // true
+contains('Hello World', 'world', ignoreCase: true); // true
+contains('a-a-a', 'a', minOccurrences: 3); // true
+```
+
+### Encoding and Data
+
+| Validator | Extension | Description |
+|---|---|---|
| `isBase32(str)` | `str.isBase32` | Base32 encoded |
| `isBase58(str)` | `str.isBase58` | Base58 encoded |
-| `isBoolean(str)` | `str.isBoolean` | Boolean string (`true`/`false`/`1`/`0`) |
+| `isBase64(str, {urlSafe})` | `str.isBase64({urlSafe})` | Base64 encoded (standard or URL-safe) |
+| `isJson(str)` | `str.isJson` | Valid JSON |
| `isHexColor(str)` | `str.isHexColor` | Hex color code (`#fff`, `ff0000`) |
-| `isPhone(str)` | `str.isPhone` | Valid phone number |
-| `isLength(str, min, [max])` | `str.isLength(min, [max])` | Length within range |
-| `equals(str, comparison)` | `str.equals(comparison)` | Exact string match |
+| `isBoolean(str)` | `str.isBoolean` | Boolean string (`true`/`false`/`1`/`0`) |
+| `isDate(str)` | `str.isDate` | Parseable date string |
+
+```dart
+'JBSWY3DP'.isBase32; // true
+'aGVsbG8='.isBase64(); // true
+'a-b_cdef'.isBase64(urlSafe: true); // true
+'{"name":"Dart"}'.isJson; // true
+'#ff0000'.isHexColor; // true
+'true'.isBoolean; // true
+'2024-01-15'.isDate; // true
+```
+
+### Identifiers and Crypto
+
+| Validator | Extension | Description |
+|---|---|---|
+| `isUUID(str)` | `str.isUUID` | UUID (v1, v3, v4, v5) |
+| `isMongoId(str)` | `str.isMongoId` | MongoDB ObjectId (24-char hex) |
+| `isMD5(str)` | `str.isMD5` | MD5 hash |
+| `isJWT(str)` | `str.isJWT` | JSON Web Token |
+| `isCreditCard(str)` | `str.isCreditCard` | Credit card number (Luhn algorithm) |
+| `isMACAddress(str)` | `str.isMACAddress` | MAC address (EUI-48 / EUI-64) |
+| `isSemVer(str)` | `str.isSemVer` | Semantic version |
+
+```dart
+'550e8400-e29b-41d4-a716-446655440000'.isUUID; // true
+'507f1f77bcf86cd799439011'.isMongoId; // true
+'d41d8cd98f00b204e9800998ecf8427e'.isMD5; // true
+'eyJhbGci.eyJzdWIi.SflKxwRJ'.isJWT; // true
+'4111111111111111'.isCreditCard; // true
+'00:1B:44:11:3A:B7'.isMACAddress; // true
+'2.1.0-alpha.1'.isSemVer; // true
+```
+
+### Security
+
+| Validator | Extension | Description |
+|---|---|---|
+| `isStrongPassword(str, {...})` | `str.isStrongPassword({...})` | Password meets configurable strength rules |
+
+`isStrongPassword` accepts five options, all with sensible defaults:
+
+| Option | Default | Meaning |
+|---|---|---|
+| `minLength` | `8` | Minimum total length |
+| `minLowercase` | `1` | Minimum lowercase letters |
+| `minUppercase` | `1` | Minimum uppercase letters |
+| `minNumbers` | `1` | Minimum digits |
+| `minSymbols` | `1` | Minimum non-alphanumeric symbols |
+
+```dart
+'Abcd1234!'.isStrongPassword(); // true
+'weak'.isStrongPassword(); // false
+
+// Relax the rules — e.g. allow passphrases with no symbols or digits
+'abcdefghij'.isStrongPassword(
+ minUppercase: 0,
+ minNumbers: 0,
+ minSymbols: 0,
+); // true
+```
---
-## 🏗️ Form Validator API Reference
+## 🧹 Sanitizers
-All methods on the `Validator` class return `String? Function(String?)`:
+Sanitizers transform or coerce strings. Like validators, they're available as both top-level functions and `String` extensions. Import them via the main library or directly:
+
+```dart
+import 'package:flutter_validators/flutter_validators.dart';
+// or, sanitizers only:
+import 'package:flutter_validators/sanitizers.dart';
+```
+
+### Trimming
+
+| Sanitizer | Extension | Description |
+|---|---|---|
+| `trim(str, [chars])` | `str.trimChars(chars)` | Trim whitespace/chars from both ends |
+| `ltrim(str, [chars])` | `str.ltrimChars(chars)` | Trim from the start |
+| `rtrim(str, [chars])` | `str.rtrimChars(chars)` | Trim from the end |
+
+```dart
+trim(' hello '); // 'hello'
+trim('xxhelloxx', 'x'); // 'hello'
+ltrim('00042', '0'); // '42'
+rtrim('hello!!!', '!'); // 'hello'
+```
+
+### HTML Escaping
+
+| Sanitizer | Extension | Description |
+|---|---|---|
+| `escape(str)` | `str.escape()` | Escape HTML-unsafe characters |
+| `unescape(str)` | `str.unescape()` | Reverse of `escape` |
+
+```dart
+escape('');
+// '<script>alert(1)</script>'
+
+unescape('<b>hi</b>'); // 'hi'
+```
+
+### Character Filtering
+
+| Sanitizer | Extension | Description |
+|---|---|---|
+| `blacklist(str, chars)` | `str.blacklist(chars)` | Remove the listed characters |
+| `whitelist(str, chars)` | `str.whitelist(chars)` | Keep only the listed characters |
+| `stripLow(str, {keepNewLines})` | `str.stripLow({keepNewLines})` | Remove ASCII control characters |
+
+```dart
+blacklist('hello world', 'lo'); // 'he wrd'
+whitelist('a1b2c3', '0123456789'); // '123'
+stripLow('line1\nline2'); // 'line1line2'
+stripLow('line1\nline2', keepNewLines: true); // 'line1\nline2'
+```
+
+### Type Conversion
+
+| Sanitizer | Extension | Returns | Description |
+|---|---|---|---|
+| `toBoolean(str, {strict})` | `str.toBoolean({strict})` | `bool` | Convert to a boolean |
+| `toInt(str, {radix})` | `str.toInt({radix})` | `int?` | Parse to an integer |
+| `toFloat(str)` | `str.toFloat()` | `double?` | Parse to a double |
+| `toDate(str)` | `str.toDate()` | `DateTime?` | Parse to a `DateTime` |
+
+```dart
+toBoolean('true'); // true
+toBoolean('0'); // false
+toBoolean('yes', strict: true); // false (strict: only '1'/'true' are true)
+toInt('42'); // 42
+toInt('ff', radix: 16); // 255
+toInt('abc'); // null
+toFloat('3.14'); // 3.14
+toDate('2024-01-15'); // DateTime(2024, 1, 15)
+```
+
+### Email Normalization
+
+| Sanitizer | Extension | Returns | Description |
+|---|---|---|---|
+| `normalizeEmail(str)` | `str.normalizeEmail()` | `String?` | Canonicalize an email address |
+
+```dart
+normalizeEmail('Test.User+promo@GMAIL.com'); // 'testuser@gmail.com'
+normalizeEmail('User@Example.COM'); // 'User@example.com'
+normalizeEmail('not-an-email'); // null
+```
+
+---
+
+## 📝 Flutter Form Integration
+
+The `Validator` class returns `String? Function(String?)` closures — exactly the type `TextFormField.validator` expects. A closure returns `null` when the value is valid, or the error message when it isn't. Every method accepts a custom `errorMessage`.
+
+```dart
+import 'package:flutter/material.dart';
+import 'package:flutter_validators/flutter_validators.dart';
+
+class SignUpForm extends StatefulWidget {
+ const SignUpForm({super.key});
+
+ @override
+ State createState() => _SignUpFormState();
+}
+
+class _SignUpFormState extends State {
+ final _formKey = GlobalKey();
+
+ @override
+ Widget build(BuildContext context) {
+ return Form(
+ key: _formKey,
+ child: Column(
+ children: [
+ // Combine `required` with `email` to enforce a non-empty, valid email.
+ TextFormField(
+ decoration: const InputDecoration(labelText: 'Email'),
+ autovalidateMode: AutovalidateMode.onUserInteraction,
+ validator: (value) {
+ return Validator.required(errorMessage: 'Email is required')(value) ??
+ Validator.email(errorMessage: 'Enter a valid email')(value);
+ },
+ ),
+ TextFormField(
+ decoration: const InputDecoration(labelText: 'Website'),
+ autovalidateMode: AutovalidateMode.onUserInteraction,
+ validator: Validator.url(),
+ ),
+ TextFormField(
+ decoration: const InputDecoration(labelText: 'Password'),
+ obscureText: true,
+ autovalidateMode: AutovalidateMode.onUserInteraction,
+ validator: Validator.strongPassword(
+ errorMessage: 'Use 8+ chars with upper, lower, number & symbol',
+ ),
+ ),
+ ElevatedButton(
+ onPressed: () {
+ if (_formKey.currentState!.validate()) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(content: Text('Form is valid!')),
+ );
+ }
+ },
+ child: const Text('Sign Up'),
+ ),
+ ],
+ ),
+ );
+ }
+}
+```
+
+A complete, runnable app is in the [`example/`](example/) directory.
+
+### Validator API Reference
+
+Every method on the `Validator` class returns `String? Function(String?)`:
```dart
Validator.required({String errorMessage})
Validator.email({String errorMessage})
Validator.url({String errorMessage})
Validator.ip({int? version, String errorMessage})
+Validator.fqdn({String errorMessage})
+Validator.phone({String errorMessage})
+Validator.latLong({String errorMessage})
Validator.date({String errorMessage})
Validator.numeric({String errorMessage})
Validator.integer({String errorMessage})
+Validator.float({double? min, double? max, String errorMessage})
+Validator.decimal({String errorMessage})
+Validator.hexadecimal({String errorMessage})
+Validator.octal({String errorMessage})
+Validator.port({String errorMessage})
Validator.alpha({String errorMessage})
Validator.alphanumeric({String errorMessage})
-Validator.phone({String errorMessage})
-Validator.creditCard({String errorMessage})
-Validator.json({String errorMessage})
-Validator.uuid({String errorMessage})
-Validator.hexColor({String errorMessage})
Validator.ascii({String errorMessage})
+Validator.lowercase({String errorMessage})
+Validator.uppercase({String errorMessage})
+Validator.slug({String errorMessage})
+Validator.length(int min, {int? max, String errorMessage})
+Validator.byteLength(int min, {int? max, String errorMessage})
+Validator.contains(String seed, {bool ignoreCase, int minOccurrences, String errorMessage})
+Validator.matches(Pattern pattern, {String errorMessage})
+Validator.inList(Iterable allowed, {String errorMessage})
+Validator.equals(String comparison, {String errorMessage})
Validator.base32({String errorMessage})
Validator.base58({String errorMessage})
+Validator.base64({bool urlSafe, String errorMessage})
+Validator.json({String errorMessage})
+Validator.hexColor({String errorMessage})
Validator.boolean({String errorMessage})
-Validator.equals(String comparison, {String errorMessage})
-Validator.length(int min, {int? max, String errorMessage})
+Validator.uuid({String errorMessage})
+Validator.mongoId({String errorMessage})
+Validator.md5({String errorMessage})
+Validator.jwt({String errorMessage})
+Validator.creditCard({String errorMessage})
+Validator.macAddress({String errorMessage})
+Validator.semVer({String errorMessage})
+Validator.strongPassword({int minLength, int minLowercase, int minUppercase, int minNumbers, int minSymbols, String errorMessage})
```
---
+## 💡 Behavior Notes and FAQ
+
+**`Validator` methods treat `null` and empty strings as valid.** This is intentional — it lets you compose validators freely. To make a field mandatory, pair it with `Validator.required()`:
+
+```dart
+validator: (value) {
+ return Validator.required()(value) ?? Validator.email()(value);
+}
+```
+
+**`contains` is a top-level function only.** Dart's `String` already has a built-in `.contains()` method, so the package does not add a conflicting extension. Use `contains(str, seed)` instead of `str.contains(...)` when you need the case-insensitivity or `minOccurrences` options.
+
+**Trimming extensions are named `trimChars` / `ltrimChars` / `rtrimChars`.** Dart's `String` already provides `.trim()`, `.trimLeft()` and `.trimRight()` for whitespace, so the custom-character variants use distinct names to avoid collisions. The top-level functions keep the plain `trim` / `ltrim` / `rtrim` names.
+
+**`isURL` accepts only `http` and `https` schemes.** Other schemes such as `ftp://` are rejected.
+
+**`isBase64` has a `urlSafe` option.** By default it validates the standard Base64 alphabet (with padding); pass `urlSafe: true` to validate the URL- and filename-safe alphabet instead.
+
+**`isBoolean` accepts `'true'`, `'false'`, `'1'` and `'0'`.** Any other value is not a boolean string.
+
+**`isFloat` rejects non-finite values.** `'Infinity'` and `'NaN'` return `false`, even though Dart's `double.tryParse` can parse them.
+
+**`normalizeEmail` applies Gmail-specific rules.** For `gmail.com` / `googlemail.com` addresses it lowercases the local part, removes dots, and strips any `+tag` suffix. For other providers it only lowercases the domain. It returns `null` if the input isn't a valid email.
+
+---
+
## 🤝 Contributing
-Contributions, issues, and feature requests are welcome!
-Feel free to check the [issues page](https://github.com/divyanshub024/flutter_validators/issues).
+Contributions, issues and feature requests are welcome! Check the [issues page](https://github.com/divyanshub024/flutter_validators/issues) to get started.
+
+Before opening a pull request, please run the test suite:
+
+```sh
+dart test
+```
---
diff --git a/lib/flutter_validators.dart b/lib/flutter_validators.dart
index de1d79e..eb74ecb 100644
--- a/lib/flutter_validators.dart
+++ b/lib/flutter_validators.dart
@@ -1,22 +1,45 @@
library;
+export 'validators/alpha.dart';
export 'validators/ascii.dart';
export 'validators/base32.dart';
export 'validators/base58.dart';
+export 'validators/base64.dart';
export 'validators/boolean.dart';
-export 'validators/email.dart';
-export 'validators/equals.dart';
-export 'validators/int.dart';
-export 'validators/json.dart';
-export 'validators/phone.dart';
-export 'validators/alpha.dart';
+export 'validators/byte_length.dart';
+export 'validators/contains.dart';
export 'validators/credit_card.dart';
export 'validators/date.dart';
+export 'validators/decimal.dart';
+export 'validators/email.dart';
+export 'validators/equals.dart';
+export 'validators/float.dart';
+export 'validators/fqdn.dart';
export 'validators/hex_color.dart';
+export 'validators/hexadecimal.dart';
+export 'validators/in.dart';
+export 'validators/int.dart';
export 'validators/ip.dart';
+export 'validators/json.dart';
+export 'validators/jwt.dart';
+export 'validators/lat_long.dart';
export 'validators/length.dart';
+export 'validators/lowercase.dart';
+export 'validators/mac_address.dart';
+export 'validators/matches.dart';
+export 'validators/md5.dart';
+export 'validators/mongo_id.dart';
export 'validators/numeric.dart';
+export 'validators/octal.dart';
+export 'validators/phone.dart';
+export 'validators/port.dart';
+export 'validators/semver.dart';
+export 'validators/slug.dart';
+export 'validators/strong_password.dart';
+export 'validators/uppercase.dart';
export 'validators/url.dart';
export 'validators/uuid.dart';
+export 'sanitizers.dart';
+
export 'form_validator.dart';
diff --git a/lib/form_validator.dart b/lib/form_validator.dart
index e2cd843..6e2f089 100644
--- a/lib/form_validator.dart
+++ b/lib/form_validator.dart
@@ -1,4 +1,5 @@
import 'package:flutter_validators/flutter_validators.dart';
+import 'package:flutter_validators/validators/contains.dart' as contains_fn;
/// A utility class for Flutter Form validation.
/// Provides methods that return a validator function suitable for `TextFormField`.
@@ -155,6 +156,185 @@ class Validator {
return _build(errorMessage, (v) => v.isLength(min, max));
}
+ /// Ensures the string is entirely lowercase.
+ static String? Function(String?) lowercase({
+ String errorMessage = 'Must be lowercase',
+ }) {
+ return _build(errorMessage, (v) => v.isLowercase);
+ }
+
+ /// Ensures the string is entirely uppercase.
+ static String? Function(String?) uppercase({
+ String errorMessage = 'Must be uppercase',
+ }) {
+ return _build(errorMessage, (v) => v.isUppercase);
+ }
+
+ /// Ensures the string is a hexadecimal number.
+ static String? Function(String?) hexadecimal({
+ String errorMessage = 'Please enter a valid hexadecimal number',
+ }) {
+ return _build(errorMessage, (v) => v.isHexadecimal);
+ }
+
+ /// Ensures the string is an octal number.
+ static String? Function(String?) octal({
+ String errorMessage = 'Please enter a valid octal number',
+ }) {
+ return _build(errorMessage, (v) => v.isOctal);
+ }
+
+ /// Ensures the string is a valid MongoDB ObjectId.
+ static String? Function(String?) mongoId({
+ String errorMessage = 'Please enter a valid MongoDB ObjectId',
+ }) {
+ return _build(errorMessage, (v) => v.isMongoId);
+ }
+
+ /// Ensures the string is a valid MD5 hash.
+ static String? Function(String?) md5({
+ String errorMessage = 'Please enter a valid MD5 hash',
+ }) {
+ return _build(errorMessage, (v) => v.isMD5);
+ }
+
+ /// Ensures the string is a valid port number.
+ static String? Function(String?) port({
+ String errorMessage = 'Please enter a valid port number',
+ }) {
+ return _build(errorMessage, (v) => v.isPort);
+ }
+
+ /// Ensures the string is a valid Semantic Version.
+ static String? Function(String?) semVer({
+ String errorMessage = 'Please enter a valid semantic version',
+ }) {
+ return _build(errorMessage, (v) => v.isSemVer);
+ }
+
+ /// Ensures the string is a valid URL slug.
+ static String? Function(String?) slug({
+ String errorMessage = 'Please enter a valid slug',
+ }) {
+ return _build(errorMessage, (v) => v.isSlug);
+ }
+
+ /// Ensures the string is a valid MAC address.
+ static String? Function(String?) macAddress({
+ String errorMessage = 'Please enter a valid MAC address',
+ }) {
+ return _build(errorMessage, (v) => v.isMACAddress);
+ }
+
+ /// Ensures the string is a valid `latitude,longitude` pair.
+ static String? Function(String?) latLong({
+ String errorMessage = 'Please enter valid coordinates',
+ }) {
+ return _build(errorMessage, (v) => v.isLatLong);
+ }
+
+ /// Ensures the string is a valid JSON Web Token.
+ static String? Function(String?) jwt({
+ String errorMessage = 'Please enter a valid JWT',
+ }) {
+ return _build(errorMessage, (v) => v.isJWT);
+ }
+
+ /// Ensures the string is a fully qualified domain name.
+ static String? Function(String?) fqdn({
+ String errorMessage = 'Please enter a valid domain name',
+ }) {
+ return _build(errorMessage, (v) => v.isFQDN);
+ }
+
+ /// Ensures the string is Base64 encoded.
+ static String? Function(String?) base64({
+ bool urlSafe = false,
+ String errorMessage = 'Please enter a valid Base64 encoded string',
+ }) {
+ return _build(errorMessage, (v) => v.isBase64(urlSafe: urlSafe));
+ }
+
+ /// Ensures the string represents a decimal number.
+ static String? Function(String?) decimal({
+ String errorMessage = 'Please enter a valid decimal number',
+ }) {
+ return _build(errorMessage, (v) => v.isDecimal);
+ }
+
+ /// Ensures the string contains the [seed] substring.
+ static String? Function(String?) contains(
+ String seed, {
+ bool ignoreCase = false,
+ int minOccurrences = 1,
+ String errorMessage = 'Required text is missing',
+ }) {
+ return _build(
+ errorMessage,
+ (v) => contains_fn.contains(
+ v,
+ seed,
+ ignoreCase: ignoreCase,
+ minOccurrences: minOccurrences,
+ ),
+ );
+ }
+
+ /// Ensures the string matches the given [pattern].
+ static String? Function(String?) matches(
+ Pattern pattern, {
+ String errorMessage = 'Invalid format',
+ }) {
+ return _build(errorMessage, (v) => v.matches(pattern));
+ }
+
+ /// Ensures the string is one of the [allowed] values.
+ static String? Function(String?) inList(
+ Iterable allowed, {
+ String errorMessage = 'Value is not allowed',
+ }) {
+ return _build(errorMessage, (v) => isIn(v, allowed));
+ }
+
+ /// Ensures the string is a finite floating-point number.
+ static String? Function(String?) float({
+ double? min,
+ double? max,
+ String errorMessage = 'Please enter a valid number',
+ }) {
+ return _build(errorMessage, (v) => v.isFloat(min: min, max: max));
+ }
+
+ /// Ensures the string's UTF-8 byte length falls within a range.
+ static String? Function(String?) byteLength(
+ int min, {
+ int? max,
+ String errorMessage = 'Length is out of range',
+ }) {
+ return _build(errorMessage, (v) => v.isByteLength(min, max));
+ }
+
+ /// Ensures the string is a strong password.
+ static String? Function(String?) strongPassword({
+ int minLength = 8,
+ int minLowercase = 1,
+ int minUppercase = 1,
+ int minNumbers = 1,
+ int minSymbols = 1,
+ String errorMessage = 'Password is not strong enough',
+ }) {
+ return _build(
+ errorMessage,
+ (v) => v.isStrongPassword(
+ minLength: minLength,
+ minLowercase: minLowercase,
+ minUppercase: minUppercase,
+ minNumbers: minNumbers,
+ minSymbols: minSymbols,
+ ),
+ );
+ }
+
/// Internal helper to construct the validator closure
static String? Function(String?) _build(
String errorMessage,
diff --git a/lib/sanitizers.dart b/lib/sanitizers.dart
new file mode 100644
index 0000000..ce4a15d
--- /dev/null
+++ b/lib/sanitizers.dart
@@ -0,0 +1,13 @@
+/// String sanitizers for the flutter_validators package.
+///
+/// Provides functions that transform or coerce strings, mirroring the
+/// sanitizers offered by validator.js.
+library;
+
+export 'validators/sanitizers/blacklist.dart';
+export 'validators/sanitizers/escape.dart';
+export 'validators/sanitizers/normalize_email.dart';
+export 'validators/sanitizers/strip_low.dart';
+export 'validators/sanitizers/to_boolean.dart';
+export 'validators/sanitizers/to_number.dart';
+export 'validators/sanitizers/trim.dart';
diff --git a/lib/validators/base64.dart b/lib/validators/base64.dart
new file mode 100644
index 0000000..ea8c44b
--- /dev/null
+++ b/lib/validators/base64.dart
@@ -0,0 +1,36 @@
+/// Checks if the string is Base64 encoded.
+///
+/// By default the standard Base64 alphabet is used. Set [urlSafe] to `true`
+/// to validate against the URL- and filename-safe alphabet.
+///
+/// Example:
+/// ```dart
+/// isBase64('aGVsbG8='); // true
+/// isBase64('aGVsbG8'); // false (invalid padding)
+/// isBase64('aGVsbG8', urlSafe: true); // true
+/// ```
+bool isBase64(String str, {bool urlSafe = false}) => _isBase64(str, urlSafe);
+
+/// Extension providing Base64 validation methods on [String].
+extension Base64X on String {
+ /// Checks if the string is Base64 encoded.
+ bool isBase64({bool urlSafe = false}) {
+ return _isBase64(this, urlSafe);
+ }
+}
+
+final _base64Standard = RegExp(
+ r'^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$',
+);
+final _base64UrlSafe = RegExp(r'^[A-Za-z0-9_-]+$');
+
+bool _isBase64(String str, bool urlSafe) {
+ if (str.isEmpty) return false;
+ if (urlSafe) {
+ // A base64 string can never have a length of `4n + 1`.
+ if (str.length % 4 == 1) return false;
+ return _base64UrlSafe.hasMatch(str);
+ }
+ if (str.length % 4 != 0) return false;
+ return _base64Standard.hasMatch(str);
+}
diff --git a/lib/validators/byte_length.dart b/lib/validators/byte_length.dart
new file mode 100644
index 0000000..db58562
--- /dev/null
+++ b/lib/validators/byte_length.dart
@@ -0,0 +1,28 @@
+import 'dart:convert';
+
+/// Checks if the string's UTF-8 byte length falls in a range.
+///
+/// The byte length must be at least [min]. If [max] is provided, it must
+/// also be at most [max].
+///
+/// Example:
+/// ```dart
+/// isByteLength('abc', 2); // true
+/// isByteLength('abc', 4); // false
+/// isByteLength('é', 1, 2); // true ('é' is 2 bytes in UTF-8)
+/// ```
+bool isByteLength(String str, int min, [int? max]) =>
+ _isByteLength(str, min, max);
+
+/// Extension providing byte-length validation methods on [String].
+extension ByteLengthX on String {
+ /// Checks if the string's UTF-8 byte length falls in a range.
+ bool isByteLength(int min, [int? max]) {
+ return _isByteLength(this, min, max);
+ }
+}
+
+bool _isByteLength(String str, int min, int? max) {
+ final length = utf8.encode(str).length;
+ return length >= min && (max == null || length <= max);
+}
diff --git a/lib/validators/contains.dart b/lib/validators/contains.dart
new file mode 100644
index 0000000..40752a7
--- /dev/null
+++ b/lib/validators/contains.dart
@@ -0,0 +1,25 @@
+/// Checks if the string contains the [seed] substring.
+///
+/// Set [ignoreCase] to `true` for a case-insensitive match. [minOccurrences]
+/// requires the seed to appear at least that many times.
+///
+/// Note: this is exposed only as a top-level function because [String]
+/// already provides a built-in `contains` method.
+///
+/// Example:
+/// ```dart
+/// contains('hello world', 'world'); // true
+/// contains('hello world', 'WORLD', ignoreCase: true); // true
+/// contains('a-a-a', 'a', minOccurrences: 3); // true
+/// ```
+bool contains(
+ String str,
+ String seed, {
+ bool ignoreCase = false,
+ int minOccurrences = 1,
+}) {
+ if (seed.isEmpty) return false;
+ final haystack = ignoreCase ? str.toLowerCase() : str;
+ final needle = ignoreCase ? seed.toLowerCase() : seed;
+ return needle.allMatches(haystack).length >= minOccurrences;
+}
diff --git a/lib/validators/decimal.dart b/lib/validators/decimal.dart
new file mode 100644
index 0000000..788e57e
--- /dev/null
+++ b/lib/validators/decimal.dart
@@ -0,0 +1,28 @@
+/// Checks if the string represents a decimal number.
+///
+/// Accepts an optional sign followed by digits with an optional fractional
+/// part (e.g. `1`, `-1.5`, `.5`, `+10.0`).
+///
+/// Example:
+/// ```dart
+/// isDecimal('1.5'); // true
+/// isDecimal('-0.25'); // true
+/// isDecimal('.5'); // true
+/// isDecimal('1.'); // false
+/// isDecimal('abc'); // false
+/// ```
+bool isDecimal(String str) => _isDecimal(str);
+
+/// Extension providing decimal validation methods on [String].
+extension DecimalX on String {
+ /// Checks if the string represents a decimal number.
+ bool get isDecimal {
+ return _isDecimal(this);
+ }
+}
+
+final _decimal = RegExp(r'^[+-]?(\d+(\.\d+)?|\.\d+)$');
+
+bool _isDecimal(String str) {
+ return _decimal.hasMatch(str);
+}
diff --git a/lib/validators/float.dart b/lib/validators/float.dart
new file mode 100644
index 0000000..0296e51
--- /dev/null
+++ b/lib/validators/float.dart
@@ -0,0 +1,30 @@
+/// Checks if the string represents a finite floating-point number.
+///
+/// If [min] and/or [max] are provided, the parsed value must also fall
+/// within that inclusive range.
+///
+/// Example:
+/// ```dart
+/// isFloat('1.5'); // true
+/// isFloat('-3'); // true
+/// isFloat('1.5', min: 0, max: 2); // true
+/// isFloat('5', min: 0, max: 2); // false
+/// isFloat('abc'); // false
+/// ```
+bool isFloat(String str, {double? min, double? max}) => _isFloat(str, min, max);
+
+/// Extension providing float validation methods on [String].
+extension FloatX on String {
+ /// Checks if the string represents a finite floating-point number.
+ bool isFloat({double? min, double? max}) {
+ return _isFloat(this, min, max);
+ }
+}
+
+bool _isFloat(String str, double? min, double? max) {
+ final value = double.tryParse(str);
+ if (value == null || !value.isFinite) return false;
+ if (min != null && value < min) return false;
+ if (max != null && value > max) return false;
+ return true;
+}
diff --git a/lib/validators/fqdn.dart b/lib/validators/fqdn.dart
new file mode 100644
index 0000000..67719d6
--- /dev/null
+++ b/lib/validators/fqdn.dart
@@ -0,0 +1,43 @@
+/// Checks if the string is a fully qualified domain name (FQDN).
+///
+/// Requires at least two labels separated by dots, a valid top-level domain,
+/// and labels that do not start or end with a hyphen.
+///
+/// Example:
+/// ```dart
+/// isFQDN('example.com'); // true
+/// isFQDN('sub.example.co.uk'); // true
+/// isFQDN('localhost'); // false (no TLD)
+/// isFQDN('-bad.com'); // false
+/// ```
+bool isFQDN(String str) => _isFQDN(str);
+
+/// Extension providing FQDN validation methods on [String].
+extension FQDNX on String {
+ /// Checks if the string is a fully qualified domain name (FQDN).
+ bool get isFQDN {
+ return _isFQDN(this);
+ }
+}
+
+final _fqdnLabel = RegExp(r'^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$');
+final _fqdnTld = RegExp(r'^[a-zA-Z]{2,}$|^xn--[a-zA-Z0-9]+$');
+
+bool _isFQDN(String str) {
+ if (str.isEmpty) return false;
+ var domain = str;
+ if (domain.endsWith('.')) {
+ domain = domain.substring(0, domain.length - 1);
+ }
+ final parts = domain.split('.');
+ if (parts.length < 2) return false;
+
+ final tld = parts.removeLast();
+ if (!_fqdnTld.hasMatch(tld)) return false;
+
+ for (final part in parts) {
+ if (part.isEmpty || part.length > 63) return false;
+ if (!_fqdnLabel.hasMatch(part)) return false;
+ }
+ return true;
+}
diff --git a/lib/validators/hexadecimal.dart b/lib/validators/hexadecimal.dart
new file mode 100644
index 0000000..3a6366d
--- /dev/null
+++ b/lib/validators/hexadecimal.dart
@@ -0,0 +1,26 @@
+/// Checks if the string is a hexadecimal number.
+///
+/// Accepts an optional `0x` or `0h` prefix followed by one or more
+/// hexadecimal digits.
+///
+/// Example:
+/// ```dart
+/// isHexadecimal('deadBEEF'); // true
+/// isHexadecimal('0xff'); // true
+/// isHexadecimal('xyz'); // false
+/// ```
+bool isHexadecimal(String str) => _isHexadecimal(str);
+
+/// Extension providing hexadecimal validation methods on [String].
+extension HexadecimalX on String {
+ /// Checks if the string is a hexadecimal number.
+ bool get isHexadecimal {
+ return _isHexadecimal(this);
+ }
+}
+
+final _hexadecimal = RegExp(r'^(0x|0h)?[0-9A-Fa-f]+$');
+
+bool _isHexadecimal(String str) {
+ return _hexadecimal.hasMatch(str);
+}
diff --git a/lib/validators/in.dart b/lib/validators/in.dart
new file mode 100644
index 0000000..ffeaad8
--- /dev/null
+++ b/lib/validators/in.dart
@@ -0,0 +1,20 @@
+/// Checks if the string is one of the [values].
+///
+/// Example:
+/// ```dart
+/// isIn('red', ['red', 'green', 'blue']); // true
+/// isIn('yellow', ['red', 'green', 'blue']); // false
+/// ```
+bool isIn(String str, Iterable values) => _isIn(str, values);
+
+/// Extension providing membership validation methods on [String].
+extension InX on String {
+ /// Checks if the string is one of the [values].
+ bool isIn(Iterable values) {
+ return _isIn(this, values);
+ }
+}
+
+bool _isIn(String str, Iterable values) {
+ return values.contains(str);
+}
diff --git a/lib/validators/ip.dart b/lib/validators/ip.dart
index 9461377..9d1b684 100644
--- a/lib/validators/ip.dart
+++ b/lib/validators/ip.dart
@@ -16,12 +16,12 @@ extension IpX on String {
bool get isIP {
return _isIP(this);
}
-
+
/// Checks if the string is a valid IPv4 address.
bool get isIPv4 {
return _isIP(this, 4);
}
-
+
/// Checks if the string is a valid IPv6 address.
bool get isIPv6 {
return _isIP(this, 6);
@@ -49,6 +49,8 @@ bool _isIPv4(String str) {
bool _isIPv6(String str) {
// Common robust regex for IPv6 validation
- final reg = RegExp(r'^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$');
+ final reg = RegExp(
+ r'^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$',
+ );
return reg.hasMatch(str);
}
diff --git a/lib/validators/jwt.dart b/lib/validators/jwt.dart
new file mode 100644
index 0000000..66e50be
--- /dev/null
+++ b/lib/validators/jwt.dart
@@ -0,0 +1,31 @@
+/// Checks if the string is a valid JSON Web Token (JWT).
+///
+/// A JWT consists of three base64url-encoded segments separated by dots.
+/// The signature segment may be empty (for unsigned tokens).
+///
+/// Example:
+/// ```dart
+/// isJWT('eyJhbGci.eyJzdWIi.SflKxwRJ'); // true
+/// isJWT('eyJhbGci.eyJzdWIi'); // false (only two segments)
+/// ```
+bool isJWT(String str) => _isJWT(str);
+
+/// Extension providing JWT validation methods on [String].
+extension JWTX on String {
+ /// Checks if the string is a valid JSON Web Token (JWT).
+ bool get isJWT {
+ return _isJWT(this);
+ }
+}
+
+final _base64Url = RegExp(r'^[A-Za-z0-9_-]+$');
+
+bool _isJWT(String str) {
+ final parts = str.split('.');
+ if (parts.length != 3) return false;
+ if (parts[0].isEmpty || parts[1].isEmpty) return false;
+ for (var i = 0; i < 2; i++) {
+ if (!_base64Url.hasMatch(parts[i])) return false;
+ }
+ return parts[2].isEmpty || _base64Url.hasMatch(parts[2]);
+}
diff --git a/lib/validators/lat_long.dart b/lib/validators/lat_long.dart
new file mode 100644
index 0000000..a37ef8d
--- /dev/null
+++ b/lib/validators/lat_long.dart
@@ -0,0 +1,30 @@
+/// Checks if the string is a valid `latitude,longitude` pair.
+///
+/// Latitude must be in the range -90 to 90 and longitude in the range
+/// -180 to 180. The two values are separated by a comma.
+///
+/// Example:
+/// ```dart
+/// isLatLong('40.7128,-74.0060'); // true
+/// isLatLong('0,0'); // true
+/// isLatLong('91,0'); // false (latitude out of range)
+/// ```
+bool isLatLong(String str) => _isLatLong(str);
+
+/// Extension providing latitude/longitude validation methods on [String].
+extension LatLongX on String {
+ /// Checks if the string is a valid `latitude,longitude` pair.
+ bool get isLatLong {
+ return _isLatLong(this);
+ }
+}
+
+bool _isLatLong(String str) {
+ final parts = str.split(',');
+ if (parts.length != 2) return false;
+ final lat = double.tryParse(parts[0].trim());
+ final long = double.tryParse(parts[1].trim());
+ if (lat == null || long == null) return false;
+ if (!lat.isFinite || !long.isFinite) return false;
+ return lat >= -90 && lat <= 90 && long >= -180 && long <= 180;
+}
diff --git a/lib/validators/lowercase.dart b/lib/validators/lowercase.dart
new file mode 100644
index 0000000..5358730
--- /dev/null
+++ b/lib/validators/lowercase.dart
@@ -0,0 +1,25 @@
+/// Checks if the string is entirely lowercase.
+///
+/// A string is considered lowercase if it is unchanged when converted to
+/// lower case. Strings without cased characters (digits, symbols) and the
+/// empty string are considered lowercase.
+///
+/// Example:
+/// ```dart
+/// isLowercase('hello'); // true
+/// isLowercase('hello123'); // true
+/// isLowercase('Hello'); // false
+/// ```
+bool isLowercase(String str) => _isLowercase(str);
+
+/// Extension providing lowercase validation methods on [String].
+extension LowercaseX on String {
+ /// Checks if the string is entirely lowercase.
+ bool get isLowercase {
+ return _isLowercase(this);
+ }
+}
+
+bool _isLowercase(String str) {
+ return str == str.toLowerCase();
+}
diff --git a/lib/validators/mac_address.dart b/lib/validators/mac_address.dart
new file mode 100644
index 0000000..cd42c47
--- /dev/null
+++ b/lib/validators/mac_address.dart
@@ -0,0 +1,32 @@
+/// Checks if the string is a valid MAC address.
+///
+/// Accepts EUI-48 (6-byte) and EUI-64 (8-byte) addresses using `:` or `-`
+/// as separators, as well as the separator-less form.
+///
+/// Example:
+/// ```dart
+/// isMACAddress('00:1B:44:11:3A:B7'); // true
+/// isMACAddress('00-1B-44-11-3A-B7'); // true
+/// isMACAddress('001B44113AB7'); // true
+/// isMACAddress('00:1B:44:11:3A'); // false
+/// ```
+bool isMACAddress(String str) => _isMACAddress(str);
+
+/// Extension providing MAC address validation methods on [String].
+extension MACAddressX on String {
+ /// Checks if the string is a valid MAC address.
+ bool get isMACAddress {
+ return _isMACAddress(this);
+ }
+}
+
+final _macAddress = RegExp(
+ r'^(?:[0-9A-Fa-f]{2}([:-]))(?:[0-9A-Fa-f]{2}\1){4}[0-9A-Fa-f]{2}$'
+ r'|^(?:[0-9A-Fa-f]{2}([:-]))(?:[0-9A-Fa-f]{2}\2){6}[0-9A-Fa-f]{2}$'
+ r'|^[0-9A-Fa-f]{12}$'
+ r'|^[0-9A-Fa-f]{16}$',
+);
+
+bool _isMACAddress(String str) {
+ return _macAddress.hasMatch(str);
+}
diff --git a/lib/validators/matches.dart b/lib/validators/matches.dart
new file mode 100644
index 0000000..a474967
--- /dev/null
+++ b/lib/validators/matches.dart
@@ -0,0 +1,23 @@
+/// Checks if the string matches the given [pattern].
+///
+/// The [pattern] may be a [RegExp] or any [Pattern]. A match anywhere in the
+/// string is sufficient.
+///
+/// Example:
+/// ```dart
+/// matches('abc123', RegExp(r'\d+')); // true
+/// matches('abc', RegExp(r'^\d+$')); // false
+/// ```
+bool matches(String str, Pattern pattern) => _matches(str, pattern);
+
+/// Extension providing pattern matching methods on [String].
+extension MatchesX on String {
+ /// Checks if the string matches the given [pattern].
+ bool matches(Pattern pattern) {
+ return _matches(this, pattern);
+ }
+}
+
+bool _matches(String str, Pattern pattern) {
+ return str.contains(pattern);
+}
diff --git a/lib/validators/md5.dart b/lib/validators/md5.dart
new file mode 100644
index 0000000..0ee4ce7
--- /dev/null
+++ b/lib/validators/md5.dart
@@ -0,0 +1,24 @@
+/// Checks if the string is a valid MD5 hash.
+///
+/// An MD5 hash is a 32-character hexadecimal string.
+///
+/// Example:
+/// ```dart
+/// isMD5('d41d8cd98f00b204e9800998ecf8427e'); // true
+/// isMD5('d41d8cd98f00b204e9800998ecf8427'); // false (too short)
+/// ```
+bool isMD5(String str) => _isMD5(str);
+
+/// Extension providing MD5 hash validation methods on [String].
+extension MD5X on String {
+ /// Checks if the string is a valid MD5 hash.
+ bool get isMD5 {
+ return _isMD5(this);
+ }
+}
+
+final _md5 = RegExp(r'^[0-9a-fA-F]{32}$');
+
+bool _isMD5(String str) {
+ return _md5.hasMatch(str);
+}
diff --git a/lib/validators/mongo_id.dart b/lib/validators/mongo_id.dart
new file mode 100644
index 0000000..09f06ee
--- /dev/null
+++ b/lib/validators/mongo_id.dart
@@ -0,0 +1,25 @@
+/// Checks if the string is a valid MongoDB ObjectId.
+///
+/// A MongoDB ObjectId is a 24-character hexadecimal string.
+///
+/// Example:
+/// ```dart
+/// isMongoId('507f1f77bcf86cd799439011'); // true
+/// isMongoId('507f1f77bcf86cd79943901'); // false (too short)
+/// isMongoId('zzzf1f77bcf86cd799439011'); // false
+/// ```
+bool isMongoId(String str) => _isMongoId(str);
+
+/// Extension providing MongoDB ObjectId validation methods on [String].
+extension MongoIdX on String {
+ /// Checks if the string is a valid MongoDB ObjectId.
+ bool get isMongoId {
+ return _isMongoId(this);
+ }
+}
+
+final _mongoId = RegExp(r'^[0-9a-fA-F]{24}$');
+
+bool _isMongoId(String str) {
+ return _mongoId.hasMatch(str);
+}
diff --git a/lib/validators/octal.dart b/lib/validators/octal.dart
new file mode 100644
index 0000000..f6dafcf
--- /dev/null
+++ b/lib/validators/octal.dart
@@ -0,0 +1,26 @@
+/// Checks if the string is an octal number.
+///
+/// Accepts an optional `0o` prefix followed by one or more octal digits
+/// (0–7).
+///
+/// Example:
+/// ```dart
+/// isOctal('0o123'); // true
+/// isOctal('777'); // true
+/// isOctal('088'); // false
+/// ```
+bool isOctal(String str) => _isOctal(str);
+
+/// Extension providing octal validation methods on [String].
+extension OctalX on String {
+ /// Checks if the string is an octal number.
+ bool get isOctal {
+ return _isOctal(this);
+ }
+}
+
+final _octal = RegExp(r'^(0o)?[0-7]+$');
+
+bool _isOctal(String str) {
+ return _octal.hasMatch(str);
+}
diff --git a/lib/validators/port.dart b/lib/validators/port.dart
new file mode 100644
index 0000000..cdbd822
--- /dev/null
+++ b/lib/validators/port.dart
@@ -0,0 +1,27 @@
+/// Checks if the string is a valid port number.
+///
+/// A valid port is an integer in the range 0–65535 without leading zeros.
+///
+/// Example:
+/// ```dart
+/// isPort('8080'); // true
+/// isPort('0'); // true
+/// isPort('65536'); // false (out of range)
+/// isPort('080'); // false (leading zero)
+/// ```
+bool isPort(String str) => _isPort(str);
+
+/// Extension providing port validation methods on [String].
+extension PortX on String {
+ /// Checks if the string is a valid port number.
+ bool get isPort {
+ return _isPort(this);
+ }
+}
+
+bool _isPort(String str) {
+ if (str.isEmpty) return false;
+ if (str.length > 1 && str.startsWith('0')) return false;
+ final port = int.tryParse(str);
+ return port != null && port >= 0 && port <= 65535;
+}
diff --git a/lib/validators/sanitizers/blacklist.dart b/lib/validators/sanitizers/blacklist.dart
new file mode 100644
index 0000000..4fdaa16
--- /dev/null
+++ b/lib/validators/sanitizers/blacklist.dart
@@ -0,0 +1,35 @@
+/// Removes all characters that appear in [chars] from the string.
+///
+/// Example:
+/// ```dart
+/// blacklist('hello world', 'lo'); // 'he wrd'
+/// ```
+String blacklist(String str, String chars) => _blacklist(str, chars);
+
+/// Removes all characters that do not appear in [chars] from the string.
+///
+/// Example:
+/// ```dart
+/// whitelist('hello world', 'lo'); // 'llool'
+/// ```
+String whitelist(String str, String chars) => _whitelist(str, chars);
+
+/// Extension providing character filtering sanitizers on [String].
+extension BlacklistX on String {
+ /// Removes all characters that appear in [chars] from the string.
+ String blacklist(String chars) => _blacklist(this, chars);
+
+ /// Removes all characters that do not appear in [chars] from the string.
+ String whitelist(String chars) => _whitelist(this, chars);
+}
+
+String _blacklist(String str, String chars) {
+ if (chars.isEmpty) return str;
+ final set = chars.split('').toSet();
+ return str.split('').where((c) => !set.contains(c)).join();
+}
+
+String _whitelist(String str, String chars) {
+ final set = chars.split('').toSet();
+ return str.split('').where(set.contains).join();
+}
diff --git a/lib/validators/sanitizers/escape.dart b/lib/validators/sanitizers/escape.dart
new file mode 100644
index 0000000..58cde31
--- /dev/null
+++ b/lib/validators/sanitizers/escape.dart
@@ -0,0 +1,52 @@
+/// Replaces HTML-unsafe characters with their entity equivalents.
+///
+/// Escapes `&`, `<`, `>`, `"`, `'`, `` ` ``, `/` and `\`.
+///
+/// Example:
+/// ```dart
+/// escape('