Skip to content

Allow clients to disable error propagation via request parameter (take 2) #1163

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
136 changes: 133 additions & 3 deletions spec/Section 4 -- Introspection.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,12 @@ operation.

## Schema Introspection

The schema introspection system is accessible from the meta-fields `__schema`
and `__type` which are accessible from the type of the root of a query
operation.
The schema introspection system is accessible from the meta-fields `__service`,
`__schema` and `__type` which are accessible from the type of the root of a
query operation.

```graphql
__service: __Service!
__schema: __Schema!
__type(name: String!): __Type
```
Expand Down Expand Up @@ -124,6 +125,11 @@ are the full set of type system definitions providing schema introspection,
which are fully defined in the sections below.

```graphql
type __Service {
capabilities: [String!]!
defaultErrorBehavior: __ErrorBehavior!
}

type __Schema {
description: String
types: [__Type!]!
Expand Down Expand Up @@ -164,6 +170,12 @@ enum __TypeKind {
NON_NULL
}

enum __ErrorBehavior {
NO_PROPAGATE
PROPAGATE
HALT
}

type __Field {
name: String!
description: String
Expand Down Expand Up @@ -220,6 +232,124 @@ enum __DirectiveLocation {
}
```

### The \_\_Service Type

The `__Service` type is returned from the `__service` meta-field and provides
information about the GraphQL service, most notably about its capabilities. This
type was added after the original release of GraphQL, so older schemas may not
support it.

Fields\:

- `capabilities` must return a list of strings indicating the capabilities
supported by the service.
- `defaultErrorBehavior` must return the _default error behavior_ of the service
using one of the values of the `__ErrorBehavior` enum:
- {"NO_PROPAGATE"}
- {"PROPAGATE"}
- {"HALT"}

**Capabilities**

As GraphQL becomes more feature rich, clients need to understand the features
that a service supports so that they only send compatible requests; the
`__Service.capabilities` field reveals these capabilities.

Note: Prior to the introduction of the capabilities system, a client would
either require out-of-band communication to discover the capabilities of a
schema, or would need to perform multiple introspection phases determining the
fields available in introspection in order to perform feature discovery.

Capabilities may be supplied by the GraphQL implementation, by the service, or
both.

A individual capability is described by a string, {CapabilityString}, inspired
by reverse domain name notation to ensure globally unique and
collision-resistant identifiers. A capability string must be composed of two or
more {CompatibilitySegment}, separated by a period (`.`). A {CapabilitySegment}
is a non-empty string composed only from ASCII letter (`[a-zA-Z]`), digits
(`[0-9]`), or hyphens (`-`); it must start with an ASCII letter and must not
terminate with a hyphen.

CapabilityString ::

- CapabilityString . CapabilitySegment
- CapabilitySegment . CapabilitySegment

CapabilitySegment ::

- CapabilitySegmentStart
- CapabilitySegmentStart CapabilitySegmentContinue\* CapabilitySegmentEnd

CapabilitySegmentStart :: Letter

CapabilitySegmentContinue ::

- Letter
- Digit
- `-`

CapabilitySegmentEnd ::

- Letter
- Digit

Identifiers beginning with the prefix {"org.graphql."} are reserved and must not
be used outside of official GraphQL Foundation specifications. Further,
identifiers beginning with the prefix {"org.graphql.http."} are reserved for use
by the GraphQL-over-HTTP specification, and identifiers beginning with the
prefix {"org.graphql.rfc."} are reserved for RFC proposals.

Identifiers defined by specific projects, vendors, or implementations should
begin with a prefix derived from a DNS name they control (e.g.,
{"com.example."})

Clients should use string equality to check for known identifiers, and should
ignore unknown identifiers.

Implementers should not change the meaning of capability identifiers, instead a
new capability identifier should be used when the meaning changes. Implementers
should ensure that capability strings remain stable and version-agnostic where
possible; capability versioning, if needed, can be indicated using dot suffixes
(e.g.{ "org.example.capability.v2"}).

This system enables incremental feature adoption and richer tooling
interoperability, while avoiding tight coupling to specific implementations.

Implementers of earlier versions of this specification may choose to implement
the capabilities system, but when doing so they must not include capabilities
that they do not support.

Implementers of this version of this specification must include the following
capabilities:

- {"org.graphql.scalar.specifiedBy"} - indicates the ability to request the
_scalar specification URL_ of a scalar via the `__Type.specifiedBy`
introspection field
- {"org.graphql.directive.repeatable"} - indicates support for repeatable
directive and the related `__Directive.isRepeatable` introspection field
- {"org.graphql.schema.description"} - indicates the ability to request a
description of the schema via the `__Schema.description` introspection field
- {"org.graphql.deprecation.inputValues"} - indicates support for deprecating
input values along with the related introspection schema coordinates:
- `__Directive.args(includeDeprecated:)`,
- `__Field.args(includeDeprecated:)`,
- `__Type.inputFields(includeDeprecated:)`,
- `__InputValue.isDeprecated`, and
- `__InputValue.deprecationReason`.
- {"org.graphql.inputObject.oneOf"} - indicates support for OneOf Input Objects
and the related introspection field `__Type.isOneOf`
- {"org.graphql.errorBehavior"} - indicates that the
`__Service.defaultErrorBehavior` field exists, which indicates the the
_default error behavior_ of the service

If the schema, implementation, and service support the subscription operation,
the {"org.graphql.subscription"} capability should be included.

If the service accepts the {onError} request parameter, the
{"org.graphql.onError"} capability should be included. If it is not included,
clients should infer the default will be used for all requests.

### The \_\_Schema Type

The `__Schema` type is returned from the `__schema` meta-field and provides all
Expand Down
106 changes: 85 additions & 21 deletions spec/Section 6 -- Execution.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ A GraphQL service generates a response from a request via execution.

:: A _request_ for execution consists of a few pieces of information:

<!-- https://github.com/prettier/prettier/issues/17286 -->
<!-- prettier-ignore -->
- {schema}: The schema to use, typically solely provided by the GraphQL service.
- {document}: A {Document} which must contain GraphQL {OperationDefinition} and
may contain {FragmentDefinition}.
Expand All @@ -15,13 +17,24 @@ A GraphQL service generates a response from a request via execution.
being executed. Conceptually, an initial value represents the "universe" of
data available via a GraphQL Service. It is common for a GraphQL Service to
always use the same initial value for every request.
- {onError} (optional): The _error behavior_ to apply to the request; see
Copy link
Contributor

@fotoetienne fotoetienne Apr 30, 2025

Choose a reason for hiding this comment

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

What about onError (or behavior) as a query directive rather than a new field on the request? Adding a field to the request payload might require changes to lots of GraphQL middleware. also a directive mirrors the behavior schema directive.

Copy link
Contributor

@martinbonnin martinbonnin Apr 30, 2025

Choose a reason for hiding this comment

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

This is where we initially started with @disableErrorPropagation. It's very handy that no middleware is impacted at all but @benjie argumented that it should be a request parameter.

The argument that won me is that the directive would most likely be automatically added by the Apollo/Relay/... compilers. Then it means you're modifying the user query and if the user types the same query in GraphiQL it won't behave the same as what they initially typed, which is surprising.

Copy link
Contributor

Choose a reason for hiding this comment

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

That's a good point that you might want to be able to add it without modifying the query document

Copy link
Member Author

Choose a reason for hiding this comment

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

Also keep in mind that it being a GraphQL request parameter does not necessarily mean it’s an HTTP request parameter; for example you could use a different endpoint in HTTP to set the param: /graphql_nobubblesplz ;)

[Handling Execution Errors](#sec-Handling-Execution-Errors). If {onError} is
provided and its value is not one of {"PROPAGATE"}, {"NO_PROPAGATE"}, or
{"HALT"}, then a _request error_ must be raised.
- {extensions} (optional): A map reserved for implementation-specific additional
information.

Given this information, the result of {ExecuteRequest(schema, document,
operationName, variableValues, initialValue)} produces the response, to be
formatted according to the Response section below.

Note: Previous versions of this specification did not define the {onError}
request attribute. Clients can detect support for {onError} by checking for the
{"org.graphql.onError"} capability. If this capability is not present, or if
capabilities themselves are not supported by introspection, then clients should
not include {onError} in the request and must assume the _error behavior_ is
{"PROPAGATE"}.

Implementations should not add additional properties to a _request_, which may
conflict with future editions of the GraphQL specification. Instead,
{extensions} provides a reserved location for implementation-specific additional
Expand Down Expand Up @@ -392,13 +405,24 @@ is explained in greater detail in the Field Collection section below.
</a>

If during {ExecuteSelectionSet()} a _response position_ with a non-null type
raises an _execution error_ then that error must propagate to the parent
response position (the entire selection set in the case of a field, or the
entire list in the case of a list position), either resolving to {null} if
allowed or being further propagated to a parent response position.

If this occurs, any sibling response positions which have not yet executed or
have not yet yielded a value may be cancelled to avoid unnecessary work.
raises an _execution error_, the error must be added to the {"errors"} list in
the _response_ and then handled according to the _error behavior_ of the
request:

<!-- https://github.com/prettier/prettier/issues/17286 -->
<!-- prettier-ignore -->
- {"NO\_PROPAGATE"}: The _response position_ must be set to {null}. (The client
is responsible for interpreting this {null} in conjunction with the {"errors"}
list to distinguish error results from intentional {null} values.)
- {"PROPAGATE"}: The _execution error_ must propagate to the parent _response
position_ (the entire selection set in the case of a field, or the entire list
in the case of a list position). The parent position resolves to {null} if
allowed, or else the error is further propagated to a parent response
position. Any sibling response positions that have not yet executed or have
not yet yielded a value may be cancelled to avoid unnecessary work.
- {"HALT"}: The entire _request_ must be cancelled. The {"data"} entry in the
_response_ must be {null}. Any _response position_ that has not yet executed
or has not yet yielded a value may be cancelled to avoid unnecessary work.

Note: See [Handling Execution Errors](#sec-Handling-Execution-Errors) for more
about this behavior.
Expand Down Expand Up @@ -823,28 +847,62 @@ MergeSelectionSets(fields):
</a>

An _execution error_ is an error raised during field execution, value resolution
or coercion, at a specific _response position_. While these errors must be
reported in the response, they are "handled" by producing partial {"data"} in
the _response_.
or coercion, at a specific _response position_. These errors must be added to
the {"errors"} list in the _response_, and are "handled" according to the _error
behavior_ of the request.

Note: An _execution error_ is distinct from a _request error_ which results in a
response with no {"data"}.

If a _response position_ resolves to {null} because of an execution error which
has already been added to the {"errors"} list in the response, the {"errors"}
list must not be further affected. That is, only one error should be added to
the errors list per _response position_.

:: The _error behavior_ of a request indicates how an _execution error_ is
handled. It may be specified using the optional {onError} attribute of the
_request_. If omitted, the _default error behavior_ of the service applies.
Valid values for _error behavior_ are {"PROPAGATE"}, {"NO_PROPAGATE"} and
{"HALT"}.

:: The _default error behavior_ of a service is implementation-defined. For
compatibility with existing clients, services should default to {"PROPAGATE"}
which reflects prior behavior. For new services, {"NO_PROPAGATE"} is
recommended. The default error behavior is indicated via the
`defaultErrorBehavior` field of the `__Service` introspection type.

Note: {"HALT"} is not recommended as the _default error behavior_ because it
prevents generating partial responses which may still contain useful data.

Regardless of error behavior, if a _response position_ with a non-null type
results in {null} due to the result of {ResolveFieldValue()} then an execution
error must be raised at that position as specified in {CompleteValue()}.

Note: This is distinct from a _request error_ which results in a response with
no data.
The _error behavior_ of a request applies to every _execution error_ raised
during execution. The following sections describe the behavior of each valid
value:

If an execution error is raised while resolving a field (either directly or
nested inside any lists), it is handled as though the _response position_ at
which the error occurred resolved to {null}, and the error must be added to the
{"errors"} list in the response.
**{"NO_PROPAGATE"}**

<!-- https://github.com/prettier/prettier/issues/17286 -->
<!-- prettier-ignore -->
With {"NO\_PROPAGATE"}, a `Non-Null` _response position_ will have the value
{null} if and only if an error occurred at that position.

Note: Clients must inspect the {"errors"} list and use the {"path"} of each
error result to distinguish between intentional {null} values and those
resulting from an _execution error_.

**{"PROPAGATE"}**

With {"PROPAGATE"}, a `Non-Null` _response position_ must not contain {null} in
the _response_.

If the result of resolving a _response position_ is {null} (either due to the
result of {ResolveFieldValue()} or because an execution error was raised), and
that position is of a `Non-Null` type, then an execution error is raised at that
position. The error must be added to the {"errors"} list in the response.

If a _response position_ resolves to {null} because of an execution error which
has already been added to the {"errors"} list in the response, the {"errors"}
list must not be further affected. That is, only one error should be added to
the errors list per _response position_.

Since `Non-Null` response positions cannot be {null}, execution errors are
propagated to be handled by the parent _response position_. If the parent
response position may be {null} then it resolves to {null}, otherwise if it is a
Expand All @@ -859,3 +917,9 @@ position_ must resolve to {null}. If the `List` type is also wrapped in a
If every _response position_ from the root of the request to the source of the
execution error has a `Non-Null` type, then the {"data"} entry in the response
should be {null}.

**{"HALT"}**

With {"HALT"}, execution must cease immediately when the first _execution error_
is raised. That error must be added to the {"errors"} list, and {"data"} must be
{null}.