Skip to content

Service capabilities / error behaviors #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 14 commits into
base: main
Choose a base branch
from
89 changes: 86 additions & 3 deletions spec/Section 4 -- Introspection.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,14 @@ 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 `__schema`,
`__type` and `__service` which are accessible from the type of the root of a
query operation.

```graphql
__schema: __Schema!
__type(name: String!): __Type
__service: __Service!
```

Like all meta-fields, these are implicit and do not appear in the fields list in
Expand Down Expand Up @@ -218,6 +219,15 @@ enum __DirectiveLocation {
INPUT_OBJECT
INPUT_FIELD_DEFINITION
}

type __Service {
capabilities: [__Capability!]!
}

type __Capability {
identifier: String!
value: String
}
```

### The \_\_Schema Type
Expand Down Expand Up @@ -500,3 +510,76 @@ Fields\:
{true}, deprecated arguments are also returned.
- `isRepeatable` must return a Boolean that indicates if the directive may be
used repeatedly at a single location.

### 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 `__Capability` indicating the
capabilities supported by the service.

### The \_\_Capability Type

:: A _capability_ describes a feature supported by the GraphQL service but not
directly expressible in the type system. This may include experimental GraphQL
features (such as new syntax or behavior), protocol support (such as GraphQL
over WebSockets), or additional operational metadata (such as release
identifiers). Capabilities may be supplied by the GraphQL implementation, the
service, or both.

The `__Service.capabilities` field exposes a _capability_ list. A _capability_
consists of a capability identifier and optionally a string value.

**Capability identifier**

A capability identifier is a string value composed of two or more segments
separated by a period (`.`). Each segment must begin with an ASCII letter, and
must contain only ASCII letters, digits and hyphens. These constraints are
inspired by reverse domain name notation to encourage global uniqueness and
collision-resistance. Unlike the domain name system, capability identifiers are
case sensitive. 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 compare identifiers using exact string equality 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 identifiers 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.

**Capability value**

The value assigned to a given capability may either be {null} to simply indicate
the capability is supported, or a string to provide additional information. For
example {"org.graphql.onError"} does not require additional information and thus
uses the value {null}, whereas {"org.graphql.defaultErrorBehavior"} needs the
{value} to indicate which _error behavior_ is the default.

**Specified capabilities**

This version of the specification defines the following capabilities:

- {"org.graphql.defaultErrorBehavior"} - indicates the _default error behavior_
of the service via the {value}. If not present, must be assumed to be
{"PROPAGATE"}.
- {"org.graphql.onError"} - indicates that the service allows the client to
specify {onError} in a request to indicate the _error behavior_ the service
should use for the request. {value} is {null}.
108 changes: 87 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,64 @@ 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"}.

<!-- https://github.com/prettier/prettier/issues/17286 -->
<!-- prettier-ignore -->
:: 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
{"org.graphql.defaultErrorBehavior"} _capability_.

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 +919,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}.