Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
d04c9dc
fix(generator): comprehensive fixes for primitives and correctness
claude Dec 20, 2025
16dbf0c
test: add missing tests and fix test bug from review
claude Dec 20, 2025
8110da2
chore(generator): remove print statements and tautological tests
leoafarias Dec 22, 2025
720b8dc
Add logging and special types tests
leoafarias Dec 22, 2025
e8bd4b1
Generate extension types for all AckType schemas
leoafarias Dec 22, 2025
c5444ca
Skip extension types for nullable AckType schemas
leoafarias Dec 22, 2025
19daea9
docs(generator): fix stale documentation for extension type generation
leoafarias Dec 23, 2025
5d097eb
refactor(generator): improve circular dependency handling and reduce …
leoafarias Dec 23, 2025
9970d39
fix(generator,ack): address code review issues for correctness
leoafarias Dec 23, 2025
0bc2e01
test(generator): update test to use non-reserved keyword field name
leoafarias Dec 23, 2025
1a44046
Improve ack_generator schema typing and docs
leoafarias Jan 21, 2026
db633a0
Merge branch 'main' into fix/ack-generator-primitives
leoafarias Jan 21, 2026
ed429e9
fix(generator): address code review issues for correctness and robust…
leoafarias Jan 23, 2026
2408446
fix(generator): address code review issues for correctness and robust…
leoafarias Jan 23, 2026
8eb5d36
fix: optimize duplicate detection and add AnyOf default tests
leoafarias Jan 27, 2026
bef88c1
fix: improve hash mixing and document numeric equality semantics
leoafarias Jan 27, 2026
5a80de8
fix(generator): optimize lookups and fix double-nullable getter types
leoafarias Feb 5, 2026
4f666c1
refactor(generator): use TypeReference for type-safe code generation …
leoafarias Feb 5, 2026
e5cffe3
feat(ack_generator): support getter-based AckType and enforce class r…
leoafarias Feb 5, 2026
1d5d159
fix(docs): align documentation with actual API surface
leoafarias Feb 6, 2026
502f890
fix(generator): include class names in circular dependency warning
leoafarias Feb 6, 2026
7ff4727
Address review feedback for AckType and generator output
leoafarias Feb 6, 2026
de5e32d
refactor: simplify AckField required handling and tighten analyzer co…
leoafarias Feb 6, 2026
5befc31
chore: enforce fatal analyzer infos in CI and clean remaining infos
leoafarias Feb 6, 2026
7d90618
chore: remove temporary code review document
leoafarias Feb 6, 2026
3024764
fix(generator): support prefixed Ack imports in AckType output
leoafarias Feb 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions docs/api-reference/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ Schema for validating strings. See [String Validation](../core-concepts/validati

### Pattern Matching

- `matches(String pattern, {String? example, String? message})`: Must match regex pattern. **Note**: Patterns are NOT automatically anchored - use `^...$` for full-string matching. See [String Validation](../core-concepts/validation.mdx#string-validation) for details.
- `matches(String pattern, {String? example, String? message})`: Must match regex pattern. **Note**: Patterns are NOT automatically anchored - use `^...$` for full-string matching. See [String Validation](../core-concepts/validation.mdx#string-constraints) for details.
- `contains(String pattern, {String? example, String? message})`: Must contain pattern anywhere in string (partial matching)
- `startsWith(String value)`: Must start with specified value
- `endsWith(String value)`: Must end with specified value
Expand Down Expand Up @@ -103,7 +103,7 @@ Schema for validating strings. See [String Validation](../core-concepts/validati

## `IntegerSchema` / `DoubleSchema` (Number Schemas)

Schemas for validating numbers. See [Number Validation](../core-concepts/validation.mdx#number-constraints).
Schemas for validating numbers. See [Number Validation](../core-concepts/validation.mdx#number-constraints-int-and-double).

- `min(num limit)`: Minimum value (inclusive)
- `max(num limit)`: Maximum value (inclusive)
Expand Down Expand Up @@ -131,7 +131,7 @@ Schema for validating arrays. See [List Validation](../core-concepts/validation.

## `ObjectSchema`

Schema for validating objects (maps). See [Object Validation](../core-concepts/schemas.mdx#object-schema).
Schema for validating objects (maps). See [Object Validation](../core-concepts/schemas.mdx#object).

- Constructed using `Ack.object(Map<String, AckSchema> properties, {bool additionalProperties = false})`.
- Use `.pick(List<String> keys)` to create schema with only specified properties.
Expand All @@ -148,10 +148,10 @@ Object returned by `safeParse()`. See [Error Handling](../core-concepts/error-ha

- `bool isOk`: True if validation succeeded
- `bool isFail`: True if validation failed
- `T getOrThrow()`: Returns the validated value or throws an exception
- `T? getOrThrow()`: Returns the validated value (nullable for nullable schemas) or throws an exception
- `T? getOrNull()`: Returns the validated value or null if validation failed
- `SchemaError getError()`: Returns the validation error (only valid when `isFail` is true)
- `T getOrElse(T Function() defaultValue)`: Returns the validated value or a default
- `T? getOrElse(T? Function() orElse)`: Returns the validated value or the fallback result

## `SchemaError` (and Subclasses)

Expand All @@ -169,9 +169,18 @@ Object representing a validation failure. See [Error Handling](../core-concepts/

Base class for creating custom validation rules. See [Custom Validation](../guides/custom-validation.mdx).

- `ConstraintError? validate(T value)`: Validates a value and returns an error if invalid
- `String constraintKey`: Unique key for the constraint
- `String description`: Human-readable description
- `Map<String, Object?> toMap()`: Serializes the constraint for debugging

## `Validator<T>` (Mixin)

Validation behavior mixin used with `Constraint<T>`.

- `bool isValid(T value)`: Returns `true` when the value passes validation
- `String buildMessage(T value)`: Builds the validation failure message
- `ConstraintError? validate(T value)`: Validates a value and returns an error if invalid

## Additional Schema Types

Ack ships with a broad set of schema factories beyond what is listed here.
Expand Down
30 changes: 12 additions & 18 deletions docs/core-concepts/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Most "configuration" happens when you define your schemas using the fluent API p

### Required Fields

Use the `required` parameter in [`Ack.object`](./schemas.mdx#object-schema) to specify mandatory fields within an object.
Fields in [`Ack.object`](./schemas.mdx#object) are required by default. Mark a field as optional with `.optional()`.

```dart
Ack.object({
Expand All @@ -28,7 +28,7 @@ Ack.object({

### Additional Properties

Control how extra fields (not defined in the schema) are handled using the `additionalProperties` parameter in [`Ack.object`](./schemas.mdx#object-schema).
Control how extra fields (not defined in the schema) are handled using the `additionalProperties` parameter in [`Ack.object`](./schemas.mdx#object).

```dart
// Disallow any extra properties (default behavior)
Expand Down Expand Up @@ -134,7 +134,6 @@ class User {
After running `dart run build_runner build`, the generator creates in `user.g.dart`:

1. **Schema constant**: `final userSchema = Ack.object({...});`
2. **Extension type**: `extension type UserType(Map<String, Object?> _data) {...}`

```dart
// Generated in user.g.dart:
Expand All @@ -145,16 +144,7 @@ After running `dart run build_runner build`, the generator creates in `user.g.da
// 'age': Ack.integer().min(18).optional().nullable(),
// }, additionalProperties: true);
//
// extension type UserType(Map<String, Object?> _data) {
// static UserType parse(Object? data) { ... }
// static SchemaResult<UserType> safeParse(Object? data) { ... }
// String get name => ...
// String get email => ...
// int? get age => ...
// Map<String, dynamic> get preferences => ...
// }

// Usage with the generated extension type:
// Usage with the generated schema:
final userData = {
'name': 'John Doe',
'email': 'john@example.com',
Expand All @@ -164,15 +154,19 @@ final userData = {
'notifications': true,
};

final result = UserType.safeParse(userData);
final result = userSchema.safeParse(userData);
if (result.isOk) {
final user = result.getOrThrow();
print(user.name); // Type-safe: John Doe
print(user.email); // Type-safe: john@example.com
print(user.preferences['theme']); // dark (additional properties)
final user = result.getOrThrow()!;
print(user['name']); // John Doe
print(user['email']); // john@example.com
print(user['theme']); // dark (additional properties)
}
```

If you want extension types, define a hand-written top-level schema variable/getter
annotated with `@AckType()` (see
[TypeSafe Schemas](./typesafe-schemas.mdx#typed-schemas-from-hand-written-definitions-with-acktype)).

*See the [Installation Guide](../getting-started/installation.mdx#code-generator-production-ready) for setup instructions and the generator README for advanced features.*

## Summary
Expand Down
12 changes: 6 additions & 6 deletions docs/core-concepts/error-handling.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ All `safeParse()` methods in Ack return a `SchemaResult` object. This object enc
- `result.getOrThrow()`: Returns the validated data if `isOk` is true, otherwise throws an exception.
- `result.getOrNull()`: Returns the validated data if `isOk` is true, otherwise returns `null`.
- `result.getError()`: Returns the `SchemaError` if `isFail` is true, otherwise throws an exception.
- `result.getOrElse(defaultValue)`: Returns the validated data if `isOk` is true, otherwise returns the `defaultValue`.
- `result.getOrElse(() => defaultValue)`: Returns the validated data if `isOk` is true, otherwise calls the fallback function.

```dart
import 'package:ack/ack.dart';
Expand Down Expand Up @@ -42,10 +42,10 @@ if (result.isFail) {

The `SchemaError` object contains details about the validation failure.

- `error.name`: A short identifier for the error type (e.g., `'string'`, `'object'`).
- `error.errorKey`: The specific error key (e.g., `'schema_constraints_error'`, `'schema_nested_error'`).
- `error.name`: The schema/context name where validation failed (for example, a field name or your `debugName`).
- `error.value`: The actual value that failed validation.
- `error.schema`: The schema that was being validated against.
- `error.path`: The JSON Pointer path to the failing location (for example, `#/address/city`).

```dart
final userSchema = Ack.object({
Expand All @@ -68,8 +68,8 @@ final result = userSchema.safeParse(invalidData);

if (result.isFail) {
final error = result.getError();
print('Error Name: ${error.name}'); // Example: 'object'
print('Error Key: ${error.errorKey}'); // Example: 'schema_nested_error'
print('Error Name: ${error.name}'); // Example: 'address'
print('Error Path: ${error.path}'); // Example: '#/address/city'
print('Failed Value: ${error.value}'); // Example: the invalid data
print('Full Error: $error'); // Complete error details

Expand Down Expand Up @@ -251,4 +251,4 @@ Learn more about related topics:
- **[Flutter Forms](/guides/flutter-form-validation)**: Display validation errors in Flutter form widgets
- **[Validation Rules](/core-concepts/validation)**: Explore all built-in validation constraints
- **[Schema Types](/core-concepts/schemas)**: Learn about different schema types and their behavior
- **[Common Recipes](/guides/common-recipes)**: See practical error handling patterns
- **[Common Recipes](/guides/common-recipes)**: See practical error handling patterns
35 changes: 9 additions & 26 deletions docs/core-concepts/json-serialization.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ processApiResponse('not valid json'); // Decoding error

## Working with Validated Data

After successful validation, `result.getOrThrow()` returns a `Map<String, dynamic>` whose structure and types match your schema. You can work with this validated data directly or convert it to a model class:
After successful validation, `result.getOrThrow()` returns a `Map<String, Object?>` whose structure and types match your schema. You can work with this validated data directly or convert it to a model class:

```dart
final result = userSchema.safeParse(jsonData);
Expand All @@ -77,8 +77,8 @@ if (result.isOk) {

// Option 2: Convert to your model class using your preferred method
// - Constructor: User(name: validData['name'], age: validData['age'])
// - json_serializable: User.fromJson(validData)
// - freezed: User.fromJson(validData)
// - json_serializable: User.fromJson(Map<String, dynamic>.from(validData))
// - freezed: User.fromJson(Map<String, dynamic>.from(validData))
// - dart_mappable: UserMapper.fromMap(validData)
// - Manual factory: User.fromMap(validData)
}
Expand Down Expand Up @@ -107,7 +107,6 @@ class User {
After running `dart run build_runner build`, the generator creates in `user.g.dart`:

1. **Schema constant**: `final userSchema = Ack.object({...});`
2. **Extension type**: `extension type UserType(Map<String, Object?> _data) {...}`

```dart
// user.g.dart (generated)
Expand All @@ -116,18 +115,9 @@ final userSchema = Ack.object({
'email': Ack.string(),
'age': Ack.integer(),
});

extension type UserType(Map<String, Object?> _data) {
static UserType parse(Object? data) { ... }
static SchemaResult<UserType> safeParse(Object? data) { ... }
String get name => _data['name'] as String;
String get email => _data['email'] as String;
int get age => _data['age'] as int;
// ... plus toJson(), copyWith(), equality, etc.
}
```

You can use either the schema or the extension type:
Use the generated schema for validation:

```dart
import 'user.dart';
Expand All @@ -146,15 +136,6 @@ if (result.isOk) {
age: validData['age'] as int,
);
}

// Option 2: Use extension type (type-safe)
final result = UserType.safeParse(jsonData);
if (result.isOk) {
final user = result.getOrThrow();
print(user.name); // Type-safe String access
print(user.email); // Type-safe String access
print(user.age); // Type-safe int access
}
```

*See the [ack_generator package](https://pub.dev/packages/ack_generator) for more details on code generation.*
Expand All @@ -179,7 +160,7 @@ final userSchema = Ack.object({

Running `dart run build_runner build` generates in `user_schema.g.dart`:

- **The original schema constant** remains in the generated file (so imports keep working)
- **The original schema constant** stays in your source file (`user_schema.dart`)
- **Extension type** `UserType` with `parse`/`safeParse`, typed getters, and `toJson()` helpers

The type name is derived from the variable name by removing "Schema" suffix and adding "Type" (e.g., `userSchema` → `UserType`).
Expand All @@ -199,10 +180,12 @@ if (result.isOk) {
}
```

**Note**: Nested schemas annotated with `@AckType()` generate their own extension types, but when referenced in object fields, getters return `Map<String, Object?>` rather than the typed extension type.
**Note**: Nested schemas annotated with `@AckType()` generate their own extension types. When referenced in object fields or list elements, getters return the typed extension type (e.g., `AddressType`) when the reference can be resolved in the same library; unresolved references fall back to `Map<String, Object?>`.

`@AckType()` is only supported on top-level schema variables/getters, not classes.

## Key Considerations

- **`dart:convert`:** Use the standard `dart:convert` library (`jsonEncode`, `jsonDecode`) for JSON string conversion. Ack handles validation only.
- **Type Safety:** While `jsonDecode` produces `dynamic`, successful validation gives you confidence in the structure and types of the resulting `Map<String, dynamic>`.
- **Type Safety:** While `jsonDecode` produces `dynamic`, successful validation gives you confidence in the structure and types of the resulting `Map<String, Object?>`.
- **Model Conversion:** After validation, how you convert the validated Map to your model class is your choice. Ack doesn't prescribe a specific pattern - use whatever works best for your project.
20 changes: 8 additions & 12 deletions docs/core-concepts/typesafe-schemas.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -41,25 +41,21 @@ You can use the generated schema for validation:
```dart
final result = userSchema.safeParse(json);
if (result.isOk) {
final data = result.getOrThrow();
final data = result.getOrThrow()!;
final user = User(
name: data['name'] as String,
age: data['age'] as int,
);
}
```

For type-safe access without manual casting, add `@AckType()` to the generated schema variable (see below).
For type-safe access without manual casting, define a schema variable in source and annotate it with `@AckType()` (see below).

### Discriminated Hierarchies

Annotate an abstract base with `discriminatedKey` and each subtype with a
`discriminatedValue` to receive:

- A generated discriminated schema that maps discriminator values to subtype
schemas.
- A sealed class plus subtype extension types so you can switch exhaustively on
the parsed result.
`discriminatedValue` to receive a generated discriminated schema that maps
discriminator values to subtype schemas.

```dart
@AckModel(discriminatedKey: 'type')
Expand Down Expand Up @@ -102,7 +98,7 @@ final addressSchema = Ack.object({

After running `dart run build_runner build`, the part file contains:

- **The original schema constants** remain unchanged (so existing imports keep working).
- **Your original schema constants/getters** remain in your source file.
- **Extension type** `UserType(Map<String, Object?> _data)` with typed getters (removes "Schema" suffix from variable name, adds "Type").
- **Extension type** `AddressType(Map<String, Object?> _data)` for the nested schema.
- **Static helpers** `parse`/`safeParse` so you can do `final user = UserType.parse(json);`.
Expand Down Expand Up @@ -138,15 +134,15 @@ behave like `String`, `int`, etc., while object wrappers implement
| Annotation | Target | Generates Schema? | Generates Extension Type? | Use When |
|------------|--------|-------------------|---------------------------|----------|
| `@AckModel` | Dart class | ✅ Yes | ❌ No | You have a class definition and want schema generation |
| `@AckType` | Schema variable | ❌ No (uses existing) | ✅ Yes | You have a schema and want type-safe access |
| `@AckType` | Top-level schema variable/getter | ❌ No (uses existing) | ✅ Yes | You have a schema and want type-safe access |

**Examples:**

- Use **`@AckModel`** when you have a Dart class (or class hierarchy) that should drive schema generation.

- Use **`@AckType`** on schema variables (either hand-written or generated by `@AckModel`) when you want type-safe extension type access.
- Use **`@AckType`** on hand-written top-level schema variables or getters when you want type-safe extension type access.

- **Combine both**: Use `@AckModel` to generate the schema from your class, then use `@AckType` on the generated schema variable if you want extension type wrappers.
- **Note:** `@AckType` is not supported on classes or generated schemas. If you need extension types, define the schema directly in your source file.

## Build Runner Checklist

Expand Down
10 changes: 5 additions & 5 deletions docs/core-concepts/validation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ final optionalName = Ack.string().nullable();
final optionalAge = Ack.integer().nullable();
```

### `constrain(SchemaConstraint constraint)`
### `constrain(Constraint<T> constraint, {String? message})`

Applies a custom `SchemaConstraint`. See the [Custom Validation](../guides/custom-validation.mdx) guide.
Applies a custom `Constraint<T>` that also implements `Validator<T>`. See the [Custom Validation](../guides/custom-validation.mdx) guide.

## String Constraints

Apply these to [`Ack.string()`](./schemas.mdx#string-schema) schemas.
Apply these to [`Ack.string()`](./schemas.mdx#string) schemas.

### `minLength(int min)`
Ensures the string has at least `min` characters.
Expand Down Expand Up @@ -192,7 +192,7 @@ Ack.string().toUpperCase()

## Number Constraints (Int and Double)

Apply these to [`Ack.integer()`](./schemas.mdx#number-schemas) and [`Ack.double()`](./schemas.mdx#number-schemas) schemas.
Apply these to [`Ack.integer()`](./schemas.mdx#number) and [`Ack.double()`](./schemas.mdx#number) schemas.

### `min(num limit)`
Ensures the number is greater than or equal to the `limit` (inclusive).
Expand Down Expand Up @@ -257,7 +257,7 @@ Ack.double().finite()

## List Constraints

Apply these to [`Ack.list()`](./schemas.mdx#list-schema) schemas.
Apply these to [`Ack.list()`](./schemas.mdx#list) schemas.

### `minLength(int min)`
Ensures the list has at least `min` items.
Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started/installation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,4 @@ See the [Quickstart Tutorial](./quickstart-tutorial.mdx) for a more complete exa

## Requirements

- Dart SDK: `>=2.17.0 <4.0.0`
- Dart SDK: `>=3.8.0 <4.0.0`
Loading
Loading