ACK Generator
Code generator for the ACK validation framework. This generator creates type-safe schema classes from your Dart model classes, providing validation without serialization.
Breaking Changes in v2.0
The toModel()
method has been removed from generated schemas. ACK is now a pure validation library, giving you complete control over how you create model instances from validated data.
Migration Guide
Before (v1.x)
final schema = UserSchema().parse(jsonData);
if (schema.isValid) {
final user = schema.toModel(); // This method no longer exists
}
After (v2.0)
Option 1: Direct Property Access
final schema = UserSchema().parse(jsonData);
if (schema.isValid) {
// Access properties directly
print(schema.name);
print(schema.email);
// Create model manually
final user = User(
name: schema.name,
email: schema.email,
address: Address(
street: schema.address.street,
city: schema.address.city,
),
);
}
Option 2: Factory Method Pattern
class User {
factory User.fromSchema(UserSchema schema) {
if (!schema.isValid) {
throw AckException(schema.getErrors() ?? throw StateError('Invalid schema with no error'));
}
return User(
name: schema.name,
email: schema.email,
address: Address.fromSchema(schema.address),
);
}
}
// Usage
final user = User.fromSchema(schema);
Option 3: Extension Methods
extension UserSchemaX on UserSchema {
User toUser() {
if (!isValid) throw AckException(getErrors() ?? throw StateError('Invalid schema with no error'));
return User(name: name, email: email);
}
}
// Usage
final user = schema.toUser();
Option 4: With dart_mappable Integration
import 'package:dart_mappable/dart_mappable.dart';
import 'package:ack/ack.dart';
part 'user.mapper.dart'; // dart_mappable
part 'user.g.dart'; // ack_generator
@MappableClass(caseStyle: CaseStyle.snakeCase)
@Schema()
class User with UserMappable {
final String firstName; // -> 'first_name' in both JSON and schema
final String lastName; // -> 'last_name' in both JSON and schema
@MappableField(key: 'email_address')
@IsEmail()
final String email; // -> 'email_address' in both
User({required this.firstName, required this.lastName, required this.email});
}
// Validation and serialization use the same field names automatically
factory User.fromJson(Map<String, dynamic> json) {
// Validate first with transformed field names
final schema = UserSchema(json);
if (!schema.isValid) {
throw AckException(schema.getErrors() ?? throw StateError('Invalid schema with no error'));
}
// Then deserialize with dart_mappable (same field names)
return UserMapper.fromMap(json);
}
Option 5: With Other Serialization Libraries
// Works seamlessly with json_serializable, freezed, etc.
factory User.fromJson(Map<String, dynamic> json) {
// Validate first
final schema = UserSchema(json);
if (!schema.isValid) {
throw AckException(schema.getErrors() ?? throw StateError('Invalid schema with no error'));
}
// Then use your preferred serialization
return _$UserFromJson(json);
}
dart_mappable Integration
ACK Generator now provides seamless integration with dart_mappable, automatically synchronizing field names and case styles between validation schemas and serialization.
Key Features
- Automatic Case Style Transformation: Supports snake_case, camelCase, kebab-case, and all other dart_mappable case styles
- Custom Field Key Support: Respects
@MappableField(key: 'custom_name')
annotations - Zero Configuration: Just add dart_mappable annotations and ACK automatically applies the same transformations
- Backward Compatible: Existing schemas continue to work unchanged
Supported Case Styles
camelCase
(default, no transformation)snake_case
kebab-case
/param-case
PascalCase
CONSTANT_CASE
dot.case
path/case
Sentence case
Header-Case
Example
@MappableClass(caseStyle: CaseStyle.snakeCase)
@Schema()
class User with UserMappable {
final String firstName; // Becomes 'first_name' in JSON and schema
final String lastName; // Becomes 'last_name' in JSON and schema
@MappableField(key: 'email_addr')
@IsEmail()
final String email; // Becomes 'email_addr' in JSON and schema
}
Both dart_mappable serialization and ACK validation will use the same transformed field names automatically.
Benefits of This Change
- Flexibility: Choose your own serialization strategy
- Smaller Generated Code: ~30% reduction in file size
- Better Separation of Concerns: Validation is separate from object creation
- Framework Agnostic: Works with any Dart serialization library
- Simpler Mental Model: Schemas validate, they don't create
- dart_mappable Integration: Seamless compatibility with automatic field name synchronization
Implementation Status
✅ Core files created:
lib/builder.dart
- Builder registrationlib/src/generator.dart
- Main generator using code_buildertest/generator_test.dart
- Golden file testsack_generator_plan_v2.md
- Simple migration plan
Next Steps to Complete Setup
1. Copy Analyzers from Current Generator
# From ack_generator directory:
cp -r ../ack_generator/lib/src/analyzers ./lib/src/
cp -r ../ack_generator/lib/src/models ./lib/src/ # If exists
# Or create models directory and copy individual files:
mkdir -p lib/src/models
cp ../ack_generator/lib/src/schema_data.dart ./lib/src/models/ # If not in analyzers
2. Create Test Directories
mkdir -p test/fixtures
mkdir -p test/golden
3. Copy Test Fixtures
# Copy test models as fixtures
cp ../ack_generator/test/models/user_model.dart ./test/fixtures/
cp ../ack_generator/test/models/product_model.dart ./test/fixtures/
cp ../ack_generator/test/models/block_model.dart ./test/fixtures/
# Copy generated files as golden files
cp ../ack_generator/test/models/user_model.g.dart ./test/golden/user_model.golden
cp ../ack_generator/test/models/product_model.g.dart ./test/golden/product_model.golden
cp ../ack_generator/test/models/block_model.g.dart ./test/golden/block_model.golden
4. Create pubspec.yaml
name: ack_generator
description: Code generator for ACK validation schemas
version: 0.1.0
environment:
sdk: '>=3.0.0 <4.0.0'
dependencies:
analyzer: ^6.3.0
build: ^2.4.1
code_builder: ^4.10.0
source_gen: ^1.4.0
dart_style: ^2.3.4
ack: ^0.2.0-beta.1
dev_dependencies:
build_runner: ^2.4.6
build_test: ^2.2.0
test: ^1.25.0
5. Create build.yaml
builders:
ack_schema:
import: "package:ack_generator/builder.dart"
builder_factories: ["ackSchemaBuilder"]
build_extensions: {".dart": [".ack.dart"]}
auto_apply: dependents
build_to: source
applies_builders: ["source_gen|combining_builder"]
6. Run Tests
# Install dependencies
dart pub get
# Run tests (will fail until golden files exist)
dart test
# Create/update golden files
UPDATE_GOLDEN=true dart test
# Run tests again to verify
dart test
Key Implementation Notes
- The generator reuses ALL existing analyzers unchanged
- Uses code_builder for type-safe generation instead of strings
- Maintains exact same output as current generator
- Discriminated unions temporarily use string generation (TODO for phase 2)
- Total new code: ~400 lines (vs 900+ in over-engineered version)
Architecture
ack_generator/
├── lib/
│ ├── builder.dart (17 lines)
│ └── src/
│ ├── generator.dart (~400 lines)
│ └── analyzers/ (copied from ack_generator)
├── test/
│ ├── generator_test.dart (86 lines)
│ ├── fixtures/ (input dart files)
│ └── golden/ (expected output files)
└── pubspec.yaml
This is a true MVP - minimal new code, maximum reuse, same functionality.
Libraries
- ack_generator
- Code generator for ACK validation schemas
- builder