Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:

jobs:
test:
uses: btwld/dart-actions/.github/workflows/ci.yml@main
uses: btwld/dart-actions/.github/workflows/ci.yml@9075ce1232ec77b8747953f2ff4a349190e5a805
secrets:
token: ${{ secrets.GITHUB_TOKEN }}
with:
Expand Down
20 changes: 19 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,26 @@ on:
- 'v*'

jobs:
validate-tag:
runs-on: ubuntu-latest
outputs:
semver_ok: ${{ steps.validate-tag.outputs.semver_ok }}
steps:
- name: Validate release tag as SemVer 2.0.0 with optional pre-release/build metadata
id: validate-tag
shell: bash
run: |
TAG="${{ github.ref_name }}"
if [[ ! "$TAG" =~ ^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-(0|[1-9][0-9]*|[0-9A-Za-z-]+\.)*[0-9A-Za-z-]+)?(\+(0|[1-9][0-9]*|[0-9A-Za-z-]+\.)*[0-9A-Za-z-]+)?$ ]]; then
echo "::error::Release tag '$TAG' is not SemVer 2.0.0 compatible. Use tags like v1.0.0, v1.0.0-alpha.1, or v1.0.0-beta.6+exp.sha."
exit 1
fi
echo "semver_ok=true" >> "$GITHUB_OUTPUT"

publish:
uses: btwld/dart-actions/.github/workflows/publish.yml@main
needs: validate-tag
if: needs.validate-tag.outputs.semver_ok == 'true'
uses: btwld/dart-actions/.github/workflows/publish.yml@9075ce1232ec77b8747953f2ff4a349190e5a805
permissions:
id-token: write
with:
Expand Down
4 changes: 2 additions & 2 deletions docs/api-reference/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Entry point for creating schemas. See [Schema Types](../core-concepts/schemas.md
- `Ack.enumString(List<String> values)`: Creates a string schema that only accepts specific values.
- `Ack.anyOf(List<AckSchema> schemas)`: Creates an `AnyOfSchema` for union types.
- `Ack.any()`: Creates an `AnySchema` that accepts any value.
- `Ack.discriminated({required String discriminatorKey, required Map<String, AckSchema> schemas})`: Creates a discriminated union schema.
- `Ack.discriminated({required String discriminatorKey, required Map<String, AckSchema<MapValue>> schemas})`: Creates a discriminated union schema.

Copilot AI Feb 17, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docs now mention AckSchema<MapValue>, but MapValue isn’t introduced/explained on this page and (from the public exports) may not be importable by consumers without reaching into src/. Consider documenting it (and how users should reference it) or keep the signature in docs as Map<String, AckSchema<Map<String, Object?>>> / Map<String, AckSchema<Map<String, Object?>>> to avoid exposing an internal alias.

Suggested change
- `Ack.discriminated({required String discriminatorKey, required Map<String, AckSchema<MapValue>> schemas})`: Creates a discriminated union schema.
- `Ack.discriminated({required String discriminatorKey, required Map<String, AckSchema<Map<String, Object?>>> schemas})`: Creates a discriminated union schema.

Copilot uses AI. Check for mistakes.

## `AckSchema<T>` (Base Class)

Expand Down Expand Up @@ -285,7 +285,7 @@ Schema for union types (value must match one of several schemas).

Schema for polymorphic validation based on a discriminator field.

- Created using `Ack.discriminated({required String discriminatorKey, required Map<String, AckSchema> schemas})`
- Created using `Ack.discriminated({required String discriminatorKey, required Map<String, AckSchema<MapValue>> schemas})`

Copilot AI Feb 17, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reference uses AckSchema<MapValue> but MapValue isn’t explained here and may not be part of the stable public surface (it’s not exported from package:ack/ack.dart). Consider replacing with an explicit Map<String, Object?> type in the docs, or add a short note defining MapValue for readers.

Suggested change
- Created using `Ack.discriminated({required String discriminatorKey, required Map<String, AckSchema<MapValue>> schemas})`
- Created using `Ack.discriminated({required String discriminatorKey, required Map<String, AckSchema<Map<String, Object?>>> schemas})`

Copilot uses AI. Check for mistakes.

### `AnySchema`

Expand Down
28 changes: 28 additions & 0 deletions example/test/extension_type_to_json_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import 'package:ack_example/schema_types_primitives.dart';
import 'package:ack_example/schema_types_simple.dart';
import 'package:test/test.dart';

void main() {
group('Extension type toJson', () {
test('object extension type returns map data', () {
final user = UserType.parse({'name': 'Alice', 'age': 30, 'active': true});

expect(user.toJson(), {'name': 'Alice', 'age': 30, 'active': true});
expect(user.toJson(), isA<Map<String, Object?>>());
});

test('primitive extension type returns wrapped value', () {
final password = PasswordType.parse('mySecurePassword123');

expect(password.toJson(), 'mySecurePassword123');
expect(password.toJson(), isA<String>());
});

test('collection extension type returns wrapped list', () {
final tags = TagsType.parse(['dart', 'ack']);

expect(tags.toJson(), ['dart', 'ack']);
expect(tags.toJson(), isA<List<String>>());
});
});
}
18 changes: 3 additions & 15 deletions packages/ack/lib/src/schemas/transformed_schema.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,36 +60,24 @@ class TransformedSchema<InputType extends Object, OutputType extends Object>
Object? inputValue,
SchemaContext context,
) {
// Handle TransformedSchema's own defaultValue for null input.
// Clone the default to prevent mutation of shared state.
// This must happen BEFORE delegating to wrapped schema, because the wrapped
// schema might not accept null (e.g., non-nullable StringSchema).
//
// NOTE: cloneDefault() returns List<Object?> or Map<Object?, Object?> for
// collections, which cannot be safely cast to parameterized OutputType like
// List<MyClass>. We use runtime type checking: if the clone is type-compatible,
// use it; otherwise fall back to the original (accepts mutation risk for
// parameterized collection defaults, but avoids runtime TypeError).
// Handle defaults before delegation, since wrapped schemas may reject null.
// If cloning loses generic type information, fall back to the original value.
if (inputValue == null && defaultValue != null) {
final cloned = cloneDefault(defaultValue!);
final safeDefault = (cloned is OutputType) ? cloned : defaultValue!;
return applyConstraintsAndRefinements(safeDefault, context);
}

// For non-null input OR null input without default:
// Delegate to underlying schema (handles type conversion, null validation, constraints)
// The inner schema determines if null is valid based on its own isNullable setting.
// Delegate validation/parsing to the wrapped schema for all other cases.
final originalResult = schema.parseAndValidate(inputValue, context);
if (originalResult.isFail) {
return SchemaResult.fail(originalResult.getError());
}

// Transform the validated value (may be null if underlying schema is nullable)
final validatedValue = originalResult.getOrNull();
try {
final transformedValue = transformer(validatedValue);

// Apply TransformedSchema's own constraints and refinements
return applyConstraintsAndRefinements(transformedValue, context);
} catch (e, st) {
return SchemaResult.fail(
Expand Down
1 change: 1 addition & 0 deletions packages/ack_annotations/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

* **AckType**: Refined annotation parameters and improved type handling (#50).
* **AckField**: Improved field annotation correctness (#50).
* **Breaking**: `AckField.required` was replaced by `requiredMode` (`AckFieldRequiredMode.auto|required|optional`). Migrate `@AckField(required: true)` to `@AckField(requiredMode: AckFieldRequiredMode.required)` and `required: false` to `requiredMode: AckFieldRequiredMode.optional`.

## 1.0.0-beta.5 (2026-01-14)

Expand Down
4 changes: 2 additions & 2 deletions packages/ack_annotations/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ Add to your `pubspec.yaml` (check [pub.dev](https://pub.dev/packages/ack_annotat

```yaml
dependencies:
ack_annotations: ^1.0.0
ack_annotations: ^1.0.0-beta.6

dev_dependencies:
ack_generator: ^1.0.0
ack_generator: ^1.0.0-beta.6
build_runner: ^2.4.0
```

Expand Down
6 changes: 3 additions & 3 deletions packages/ack_generator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ Add the following dependencies to your `pubspec.yaml` (check [pub.dev](https://p

```yaml
dependencies:
ack: ^1.0.0
ack_annotations: ^1.0.0
ack: ^1.0.0-beta.6
ack_annotations: ^1.0.0-beta.6

dev_dependencies:
ack_generator: ^1.0.0
ack_generator: ^1.0.0-beta.6
build_runner: ^2.4.0
```

Expand Down
Loading
Loading