Skip to content

Handle partial data and error paths - #926

Open
hugo-vrijswijk wants to merge 1 commit into
mainfrom
fix/field-error-path-and-null-propagation
Open

Handle partial data and error paths#926
hugo-vrijswijk wants to merge 1 commit into
mainfrom
fix/field-error-path-and-null-propagation

Conversation

@hugo-vrijswijk

@hugo-vrijswijk hugo-vrijswijk commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

This is a complicated change, and open to discussion if it is actually wanted.

Failed fields now propagate their null to the nearest nullable position, rather than a Result.Failure which discards all data. Example:

Before:

query {
  ping
  viaEffect {
    name
  }
}
# result:
{
  "errors": [
    { "message": "boom" }
  ]
}

After:

query {
  ping
  viaEffect {
    name
  }
}
# result:
{
  "data": {
    "ping": "pong",
    "viaEffect": null
  },
  "errors": [
    { "message": "boom", "path": ["viaEffect"] }
  ]
}
# or, if all fields are non-nullable:
{
  "data": null,
  "errors": [
    { "message": "boom", "path": ["viaEffect"] }
  ]
}

More examples are in the tests, which show the difference clearly. This is a big breaking change for users that expect data to be complete if there is data at the root. Release notes should mention this very clearly. But it is in line with the GraphQL specification, which says that a failed field should not discard its siblings' data. This partially closes a few conformance suites, except for locations in errors, which are not (yet) implemented.


The interpreter now tracks the response position of each value:

  • A failure at a nullable position completes as null and keeps the data of its siblings.
  • A failure at a non-null position propagates its null to the nearest enclosing nullable position.
  • A null which reaches the root leaves data as null.

Each problem carries the response path of its own position, with the alias of the field and the index of the list entry.

Both error policies now complete the deferred positions of a failed batch as null and keep the rest of the response. An internal error stays a request error and aborts the completion.

BREAKING: Problem.path changes type from List[String] to List[Problem.PathSegment], so that a path can hold list indexes. A segment is a Name(String) or an Index(Int).

This is a complicated change, and open to discussion if it is actually wanted.

Failed fields now propagate their `null` to the nearest nullable position, rather than a `Result.Failure` which discards all data. Example:

Before:

```graphql
query {
  ping
  viaEffect {
    name
  }
}

{
  "errors": [
    { "message": "boom" }
  ]
}
```

After:

```graphql
query {
  ping
  viaEffect {
    name
  }
}

{
  "data": {
    "ping": "pong",
    "viaEffect": null
  },
  "errors": [
    { "message": "boom", "path": ["viaEffect"] }
  ]
}
```

This is a big breaking change for users that expect data to be complete if there is `data` at the root. But it is in line with the GraphQL specification, which says that a failed field should not discard its siblings' data. This partially closes a few conformance suites, except for `location`s in errors, which are not (yet) implemented.

---

The interpreter now tracks the response position of each value:

- A failure at a nullable position completes as null and keeps the data of its siblings.
- A failure at a non-null position propagates its null to the nearest enclosing nullable position.
- A null which reaches the root leaves `data` as null.

Each problem carries the response path of its own position, with the alias of the field and the index of the list entry.

Both error policies now complete the deferred positions of a failed batch as null and keep the rest of the response. An internal error stays a request error and aborts the completion.

BREAKING: `Problem.path` changes type from `List[String]` to `List[Problem.PathSegment]`, so that a path can hold list indexes. A segment is a `Name(String)` or an `Index(Int)`.
@hugo-vrijswijk
hugo-vrijswijk force-pushed the fix/field-error-path-and-null-propagation branch from 78fc852 to 9557888 Compare August 31, 2026 21:56
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.

1 participant