Overview

Ack is a schema validation library for Dart and Flutter that helps you validate data with a simple, fluent API. Ack is short for "acknowledgment".

Why Use Ack?

  • Simplify Validation: Easily handle complex data validation logic.
  • Ensure Data Integrity: Guarantee data consistency from external sources (APIs, user input).
  • Single Source of Truth: Define data structures and rules in one place.
  • Reduce Boilerplate: Minimize repetitive code for validation and JSON conversion.

What Ack Does

  • Ensures data conforms to your defined schemas
  • Facilitates seamless conversion between JSON and Dart models
  • Generates JSON Schema specifications from your schemas
  • Delivers detailed and easy-to-understand validation error messages

Quick Start

Add Ack to your project:

dart pub add ack

Basic Usage

import 'package:ack/ack.dart';

// Define the structure and rules for a user object.
final userSchema = Ack.object({
  'name': Ack.string.minLength(2).maxLength(50), // Name must be a string between 2 and 50 chars.
  'age': Ack.int.min(0).max(120),                // Age must be an integer between 0 and 120.
  'email': Ack.string.email().nullable(),      // Email must be a valid format, but can be null.
}, required: ['name', 'age']); // Name and age are mandatory fields.

// Data to validate.
final dataToValidate = {
  'name': 'John',
  'age': 30,
  'email': 'john@example.com'
};

// Perform the validation against the schema.
final result = userSchema.validate(dataToValidate);

// Check the validation outcome.
if (result.isOk) {
  // Validation passed, safely access the validated data.
  final validData = result.getOrThrow();
  print('Valid data: $validData');
} else {
  // Validation failed, access the detailed error.
  final error = result.getError();
  print('Validation Error: $error'); // Use error.toString() for full details.
}

Core Features

Ack provides a comprehensive set of features for data validation and transformation:

  • Schema Types: Define data structures with strings, numbers, booleans, lists, objects, and more
  • Validation Rules: Apply constraints like length, range, pattern matching, and custom validators
  • TypeSafe Schemas: Create type-safe schemas with automatic validation
  • Error Handling: Get detailed, structured error messages for validation failures
  • JSON Serialization: Convert between JSON and typed models

TypeSafe Schemas

Turn any Dart class into a validating schema with annotations:

import 'package:ack/ack.dart';
import 'dart:convert';

// Define a model with validation and automatic inference
@Schema()
class User {
  @MinLength(2)                    // Validation rule
  final String name;               // Non-nullable, required from constructor

  @IsEmail()                       // Validation rule
  final String email;              // Non-nullable, required from constructor

  @Min(18)                         // Validation rule
  final int age;                   // Non-nullable, required from constructor

  // Constructor parameters automatically determine required/optional status
  User({
    required this.name,            // → Automatically inferred as required
    required this.email,           // → Automatically inferred as required
    required this.age,             // → Automatically inferred as required
  });
}

// Dynamic payload from API, form, etc.
final Map<String, dynamic> payload = {
  'name': 'John',
  'email': 'john@example.com',
  'age': 30
};

Automatic Inference: Ack automatically determines which fields are required or optional based on your constructor parameters (required this.field vs this.field) and field types (String vs String?). No need for redundant @Required or @Nullable annotations in most cases!

Generated schemas provide three main capabilities:

1. Type-safe property access

// Access typed properties directly from the schema
final userSchema = UserSchema().parse(payload);
if (userSchema.isValid) {
  String name = userSchema.name;    // Typed as String
  String email = userSchema.email;  // Typed as String
  int age = userSchema.age;         // Typed as int
  print('$name ($email) is $age years old');

  // Create model when needed
  final user = User(name: name, email: email, age: age);
}

2. Flexible model creation patterns

// Add factory method to your model
class User {
  // ... existing fields ...
  
  factory User.fromSchema(UserSchema schema) {
    if (!schema.isValid) {
      throw AckException(schema.getErrors()!);
    }
    return User(name: schema.name, email: schema.email, age: schema.age);
  }
}

// Usage
final user = User.fromSchema(UserSchema().parse(payload));

3. JSON Schema generation for documentation

// Generate JSON Schema from the same model definition
final jsonSchema = UserSchema().toJsonSchema();
print(jsonEncode(jsonSchema));  // Use in API docs or client validation

Error Handling

if (result.isFail) {
  // Get formatted error message (or the full error object)
  final error = result.getError();
  print('${error.name}: $error'); // Full error details
}