Skip to content

feat: union-owned discriminator for discriminated schemas - #107

Merged
leoafarias merged 3 commits into
mainfrom
feat/union-owned-discriminator
May 20, 2026
Merged

leoafarias merged 3 commits into
mainfrom
feat/union-owned-discriminator

Conversation

@leoafarias

@leoafarias leoafarias commented May 19, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Branches in Ack.discriminated(...) no longer need to redeclare the discriminator property; the union owns it and synthesizes an effective branch with the literal injected first for validation and JSON Schema export.
  • If a branch declares the discriminator property, it must be Ack.literal(...) matching the branch key or Ack.enumString(...) containing the branch key. Broad Ack.string(), transformed/refined discriminator fields, and restrictive discriminator chains are rejected.
  • Adds DiscriminatedObjectSchema.effectiveBranch, the effectiveDiscriminatedBranch util, and TransformedSchema.copyWithSchema; refactors ack_generator to emit subtype parse/safeParse via the union effective branch and updates docs, examples, and tests.

Test plan

  • melos run build --no-select
  • melos run analyze --no-select
  • melos run test:dart --no-select
  • melos run test:flutter --no-select
  • melos run validate-jsonschema --no-select
  • git diff --check

Branches in Ack.discriminated(...) no longer need to redeclare the
discriminator property. The union owns the discriminator and synthesizes
an effective branch with the literal injected (first in the property
order, marked required) for validation and JSON Schema export. Branches
may still declare the discriminator as Ack.literal, Ack.enumString, or
unconstrained Ack.string — the analyzer verifies compatibility.

Adds DiscriminatedObjectSchema.effectiveBranch, effectiveDiscriminatedBranch
util, TransformedSchema.copyWithSchema, and refactors the ack_generator
analyzer + type_builder to emit subtype parse/safeParse via the union's
effective branch. Docs, examples, and tests updated.
@docs-page

docs-page Bot commented May 19, 2026

Copy link
Copy Markdown

To view this pull requests documentation preview, visit the following URL:

docs.page/btwld/ack~107

Documentation is deployed and generated using docs.page.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR updates Ack.discriminated(...) so the union owns the discriminator property: branch schemas can omit the discriminator field, and the runtime/generator synthesize an “effective branch” schema with a required literal discriminator injected first (used for validation and JSON Schema export).

Changes:

  • Add “effective branch” synthesis (DiscriminatedObjectSchema.effectiveBranch, effectiveDiscriminatedBranch(...)) and use it for discriminated validation + JSON Schema export.
  • Add TransformedSchema.copyWithSchema to support injecting discriminator literals through object-backed transforms.
  • Update ack_generator analysis + emitted subtype parse/safeParse to validate through the union’s effective branch; update docs/examples/tests accordingly.

Reviewed changes

Copilot reviewed 21 out of 23 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/ack/test/schemas/parse_result_immutability_test.dart Updates discriminated branch test setup to omit discriminator property.
packages/ack/test/schemas/json_schema_default_emission_test.dart Adjusts JSON Schema emission test for discriminator-owned behavior.
packages/ack/test/schemas/discriminated_object_schema_test.dart Adds extensive runtime tests covering union-owned discriminator policy + effectiveBranch behavior.
packages/ack/test/schemas/comprehensive_json_schema_test.dart Updates comprehensive JSON Schema tests to omit discriminator in branches.
packages/ack/test/schemas/composite_default_test.dart Updates composite default tests for discriminator-owned branches.
packages/ack/test/converters/ack_to_json_schema_model_test.dart Adds model-conversion tests for omitted/compatible discriminator properties and rejection cases.
packages/ack/lib/src/utils/discriminated_branch_utils.dart Introduces structural, side-effect-free discriminator compatibility checking + effective-branch synthesis.
packages/ack/lib/src/schemas/transformed_schema.dart Adds copyWithSchema to rebuild transforms with a different input schema.
packages/ack/lib/src/schemas/discriminated_object_schema.dart Adds effectiveBranch and refactors parsing/JSON Schema export to use effective branches.
packages/ack/lib/src/converters/ack_to_json_schema_model.dart Refactors discriminated conversion to convert effective branches and enforce discriminator-first ordering.
packages/ack_json_schema_builder/test/to_json_schema_builder_test.dart Updates builder conversion tests for compatible discriminator handling and new failure mode.
packages/ack_generator/test/integration/ack_type_discriminated_test.dart Expands integration coverage for omitted/compatible discriminators and rejection diagnostics.
packages/ack_generator/README.md Documents union-owned discriminator rules for generation.
packages/ack_generator/lib/src/builders/type_builder.dart Emits discriminated subtype factories using baseSchema.effectiveBranch(value); adjusts args filtering.
packages/ack_generator/lib/src/analyzer/schema_ast_analyzer.dart Updates discriminated analysis to allow omitted/compatible discriminator properties and reject incompatible ones with clearer errors.
packages/ack_firebase_ai/test/to_firebase_ai_schema_test.dart Updates Firebase AI schema conversion tests for compatible discriminator handling.
llms.txt Updates canonical API/generation guidance for discriminator-owned discriminated unions.
example/test/schema_types_discriminated_test.dart Adds example tests for generated discriminated types with omitted/explicit branch discriminators.
example/lib/schema_types_discriminated.g.dart Updates generated example subtype parsing to validate via effectiveBranch.
example/lib/schema_types_discriminated.dart Updates example source to omit discriminator fields in branches.
example/lib/pet.g.dart Updates generated example subtype parsing to validate via effectiveBranch.
docs/core-concepts/typesafe-schemas.mdx Updates guide docs to describe discriminator-owned discriminated unions and generation behavior.
docs/api-reference/index.mdx Updates API reference with discriminator-owned semantics and effectiveBranch(...).
Files not reviewed (2)
  • example/lib/pet.g.dart: Language not supported
  • example/lib/schema_types_discriminated.g.dart: Language not supported

Comment on lines +189 to 193
final AckSchema<T> effectiveSubSchema;
try {
effectiveSubSchema = effectiveBranch(discValueRaw);
} on ArgumentError catch (error) {
return SchemaResult.fail(
Comment thread llms.txt
Comment on lines +100 to 103
- branch schemas normally omit the discriminator field
- if a branch includes the discriminator field, it must be `Ack.literal(...)`
matching the branch key or `Ack.enumString(...)` containing the branch key

@leoafarias
leoafarias merged commit f0189c8 into main May 20, 2026
7 checks passed
@leoafarias
leoafarias deleted the feat/union-owned-discriminator branch May 20, 2026 01:14
leoafarias added a commit that referenced this pull request May 27, 2026
The synthesized discriminator schema produced by effectiveDiscriminatedObjectBranch
now carries withDefault(discriminatorValue), so a discriminated branch whose runtime
encode lambda omits the discriminator key still emits it via the default. Closes
the encode side of PR #107's union-owned discriminator story (parse already
synthesized the literal; encode previously required the branch to emit the key).

ack_schema_model_builder.\_discriminated wraps each branch's exported model with
_withRequiredDiscriminator so the JSON Schema marks the discriminator as required
and strips the synthetic default from the output.
leoafarias added a commit that referenced this pull request May 28, 2026
The previous commit added `_matchesAnyBranchRuntime` plus a map-block
in `validateRuntimeWithContext` so that calling validateRuntime
directly stayed consistent with what encode would do for a map missing
the discriminator. The consistency was edge-case and the cost was
~21 lines that most users never observe.

Drop both. The encode fast path now calls `_validateDiscriminatorKey`
inline to keep focused errors for non-string and unknown discriminator
values (this used to fall out of the validateRuntime block).
validateRuntime called directly is now stricter than encode for the
specific case of "a JsonMap missing the discriminator key", which is a
defensible invariant: a valid discriminated runtime carries its label.

Behavior preserved: PR #107 union-owned discriminator (branches that
omit the property) still works via `effectiveBranch`, and PR #109
codec branches that synthesize the discriminator at encode time still
succeed through the slow path.

Net: -33 lines on this PR.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants