diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 5bde7a6c1..31eae6ec3 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -7,6 +7,12 @@ A GraphQL service generates a response from a request via execution. - {schema}: The schema to use, typically solely provided by the GraphQL service. - {document}: A {Document} which must contain GraphQL {OperationDefinition} and may contain {FragmentDefinition}. +- {onError} (recommended): The _error behavior_ to apply to the request; see + [Handling Execution Errors](#sec-Handling-Execution-Errors). Clients should + provide {onError} as part of a GraphQL request. If {onError} is provided and + its value is not one of {"NULL"}, {"PROPAGATE"}, or {"HALT"}, then a _request + error_ must be raised. If {onError} is not provided, the value {"PROPAGATE"} + will be used. - {operationName} (optional): The name of the Operation in the Document to execute. - {variableValues} (optional): Values for any Variables defined by the @@ -22,6 +28,10 @@ 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 should only include {onError} in the request if it is +known that the service supports this property. + 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 @@ -600,13 +610,26 @@ section. If during {ExecuteCollectedFields()} 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 _execution result_ and then handled according to the _error behavior_ of the +request: + +- {"NULL"}: The _response position_ must be set to {null}, even if such position + is indicated by the schema to be non-nullable. (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 current {ExecuteRootSelectionSet()} must be aborted immediately + and must yield an execution result with an {"errors"} list consisting of this + _execution error_ only and the {"data"} entry set to {null}. Any _response + position_ that has not yet executed or has not yet yielded a value may be + cancelled to avoid unnecessary work. (Note: For a subscription operation the + underlying stream is not terminated.) Note: See [Handling Execution Errors](#sec-Handling-Execution-Errors) for more about this behavior. @@ -902,17 +925,45 @@ ResolveAbstractType(abstractType, objectValue): 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 _execution result_, 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; valid values are {"NULL"}, {"PROPAGATE"} and {"HALT"}. The _error +behavior_ for a _request_ should be specified using the {onError} attribute of +the request; if unspecified, the _error behavior_ is {"PROPAGATE"}. + +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()}. + +The _error behavior_ of a request applies to every _execution error_ raised +during execution. + +The following sections describe the behavior of each valid value: -Note: This is distinct from a _request error_ which results in a _request error -result_ with no data. +**{"NULL"}** -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 _execution result_. +With {"NULL"}, 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 @@ -920,11 +971,6 @@ 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 _execution result_. -If a _response position_ resolves to {null} because of an execution error which -has already been added to the {"errors"} list in the _execution result_, 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 @@ -939,3 +985,13 @@ 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 _execution result_ should be {null}. + +**{"HALT"}** + +With {"HALT"}, {ExecuteRootSelectionSet()} must cease immediately that the first +_execution error_ is raised. That error must be added to the {"errors"} list, +and {"data"} must be {null}. + +Note: For subscription operations, processing of the current event is ceased, +but the subscription still remains in place and future events will be processed +as normal. diff --git a/spec/Section 7 -- Response.md b/spec/Section 7 -- Response.md index 4ece8639d..7123b869d 100644 --- a/spec/Section 7 -- Response.md +++ b/spec/Section 7 -- Response.md @@ -113,6 +113,18 @@ found at `["hero", "friends"]`, the hero's first friend at `["hero", "friends", 0]` and that friend's name at `["hero", "friends", 0, "name"]`. +### Response Path Nullability + +Every non-empty prefix of a _response path_ is itself a response path and +identifies a _response position_. A _response position_ is non-null if the type +at that position is a Non-Null type. + +:: The _response path nullability_ of a _response path_ is a list of boolean +values having the same length as the response path. Each value corresponds to +the _response position_ identified by the _response path_ prefix ending at the +same index: the value is {true} if that response position is non-null, and +{false} otherwise. + ### Data The {"data"} entry in the _execution result_ will be the result of the execution @@ -166,7 +178,9 @@ An execution error is typically the fault of a GraphQL service. An _execution error_ must occur at a specific _response position_, and may occur in any response position. The response position of an execution error is -indicated via a _response path_ in the error response's {"path"} entry. +indicated via a _response path_ in the error response's {"path"} entry. The +_response path nullability_ of that response path is indicated via the error's +{"pathNonNull"} entry. When an execution error is raised at a given _response position_, then that response position must not be present within the _response_ {"data"} entry @@ -191,13 +205,18 @@ If an error can be associated to a particular field in the GraphQL result, it must contain an entry with the key {"path"} with a _response path_ which describes the _response position_ which raised the error. This allows clients to identify whether a {null} resolved result is a true value or the result of an -_execution error_. +_execution error_. It must also contain an entry with the key {"pathNonNull"} +with the _response path nullability_ for that path. This enables clients to +implement advanced error handling behavior: for example, a client could issue a +request using the {"NULL"} _error behavior_ and then reproduce any _error +behavior_ locally, something that would otherwise require access to both the +schema and the request document. For example, if fetching one of the friends' names fails in the following operation: ```graphql example -{ +query episodeHero($episode: Int!) { hero(episode: $episode) { name heroFriends: friends { @@ -208,6 +227,20 @@ operation: } ``` +Against the following schema: + +```graphql example +type Query { + hero(episode: Int!): Hero +} + +type Hero { + id: ID! + name: String + friends: [Hero]! +} +``` + The response might look like: ```json example @@ -216,7 +249,8 @@ The response might look like: { "message": "Name for character with ID 1002 could not be fetched.", "locations": [{ "line": 6, "column": 7 }], - "path": ["hero", "heroFriends", 1, "name"] + "path": ["hero", "heroFriends", 1, "name"], + "pathNonNull": [false, true, false, false] } ], "data": { @@ -248,7 +282,7 @@ raised, even if that field is not present in the response. For example, if the `name` field from above had declared a `Non-Null` return type in the schema, the result would look different but the error reported would -be the same: +be the same except the corresponding entry in {"pathNonNull"}: ```json example { @@ -256,7 +290,8 @@ be the same: { "message": "Name for character with ID 1002 could not be fetched.", "locations": [{ "line": 6, "column": 7 }], - "path": ["hero", "heroFriends", 1, "name"] + "path": ["hero", "heroFriends", 1, "name"], + "pathNonNull": [false, true, false, true] } ], "data": { @@ -290,6 +325,7 @@ see fit, and there are no additional restrictions on its contents. "message": "Name for character with ID 1002 could not be fetched.", "locations": [{ "line": 6, "column": 7 }], "path": ["hero", "heroFriends", 1, "name"], + "pathNonNull": [false, true, false, false], "extensions": { "code": "CAN_NOT_FETCH_BY_ID", "timestamp": "Fri Feb 9 14:33:09 UTC 2018" @@ -314,6 +350,7 @@ discouraged. "message": "Name for character with ID 1002 could not be fetched.", "locations": [{ "line": 6, "column": 7 }], "path": ["hero", "heroFriends", 1, "name"], + "pathNonNull": [false, true, false, false], "code": "CAN_NOT_FETCH_BY_ID", "timestamp": "Fri Feb 9 14:33:09 UTC 2018" }