Skip to content

refactor!: refactor union properties handling - #701

Merged
stuebingerb merged 1 commit into
mainfrom
refactor/refactor-union-properties
Jul 28, 2026
Merged

refactor!: refactor union properties handling#701
stuebingerb merged 1 commit into
mainfrom
refactor/refactor-union-properties

Conversation

@stuebingerb

Copy link
Copy Markdown
Owner

Refactors handling of union properties and processes them just like regular fragments, thereby benefitting from all recent bugfixes and simplifying code.

Fixes #697

BREAKING CHANGE: removed Type.Union, Field.Union, and Execution.Union.

BREAKING CHANGE: invalid fragment conditions in union properties were previously simply ignored and now cause the query to fail.

@stuebingerb stuebingerb added the breaking Introduces a breaking change label Jul 21, 2026
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@stuebingerb, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 45 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6caf4c18-c49f-44af-88fe-95b2be05e616

📥 Commits

Reviewing files that changed from the base of the PR and between 6a3fee3 and 542d4c1.

📒 Files selected for processing (11)
  • kgraphql/api/kgraphql.api
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/execution/Execution.kt
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/execution/ParallelRequestExecutor.kt
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/structure/Field.kt
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/structure/RequestInterpreter.kt
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/structure/SchemaCompilation.kt
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/structure/Validation.kt
  • kgraphql/src/test/kotlin/de/stuebingerb/kgraphql/schema/SchemaBuilderTest.kt
  • kgraphql/src/test/kotlin/de/stuebingerb/kgraphql/specification/language/FragmentsSpecificationTest.kt
  • kgraphql/src/test/kotlin/de/stuebingerb/kgraphql/specification/language/VariablesSpecificationTest.kt
  • kgraphql/src/test/kotlin/de/stuebingerb/kgraphql/specification/typesystem/UnionsSpecificationTest.kt
📝 Walkthrough

Walkthrough

Union-specific field and execution classes are removed. Union properties now use generic function fields and execution paths, with runtime type validation and fragment-condition checks for invalid union targets.

Changes

Union execution simplification

Layer / File(s) Summary
Remove union-specific public models
kgraphql/src/main/kotlin/.../Execution.kt, kgraphql/src/main/kotlin/.../Field.kt, kgraphql/api/kgraphql.api
The Execution.Union and Field.Union classes, their members, and exported API declarations are removed.
Route union fields through generic interpretation
kgraphql/src/main/kotlin/.../SchemaCompilation.kt, kgraphql/src/main/kotlin/.../RequestInterpreter.kt, kgraphql/src/main/kotlin/.../Validation.kt, kgraphql/src/test/kotlin/.../FragmentsSpecificationTest.kt
Union properties are compiled as Field.Function; union-specific request handling and validation are removed, and invalid fragment type conditions are tested.
Simplify union execution and validation
kgraphql/src/main/kotlin/.../ParallelRequestExecutor.kt, kgraphql/src/test/kotlin/.../SchemaBuilderTest.kt, kgraphql/src/test/kotlin/.../UnionsSpecificationTest.kt, kgraphql/src/test/kotlin/.../VariablesSpecificationTest.kt
Execution uses generic nodes with runtime type checks, while field, error-message, union-result, and numeric-input expectations are updated.

Sequence Diagram(s)

sequenceDiagram
  participant RequestInterpreter
  participant ParallelRequestExecutor
  participant UnionResolver
  participant RuntimeTypeCheck
  RequestInterpreter->>ParallelRequestExecutor: create generic property execution node
  ParallelRequestExecutor->>UnionResolver: resolve union-backed property
  UnionResolver-->>ParallelRequestExecutor: return runtime value
  ParallelRequestExecutor->>RuntimeTypeCheck: validate possible union/interface type
  RuntimeTypeCheck-->>ParallelRequestExecutor: accept value or raise ExecutionError
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The added Int variable exponential-notation test is unrelated to union property fragment validation. Move the variable-coercion test to a separate PR unless it is intentionally part of this change.
Docstring Coverage ⚠️ Warning Docstring coverage is 8.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and clearly describes the union-property refactor in conventional-commit style.
Description check ✅ Passed The description matches the union-property refactor and its breaking changes.
Linked Issues check ✅ Passed The changes address #697 by routing union properties through shared fragment validation and adding tests for invalid conditions.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/refactor-union-properties

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@stuebingerb
stuebingerb force-pushed the refactor/refactor-union-properties branch from 626bac4 to dbec446 Compare July 21, 2026 16:21

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 935f9711-9e5f-41fa-ad6c-09d59f9d8b42

📥 Commits

Reviewing files that changed from the base of the PR and between f72e1f3 and 626bac4.

📒 Files selected for processing (10)
  • kgraphql/api/kgraphql.api
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/execution/Execution.kt
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/execution/ParallelRequestExecutor.kt
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/structure/Field.kt
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/structure/RequestInterpreter.kt
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/structure/SchemaCompilation.kt
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/structure/Validation.kt
  • kgraphql/src/test/kotlin/de/stuebingerb/kgraphql/schema/SchemaBuilderTest.kt
  • kgraphql/src/test/kotlin/de/stuebingerb/kgraphql/specification/language/FragmentsSpecificationTest.kt
  • kgraphql/src/test/kotlin/de/stuebingerb/kgraphql/specification/typesystem/UnionsSpecificationTest.kt
💤 Files with no reviewable changes (5)
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/structure/Validation.kt
  • kgraphql/api/kgraphql.api
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/execution/Execution.kt
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/structure/RequestInterpreter.kt
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/structure/Field.kt

@codecov

codecov Bot commented Jul 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 69.23077% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.05%. Comparing base (f72e1f3) to head (542d4c1).

Files with missing lines Patch % Lines
...raphql/schema/execution/ParallelRequestExecutor.kt 66.66% 0 Missing and 4 partials ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main     #701   +/-   ##
=======================================
  Coverage   84.05%   84.05%           
=======================================
  Files         151      151           
  Lines        5024     4930   -94     
  Branches      870      853   -17     
=======================================
- Hits         4223     4144   -79     
+ Misses        493      488    -5     
+ Partials      308      298   -10     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown

🐰 Bencher Report

ProjectKGraphQL
Branchrefactor/refactor-union-properties
Testbedubuntu-latest
Click to view all benchmark results
BenchmarkThroughputBenchmark Result
operations / second (ops/s)
(Result Δ%)
Lower Boundary
operations / second (ops/s)
(Limit %)
de.stuebingerb.kgraphql.FunctionExecutionBenchmark.benchmarkFunctionExecution📈 view plot
🚷 view threshold
5,518,735.65 ops/s
(-4.06%)Baseline: 5,752,321.95 ops/s
4,990,269.02 ops/s
(90.42%)
de.stuebingerb.kgraphql.ParallelExecutionBenchmark.queryBenchmark📈 view plot
🚷 view threshold
1.30 ops/s
(-0.00%)Baseline: 1.30 ops/s
1.30 ops/s
(99.86%)
de.stuebingerb.kgraphql.QueryBenchmark.executionError📈 view plot
🚷 view threshold
21,696.83 ops/s
(+23.79%)Baseline: 17,527.48 ops/s
10,830.62 ops/s
(49.92%)
de.stuebingerb.kgraphql.QueryBenchmark.inputFromDocument📈 view plot
🚷 view threshold
25,022.13 ops/s
(+16.26%)Baseline: 21,522.53 ops/s
14,081.46 ops/s
(56.28%)
de.stuebingerb.kgraphql.QueryBenchmark.inputFromVariable📈 view plot
🚷 view threshold
24,526.57 ops/s
(+17.82%)Baseline: 20,817.14 ops/s
13,893.32 ops/s
(56.65%)
de.stuebingerb.kgraphql.QueryBenchmark.largeList📈 view plot
🚷 view threshold
4.69 ops/s
(-0.59%)Baseline: 4.72 ops/s
4.15 ops/s
(88.50%)
de.stuebingerb.kgraphql.QueryBenchmark.largeListWithFragment📈 view plot
🚷 view threshold
5.25 ops/s
(+0.12%)Baseline: 5.24 ops/s
4.60 ops/s
(87.66%)
de.stuebingerb.kgraphql.QueryBenchmark.manyChildren📈 view plot
🚷 view threshold
190.44 ops/s
(+4.06%)Baseline: 183.00 ops/s
143.40 ops/s
(75.30%)
de.stuebingerb.kgraphql.QueryBenchmark.manyChildrenWithFragment📈 view plot
🚷 view threshold
202.27 ops/s
(+4.05%)Baseline: 194.41 ops/s
153.83 ops/s
(76.05%)
de.stuebingerb.kgraphql.QueryBenchmark.manyDataChildren📈 view plot
🚷 view threshold
8.90 ops/s
(-0.04%)Baseline: 8.91 ops/s
8.79 ops/s
(98.74%)
de.stuebingerb.kgraphql.QueryBenchmark.manyOperations📈 view plot
🚷 view threshold
317.05 ops/s
(+3.87%)Baseline: 305.23 ops/s
245.21 ops/s
(77.34%)
de.stuebingerb.kgraphql.QueryBenchmark.manyOperationsWithFragment📈 view plot
🚷 view threshold
321.63 ops/s
(+1.62%)Baseline: 316.49 ops/s
259.76 ops/s
(80.76%)
de.stuebingerb.kgraphql.QueryBenchmark.nestedObject📈 view plot
🚷 view threshold
9,238.83 ops/s
(+18.56%)Baseline: 7,792.58 ops/s
6,035.50 ops/s
(65.33%)
de.stuebingerb.kgraphql.RequestCachingBenchmark.invalidRequest📈 view plot
🚷 view threshold
144,255.36 ops/s
(+1.64%)Baseline: 141,934.06 ops/s
135,534.91 ops/s
(93.95%)
de.stuebingerb.kgraphql.RequestCachingBenchmark.largeRequest📈 view plot
🚷 view threshold
9,377.14 ops/s
(+17.06%)Baseline: 8,010.46 ops/s
5,827.56 ops/s
(62.15%)
de.stuebingerb.kgraphql.RequestCachingBenchmark.smallRequest📈 view plot
🚷 view threshold
13,702.53 ops/s
(+18.53%)Baseline: 11,560.29 ops/s
8,346.93 ops/s
(60.92%)
de.stuebingerb.kgraphql.SimpleExecutionOverheadBenchmark.benchmark📈 view plot
🚷 view threshold
480,420.97 ops/s
(+1.75%)Baseline: 472,157.67 ops/s
446,326.41 ops/s
(92.90%)
🐰 View full continuous benchmarking report in Bencher

@cubic-dev-ai cubic-dev-ai Bot 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.

No issues found across 10 files

Architecture diagram
sequenceDiagram
    participant Client
    participant SC as SchemaCompilation
    participant RI as RequestInterpreter
    participant Exe as Execution.Node
    participant PRE as ParallelRequestExecutor
    participant Resolver

    Note over SC,Resolver: Schema Build Time
    SC->>SC: handleUnionProperty(unionProperty)
    SC->>SC: CHANGED: create Field.Function instead of Field.Union

    Note over Client,PRE: Query Execution Time
    Client->>RI: POST /graphql (query with union field + fragments)
    RI->>RI: parse selection set for union field
    RI->>RI: lookup field: returns Field.Function (returnType=Type.Union)
    loop each selection (fragment spread / inline fragment)
        RI->>RI: validate fragment type condition against union's possibleTypes
        alt Invalid type condition
            RI-->>Client: ValidationException (error)
        else Valid
            RI->>Exe: CHANGED: create Execution.Node<br>with children as Execution.Fragment<br>per typed fragment
        end
    end
    RI->>PRE: pass execution tree

    PRE->>PRE: createNode(value, node, returnType)
    PRE->>Resolver: invoke union property resolver
    Resolver-->>PRE: union value (e.g. Actor instance)
    loop each child Execution.Fragment
        PRE->>PRE: check if value's type matches fragment's type condition
        alt Match
            PRE->>PRE: execute fragment's field selections
        else No match
            PRE->>PRE: skip fragment
        end
    end
    PRE-->>Client: JSON result with fields from matching fragment
Loading

Re-trigger cubic

@stuebingerb

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/execution/ParallelRequestExecutor.kt (1)

190-192: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Restore runtime type validation for union returns.

By deleting createUnionOperationNode and routing unions through this generic path, the executor no longer validates that the resolved value is actually a member of the union's possibleTypes. This introduces two specification violations for abstract types:

  1. Type Leakage: If a union resolver returns an invalid object (e.g., Director instead of Actor or Film), querying __typename will successfully return "Director", leaking a type that isn't defined as a valid member of the union into the response.
  2. Silent Empty Objects: If the invalid object is queried with inline fragments, no fragments will match, and the executor will silently return {} instead of raising a field error as required by the GraphQL specification for invalid abstract type resolutions.

Additionally, because primitive checks (like value is String) happen before node.children.isNotEmpty(), returning a primitive from a union resolver will incorrectly serialize as a scalar, bypassing the selection set entirely.

Please consider restoring a runtime validation check in this block to ensure that value conforms to returnType.unwrapped().possibleTypes for abstract types before proceeding to create the object node.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0bb5979a-20f6-4a53-846f-0b6e57a2c54b

📥 Commits

Reviewing files that changed from the base of the PR and between f72e1f3 and dbec446.

📒 Files selected for processing (10)
  • kgraphql/api/kgraphql.api
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/execution/Execution.kt
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/execution/ParallelRequestExecutor.kt
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/structure/Field.kt
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/structure/RequestInterpreter.kt
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/structure/SchemaCompilation.kt
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/structure/Validation.kt
  • kgraphql/src/test/kotlin/de/stuebingerb/kgraphql/schema/SchemaBuilderTest.kt
  • kgraphql/src/test/kotlin/de/stuebingerb/kgraphql/specification/language/FragmentsSpecificationTest.kt
  • kgraphql/src/test/kotlin/de/stuebingerb/kgraphql/specification/typesystem/UnionsSpecificationTest.kt
💤 Files with no reviewable changes (5)
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/structure/Validation.kt
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/execution/Execution.kt
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/structure/Field.kt
  • kgraphql/api/kgraphql.api
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/structure/RequestInterpreter.kt

@stuebingerb

stuebingerb commented Jul 21, 2026

Copy link
Copy Markdown
Owner Author

CodeRabbit is right, the actual return type is no longer validated against the possible union types. I have a draft implementation but I'm not sure if I want to restore that check now, and if to what extent - I'd expect that invalid return types are possible in other places as well.

Edit: in fact, everything except the unionProperty seems to be type-safe (e.g. you cannot simply specify returns<IllegalType>() on a resolver).

@stuebingerb
stuebingerb force-pushed the refactor/refactor-union-properties branch from dbec446 to 6a3fee3 Compare July 27, 2026 17:25
@stuebingerb

Copy link
Copy Markdown
Owner Author

CodeRabbit is right, the actual return type is no longer validated against the possible union types. I have a draft implementation but I'm not sure if I want to restore that check now, and if to what extent - I'd expect that invalid return types are possible in other places as well.

Edit: in fact, everything except the unionProperty seems to be type-safe (e.g. you cannot simply specify returns<IllegalType>() on a resolver).

Restored validation: https://github.com/stuebingerb/KGraphQL/compare/dbec446f2fef5d4ab41c185a86e0bb46aee33873..6a3fee323624be328e5bac84c100cbb01f30d147

@coderabbitai coderabbitai Bot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/execution/ParallelRequestExecutor.kt (1)

190-190: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Validate composite results before serializing primitives.

A resolver returning "invalid" for a union/interface selection hits the String branch before Line 190, so it produces a scalar JSON value without checking possibleTypes. Move the deferred/object-selection handling ahead of primitive serialization, then add a scalar-invalid-union regression case.

Proposed fix
+                value is Deferred<*> -> createNode(ctx, value.await(), node, returnType)
+
+                node.children.isNotEmpty() -> createObjectNode(ctx, value, node, returnType)
+
                 value is String -> CompletableDeferred(jsonNodeFactory.textNode(value))
                 value is Int -> CompletableDeferred(jsonNodeFactory.numberNode(value))
                 value is Float -> CompletableDeferred(jsonNodeFactory.numberNode(value))
                 value is Double -> CompletableDeferred(jsonNodeFactory.numberNode(value))
                 value is Boolean -> CompletableDeferred(jsonNodeFactory.booleanNode(value))
                 value is Long -> CompletableDeferred(jsonNodeFactory.numberNode(value))
                 value is Short -> CompletableDeferred(jsonNodeFactory.numberNode(value))
-
-                value is Deferred<*> -> createNode(ctx, value.await(), node, returnType)
-
-                node.children.isNotEmpty() -> createObjectNode(ctx, value, node, returnType)

Also applies to: 229-238


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 741ca9a7-d553-4c53-bef0-8a75d6bf159d

📥 Commits

Reviewing files that changed from the base of the PR and between dbec446 and 6a3fee3.

📒 Files selected for processing (11)
  • kgraphql/api/kgraphql.api
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/execution/Execution.kt
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/execution/ParallelRequestExecutor.kt
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/structure/Field.kt
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/structure/RequestInterpreter.kt
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/structure/SchemaCompilation.kt
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/structure/Validation.kt
  • kgraphql/src/test/kotlin/de/stuebingerb/kgraphql/schema/SchemaBuilderTest.kt
  • kgraphql/src/test/kotlin/de/stuebingerb/kgraphql/specification/language/FragmentsSpecificationTest.kt
  • kgraphql/src/test/kotlin/de/stuebingerb/kgraphql/specification/language/VariablesSpecificationTest.kt
  • kgraphql/src/test/kotlin/de/stuebingerb/kgraphql/specification/typesystem/UnionsSpecificationTest.kt
💤 Files with no reviewable changes (5)
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/structure/Field.kt
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/structure/Validation.kt
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/execution/Execution.kt
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/structure/RequestInterpreter.kt
  • kgraphql/api/kgraphql.api
🚧 Files skipped from review as they are similar to previous changes (3)
  • kgraphql/src/test/kotlin/de/stuebingerb/kgraphql/schema/SchemaBuilderTest.kt
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/structure/SchemaCompilation.kt
  • kgraphql/src/test/kotlin/de/stuebingerb/kgraphql/specification/language/FragmentsSpecificationTest.kt

Refactors handling of union properties and processes them just like
regular fragments, thereby benefitting from all recent bugfixes and
simplifying code.

Fixes #697

BREAKING CHANGE: removed `Type.Union`, `Field.Union`, and
`Execution.Union`.

BREAKING CHANGE: invalid fragment conditions in union properties were
previously simply ignored and now cause the query to fail.
@stuebingerb
stuebingerb force-pushed the refactor/refactor-union-properties branch from 6a3fee3 to 542d4c1 Compare July 27, 2026 17:40

@cubic-dev-ai cubic-dev-ai Bot 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.

No issues found across 11 files

Architecture diagram
sequenceDiagram
    participant Client
    participant SchemaComp as Schema Compilation
    participant RequestInterp as Request Interpreter
    participant Validation
    participant Executor as Parallel Request Executor
    participant FieldFunc as Field.Function
    participant ExecNode as Execution.Node
    participant FragmentHandler as Fragment Logic
    participant TypeCheck as Type Checking

    Note over Client,TypeCheck: Current architecture after union property refactor

    Client->>SchemaComp: Define schema with union property
    SchemaComp->>SchemaComp: handleUnionProperty()
    Note over SchemaComp: NEW: Compiles union property to Field.Function (instead of Field.Union)
    SchemaComp-->>SchemaComp: Type resolved as union/interface

    Client->>RequestInterp: Execute query (includes union field with fragments)
    RequestInterp->>RequestInterp: interpretField() for union property
    Note over RequestInterp: No special handleUnion() exists now
    RequestInterp->>RequestInterp: Use getSelectionSet() like any object field
    RequestInterp->>Validation: validateFragmentConditions(inline/spread)
    alt Valid fragment conditions
        Validation-->>RequestInterp: OK
        RequestInterp->>ExecNode: Create Execution.Node with fragment children
    else Invalid type condition (e.g., unknown type, wrong kind)
        Validation-->>RequestInterp: NEW: Throws ValidationException (previously ignored)
        RequestInterp-->>Client: Error response
    end

    Executor->>ExecNode: resolveNode() for union field
    ExecNode->>FieldFunc: Invoke union property resolver (T -> Any?)
    FieldFunc-->>ExecNode: value (Deferred or raw)
    ExecNode->>ExecNode: createObjectNode() or createNode()
    Note over ExecNode: Now enters common path for object fields
    ExecNode->>TypeCheck: Check if returnType is UNION/INTERFACE
    alt Value type does not match any possible type
        TypeCheck-->>ExecNode: NEW: ExecutionError("Unexpected value type...")
        ExecNode-->>Client: Error response
    end
    ExecNode->>FragmentHandler: For each child (fragment nodes)
    FragmentHandler->>FragmentHandler: handleFragment() with isExpectedType() check
    Note over FragmentHandler: Reuses existing fragment logic (no special union handling)
    FragmentHandler-->>ExecNode: Merged children for matching type
    ExecNode-->>Client: JSON result
Loading

Re-trigger cubic

@stuebingerb
stuebingerb merged commit 7452ecd into main Jul 28, 2026
13 of 14 checks passed
@stuebingerb
stuebingerb deleted the refactor/refactor-union-properties branch July 28, 2026 06:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking Introduces a breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Missing/incorrect fragment condition validation on union properties

1 participant