Common Recipes

This page provides quick solutions to common validation scenarios. Each recipe shows a complete, working example you can copy and adapt.

Email and Password Validation

Validate user credentials with proper constraints:

import 'package:ack/ack.dart';

// Email with proper format
final emailSchema = Ack.string()
  .email()
  .notEmpty();

// Password with security requirements
final passwordSchema = Ack.string()
  .minLength(8)
  .matches(r'.*[A-Z].*', message: 'Password must contain an uppercase letter')
  .matches(r'.*[a-z].*', message: 'Password must contain a lowercase letter')
  .matches(r'.*[0-9].*', message: 'Password must contain a number');

// Login form schema
final loginSchema = Ack.object({
  'email': emailSchema,
  'password': passwordSchema,
});

// Usage
final result = loginSchema.safeParse({
  'email': 'user@example.com',
  'password': 'SecurePass123',
});

if (result.isOk) {
  print('Credentials valid');
} else {
  print('Error: ${result.getError()}');
}

Nested Object Validation

Validate nested data structures like addresses:

final addressSchema = Ack.object({
  'street': Ack.string().notEmpty(),
  'city': Ack.string().notEmpty(),
  'zipCode': Ack.string().matches(r'\d{5}(-\d{4})?'),
  'country': Ack.string().notEmpty(),
});

final userWithAddressSchema = Ack.object({
  'name': Ack.string(),
  'email': Ack.string().email(),
  'shippingAddress': addressSchema,
  'billingAddress': addressSchema.optional().nullable(),
});

// Usage
final result = userWithAddressSchema.safeParse({
  'name': 'John Doe',
  'email': 'john@example.com',
  'shippingAddress': {
    'street': '123 Main St',
    'city': 'Springfield',
    'zipCode': '12345',
    'country': 'USA',
  },
});

List Validation

Validate lists with constraints on items and length:

// Shopping cart with at least 1 item
final cartSchema = Ack.object({
  'userId': Ack.string(),
  'items': Ack.list(Ack.object({
    'productId': Ack.string(),
    'quantity': Ack.integer().positive(),
    'price': Ack.double().positive(),
  })).minLength(1).maxLength(50),
});

// Tags with max 5 items
final postSchema = Ack.object({
  'title': Ack.string().minLength(5).maxLength(100),
  'content': Ack.string().minLength(10),
  'tags': Ack.list(Ack.string()).maxLength(5),
});

Enum Validation

Validate string values against allowed options:

// Status with specific allowed values
final orderSchema = Ack.object({
  'orderId': Ack.string(),
  'status': Ack.string().enumString([
    'pending',
    'processing',
    'shipped',
    'delivered',
    'cancelled',
  ]),
  'priority': Ack.string().enumString(['low', 'medium', 'high']),
});

// Usage
final result = orderSchema.safeParse({
  'orderId': 'ORD-123',
  'status': 'shipped',
  'priority': 'high',
});

Custom Validation

Create reusable custom validators for domain-specific rules:

import 'package:ack/ack.dart';

// Custom phone number constraint
class PhoneNumberConstraint extends Constraint<String> with Validator<String> {
  PhoneNumberConstraint()
      : super(
          constraintKey: 'phone_number',
          description: 'Must be valid phone number (e.g., +1-234-567-8900)',
        );

  final _regex = RegExp(r'^\+\d{1,3}-\d{3}-\d{3}-\d{4}$');

  @override
  bool isValid(String value) => _regex.hasMatch(value);

  @override
  String buildMessage(String value) =>
      'Must be valid phone number (e.g., +1-234-567-8900)';
}

final registrationSchema = Ack.object({
  'username': Ack.string().minLength(3),
  'email': Ack.string().email(),
  'phone': Ack.string().constrain(PhoneNumberConstraint()),
  'password': Ack.string().minLength(8),
  'confirmPassword': Ack.string().minLength(8),
}).refine(
  (data) => data['password'] == data['confirmPassword'],
  message: 'Passwords do not match',
);

API Response Validation

Validate external API responses to ensure data integrity:

// GitHub user API response
final githubUserSchema = Ack.object({
  'login': Ack.string(),
  'id': Ack.integer(),
  'avatar_url': Ack.string().url(),
  'name': Ack.string().nullable(),
  'email': Ack.string().email().nullable(),
  'bio': Ack.string().nullable(),
  'public_repos': Ack.integer(),
  'followers': Ack.integer(),
  'following': Ack.integer(),
  'created_at': Ack.string(), // ISO datetime
});

// Usage in API call
Future<void> fetchUser(String username) async {
  final response = await http.get(
    Uri.parse('https://api.github.com/users/$username'),
  );

  final json = jsonDecode(response.body);
  final result = githubUserSchema.safeParse(json);

  if (result.isOk) {
    final user = result.getOrThrow();
    print('User: ${user['login']}');
  } else {
    print('Invalid API response: ${result.getError()}');
  }
}

See Also