diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 945c916f6..7c84da9be 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -329,8 +329,8 @@ A GraphQL schema may describe that a field represents a list of another type; the `List` type is provided for this reason, and wraps another type. Similarly, the `Non-Null` type wraps another type, and denotes that the -resulting value will never be {null} (and that a _field error_ cannot result in -a {null} value). +resulting value will never be {null} (and that an _execution error_ cannot +result in a {null} value). These two types are referred to as "wrapping types"; non-wrapping types are referred to as "named types". A wrapping type has an underlying named type, @@ -461,14 +461,14 @@ more guidance. A GraphQL service, when preparing a field of a given scalar type, must uphold the contract the scalar type describes, either by coercing the value or -producing a _field error_ if a value cannot be coerced or if coercion may result -in data loss. +producing an _execution error_ if a value cannot be coerced or if coercion may +result in data loss. A GraphQL service may decide to allow coercing different internal types to the expected return type. For example when coercing a field of type {Int} a boolean {true} value may produce {1} or a string value {"123"} may be parsed as base-10 {123}. However if internal type coercion cannot be reasonably performed without -losing information, then it must raise a _field error_. +losing information, then it must raise an _execution error_. Since this coercion behavior is not observable to clients of the GraphQL service, the precise rules of coercion are left to the implementation. The only @@ -513,15 +513,15 @@ Fields returning the type {Int} expect to encounter 32-bit integer internal values. GraphQL services may coerce non-integer internal values to integers when -reasonable without losing information, otherwise they must raise a _field +reasonable without losing information, otherwise they must raise an _execution error_. Examples of this may include returning `1` for the floating-point number `1.0`, or returning `123` for the string `"123"`. In scenarios where coercion -may lose data, raising a field error is more appropriate. For example, a -floating-point number `1.2` should raise a field error instead of being +may lose data, raising an execution error is more appropriate. For example, a +floating-point number `1.2` should raise an execution error instead of being truncated to `1`. If the integer internal value represents a value less than -231 or -greater than or equal to 231, a _field error_ should be raised. +greater than or equal to 231, an _execution error_ should be raised. **Input Coercion** @@ -548,12 +548,12 @@ Fields returning the type {Float} expect to encounter double-precision floating-point internal values. GraphQL services may coerce non-floating-point internal values to {Float} when -reasonable without losing information, otherwise they must raise a _field +reasonable without losing information, otherwise they must raise an _execution error_. Examples of this may include returning `1.0` for the integer number `1`, or `123.0` for the string `"123"`. Non-finite floating-point internal values ({NaN} and {Infinity}) cannot be -coerced to {Float} and must raise a _field error_. +coerced to {Float} and must raise an _execution error_. **Input Coercion** @@ -579,9 +579,9 @@ that representation must be used to serialize this type. Fields returning the type {String} expect to encounter Unicode string values. GraphQL services may coerce non-string raw values to {String} when reasonable -without losing information, otherwise they must raise a _field error_. Examples -of this may include returning the string `"true"` for a boolean true value, or -the string `"1"` for the integer `1`. +without losing information, otherwise they must raise an _execution error_. +Examples of this may include returning the string `"true"` for a boolean true +value, or the string `"1"` for the integer `1`. **Input Coercion** @@ -600,8 +600,8 @@ representation of the integers `1` and `0`. Fields returning the type {Boolean} expect to encounter boolean internal values. GraphQL services may coerce non-boolean raw values to {Boolean} when reasonable -without losing information, otherwise they must raise a _field error_. Examples -of this may include returning `true` for non-zero numbers. +without losing information, otherwise they must raise an _execution error_. +Examples of this may include returning `true` for non-zero numbers. **Input Coercion** @@ -623,7 +623,7 @@ large 128-bit random numbers, to base64 encoded values, or string values of a format like [GUID](https://en.wikipedia.org/wiki/Globally_unique_identifier). GraphQL services should coerce as appropriate given the ID formats they expect. -When coercion is not possible they must raise a _field error_. +When coercion is not possible they must raise an _execution error_. **Input Coercion** @@ -1492,7 +1492,7 @@ enum Direction { **Result Coercion** GraphQL services must return one of the defined set of possible values. If a -reasonable coercion is not possible they must raise a _field error_. +reasonable coercion is not possible they must raise an _execution error_. **Input Coercion** @@ -1654,10 +1654,10 @@ is constructed with the following rules: - If a variable is provided for an input object field, the runtime value of that variable must be used. If the runtime value is {null} and the field type is - non-null, a _field error_ must be raised. If no runtime value is provided, the - variable definition's default value should be used. If the variable definition - does not provide a default value, the input object field definition's default - value should be used. + non-null, an _execution error_ must be raised. If no runtime value is + provided, the variable definition's default value should be used. If the + variable definition does not provide a default value, the input object field + definition's default value should be used. Following are examples of input coercion for an input object type with a `String` field `a` and a required (non-null) `Int!` field `b`: @@ -1742,19 +1742,19 @@ brackets like this: `pets: [Pet]`. Nesting lists is allowed: `matrix: [[Int]]`. GraphQL services must return an ordered list as the result of a list type. Each item in the list must be the result of a result coercion of the item type. If a -reasonable coercion is not possible it must raise a _field error_. In +reasonable coercion is not possible it must raise an _execution error_. In particular, if a non-list is returned, the coercion should fail, as this indicates a mismatch in expectations between the type system and the implementation. If a list's item type is nullable, then errors occurring during preparation or coercion of an individual item in the list must result in a the value {null} at -that position in the list along with a _field error_ added to the response. If a -list's item type is non-null, a field error occurring at an individual item in -the list must result in a field error for the entire list. +that position in the list along with an _execution error_ added to the response. +If a list's item type is non-null, an execution error occurring at an individual +item in the list must result in an execution error for the entire list. -Note: See [Handling Field Errors](#sec-Handling-Field-Errors) for more about -this behavior. +Note: See [Handling Execution Errors](#sec-Handling-Execution-Errors) for more +about this behavior. **Input Coercion** @@ -1812,12 +1812,13 @@ always optional and non-null types are always required. In all of the above result coercions, {null} was considered a valid value. To coerce the result of a Non-Null type, the coercion of the wrapped type should be performed. If that result was not {null}, then the result of coercing the -Non-Null type is that result. If that result was {null}, then a _field error_ -must be raised. +Non-Null type is that result. If that result was {null}, then an _execution +error_ must be raised. -Note: When a _field error_ is raised on a non-null value, the error propagates -to the parent field. For more information on this process, see -[Errors and Non-Null Fields](#sec-Executing-Selection-Sets.Errors-and-Non-Null-Fields) +Note: When an _execution error_ is raised on a non-null _response position_, the +error propagates to the parent _response position_. For more information on this +process, see +[Errors and Non-Null Types](#sec-Executing-Selection-Sets.Errors-and-Non-Null-Types) within the Execution section. **Input Coercion** diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index 44bc9dbba..6ca9506d2 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -2010,4 +2010,4 @@ query booleanArgQueryWithDefault($booleanArg: Boolean = true) { ``` Note: The value {null} could still be provided to such a variable at runtime. A -non-null argument must raise a _field error_ if provided a {null} value. +non-null argument must raise an _execution error_ if provided a {null} value. diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index d88c685f9..72907a511 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -15,11 +15,26 @@ 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, recommended): The _error behavior_ that is desired, see + [Handling Execution Errors](#sec-Handling-Execution-Errors). Given this information, the result of {ExecuteRequest(schema, document, operationName, variableValues, initialValue)} produces the response, to be formatted according to the Response section below. +Servers should use the value of {onError}, if present, as the _error behavior_ +of the request described in +[Handling Execution Errors](#sec-Handling-Execution-Errors). However, it should +be noted that previous versions of this specification did not make this option +available and thus a client must not rely on the server to honor the {onError} +value it has specified. If a _response_ includes {"errors"}, the client must +check the {"onError"} of the _response_ determine how errors are treated. If no +such property is present, the client must treat the request as if it had +specified {onError} as {"PROPAGATE"}. + +If {onError} is present and it's value is not one of {"PROPAGATE"}, +{"NO_PROPAGATE"}, or {"ABORT"} then a request error must be raised. + Note: GraphQL requests do not require any specific serialization format or transport mechanism. Message serialization and transport mechanisms should be chosen by the implementing service. @@ -137,7 +152,7 @@ ExecuteQuery(query, schema, variableValues, initialValue): - Let {data} be the result of running {ExecuteSelectionSet(selectionSet, queryType, initialValue, variableValues)} _normally_ (allowing parallelization). -- Let {errors} be the list of all _field error_ raised while executing the +- Let {errors} be the list of all _execution error_ raised while executing the selection set. - Return an unordered map containing {data} and {errors}. @@ -158,7 +173,7 @@ ExecuteMutation(mutation, schema, variableValues, initialValue): - Let {selectionSet} be the top level selection set in {mutation}. - Let {data} be the result of running {ExecuteSelectionSet(selectionSet, mutationType, initialValue, variableValues)} _serially_. -- Let {errors} be the list of all _field error_ raised while executing the +- Let {errors} be the list of all _execution error_ raised while executing the selection set. - Return an unordered map containing {data} and {errors}. @@ -317,10 +332,10 @@ MapSourceToResponseEvent(sourceStream, subscription, schema, variableValues): - Complete {responseStream} normally. - Return {responseStream}. -Note: Since {ExecuteSubscriptionEvent()} handles all _field error_, and _request -error_ only occur during {CreateSourceEventStream()}, the only remaining error -condition handled from {ExecuteSubscriptionEvent()} are internal exceptional -errors not described by this specification. +Note: Since {ExecuteSubscriptionEvent()} handles all _execution error_, and +_request error_ only occur during {CreateSourceEventStream()}, the only +remaining error condition handled from {ExecuteSubscriptionEvent()} are internal +exceptional errors not described by this specification. ExecuteSubscriptionEvent(subscription, schema, variableValues, initialValue): @@ -330,7 +345,7 @@ ExecuteSubscriptionEvent(subscription, schema, variableValues, initialValue): - Let {data} be the result of running {ExecuteSelectionSet(selectionSet, subscriptionType, initialValue, variableValues)} _normally_ (allowing parallelization). -- Let {errors} be the list of all _field error_ raised while executing the +- Let {errors} be the list of all _execution error_ raised while executing the selection set. - Return an unordered map containing {data} and {errors}. @@ -377,17 +392,23 @@ ExecuteSelectionSet(selectionSet, objectType, objectValue, variableValues): Note: {resultMap} is ordered by which fields appear first in the operation. This is explained in greater detail in the Field Collection section below. -**Errors and Non-Null Fields** + + + + +**Errors and Non-Null Types** -If during {ExecuteSelectionSet()} a field with a non-null {fieldType} raises a -_field error_ then that error must propagate to this entire selection set, -either resolving to {null} if allowed or further propagated to a parent field. +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 fields which have not yet executed or have not yet -yielded a value may be cancelled to avoid unnecessary work. +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. -Note: See [Handling Field Errors](#sec-Handling-Field-Errors) for more about -this behavior. +Note: See [Handling Execution Errors](#sec-Handling-Execution-Errors) for more +about this behavior. ### Normal and Serial Execution @@ -621,6 +642,9 @@ the type system to have a specific input type. At each argument position in an operation may be a literal {Value}, or a {Variable} to be provided at runtime. +Any _request error_ raised during {CoerceArgumentValues()} should be treated +instead as an _execution error_. + CoerceArgumentValues(objectType, field, variableValues): - Let {coercedValues} be an empty unordered Map. @@ -647,7 +671,7 @@ CoerceArgumentValues(objectType, field, variableValues): - Add an entry to {coercedValues} named {argumentName} with the value {defaultValue}. - Otherwise if {argumentType} is a Non-Nullable type, and either {hasValue} is - not {true} or {value} is {null}, raise a _field error_. + not {true} or {value} is {null}, raise an _execution error_. - Otherwise if {hasValue} is {true}: - If {value} is {null}: - Add an entry to {coercedValues} named {argumentName} with the value @@ -657,7 +681,7 @@ CoerceArgumentValues(objectType, field, variableValues): {value}. - Otherwise: - If {value} cannot be coerced according to the input coercion rules of - {argumentType}, raise a _field error_. + {argumentType}, raise an _execution error_. - Let {coercedValue} be the result of coercing {value} according to the input coercion rules of {argumentType}. - Add an entry to {coercedValues} named {argumentName} with the value @@ -704,12 +728,12 @@ CompleteValue(fieldType, fields, result, variableValues): - Let {innerType} be the inner type of {fieldType}. - Let {completedResult} be the result of calling {CompleteValue(innerType, fields, result, variableValues)}. - - If {completedResult} is {null}, raise a _field error_. + - If {completedResult} is {null}, raise an _execution error_. - Return {completedResult}. - If {result} is {null} (or another internal value similar to {null} such as {undefined}), return {null}. - If {fieldType} is a List type: - - If {result} is not a collection of values, raise a _field error_. + - If {result} is not a collection of values, raise an _execution error_. - Let {innerType} be the inner type of {fieldType}. - Return a list where each list item is the result of calling {CompleteValue(innerType, fields, resultItem, variableValues)}, where @@ -744,7 +768,7 @@ CoerceResult(leafType, value): - Return the result of calling the internal method provided by the type system for determining the "result coercion" of {leafType} given the value {value}. This internal method must return a valid value for the type and not {null}. - Otherwise raise a _field error_. + Otherwise raise an _execution error_. Note: If a field resolver returns {null} then it is handled within {CompleteValue()} before {CoerceResult()} is called. Therefore both the input @@ -799,39 +823,89 @@ MergeSelectionSets(fields): - Append all selections in {fieldSelectionSet} to {selectionSet}. - Return {selectionSet}. -### Handling Field Errors + + + + +### Handling Execution Errors -A _field error_ is an error raised from a particular field during value -resolution or coercion. While these errors should be reported in the response, -they are "handled" by producing a partial response. +An _execution error_ is an error raised from a particular field during value +resolution or coercion. These errors should be reported in the response and are +"handled" according to the selected _error behavior_. -Note: This is distinct from a _request error_ which results in a response with -no data. +Note: An _execution error_ is distinct from a _request error_ which results in a +response with no data. -If a field error is raised while resolving a field, it is handled as though the -field returned {null}, and the error must be added to the {"errors"} list in the -response. +If the result of resolving a field is {null}, and that field is of a `Non-Null` +type, then an execution error is raised by the field. -If the result of resolving a field is {null} (either because the function to -resolve the field returned {null} or because a field error was raised), and that -field is of a `Non-Null` type, then a field error is raised. The error must be -added to the {"errors"} list in the response. +If a `List` type wraps a `Non-Null` type, and one of the elements of that list +resolves to {null}, then an execution error is raised by the list item. -If the field returns {null} because of a field 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 -field. +:: The _error behavior_ indicates the way in which any errors are handled during +the request. It can be specified by the client using the {onError} property of +the _request_. Valid values are {"PROPAGATE"}, {"NO_PROPAGATE"} and {"ABORT"}; +their respective behaviors are detailed below. -Since `Non-Null` type fields cannot be {null}, field errors are propagated to be -handled by the parent field. If the parent field may be {null} then it resolves -to {null}, otherwise if it is a `Non-Null` type, the field error is further -propagated to its parent field. +Implementations are free to choose the default value to use if _error behavior_ +is not specified. It is recommended this is {"NO_PROPAGATE"} for newly created +schemas, but {"PROPAGATE"} should be used for existing schemas since that was +the implicit behavior in previous versions of this specification. -If a `List` type wraps a `Non-Null` type, and one of the elements of that list -resolves to {null}, then the entire list must resolve to {null}. If the `List` -type is also wrapped in a `Non-Null`, the field error continues to propagate -upwards. +Note: {"ABORT"} is not recommended as the default error behavior because it +makes clients less resilient to errors. GraphQL enables partial responses so +that the end user can still see some useful data even when something goes wrong +on the server. + +If an execution error is raised from a response position, it must be added to +the {"errors"} list in the _response_. + +If the response position returns {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_. + +Execution errors are handled according to the selected _error behavior_, as +detailed below: -If all fields from the root of the request to the source of the field error -return `Non-Null` types, then the {"data"} entry in the response should be +**PROPAGATE** + +This is the traditional error handling approach in which errors are "handled" by +producing a partial response whilst ensuring that {null} may not occur in a +`Non-Null` position. + +If an execution error is raised while resolving a nullable response position, it +is handled as though the response position returned {null} (and as stated above, +the error must be added to the {"errors"} list in the response). + +Since `Non-Null` response positions cannot be {null}, execution errors that +occur in `Non-Null` response positions are propagated to be handled by the +parent position. If the parent response position may be {null} then it resolves +to {null}, otherwise if it is a `Non-Null` type, the execution error is further +propagated to its parent response position. + +If all response positions from the root of the request to the source of the +execution error return `Non-Null` types, then the {"data"} entry in the response +is made {null}. + +**NO_PROPAGATE** + +This is the modern error handling approach in which errors are "handled" by +producing a partial response _without_ propagating errors to conform to +`Non-Null` positions. + +Note: With this error behavior, the client is expected to honour the {"errors"} +in the _response_ and prevent developers from reading a {null} produced by an +error. One approach for clients to to prevent a {null} produced by an error from +being read is to raise an error on the client when the data at that error's +_path entry_ is accessed. + +**ABORT** + +This error handling approach terminates the request when an execution error is +raised in any response position by setting the {"data"} entry in the response to {null}. + +It is not recommended to default to {"ABORT"}, however it can be useful for +certain classes of clients, such as ad-hoc scripts, that do not know how to +handle errors and thus wish to abort and fail the moment any error occurs. diff --git a/spec/Section 7 -- Response.md b/spec/Section 7 -- Response.md index daca5bb1f..1579b066c 100644 --- a/spec/Section 7 -- Response.md +++ b/spec/Section 7 -- Response.md @@ -5,8 +5,7 @@ response. The service's response describes the result of executing the requested operation if successful, and describes any errors raised during the request. A response may contain both a partial response as well as a list of errors in -the case that any _field error_ was raised on a field and was replaced with -{null}. +the case that any _execution error_ was raised and replaced with {null}. ## Response Format @@ -27,6 +26,13 @@ key `data`. The value of this entry is described in the "Data" section. If the request failed before execution, due to a syntax error, missing information, or validation error, this entry must not be present. +If the request included execution, raised any errors, and the _error behavior_ +was not {"PROPAGATE"} then the response map must contain an entry with key +`onError`. Otherwise if the request included execution, the server may include +the `onError` key. Otherwise, inclusion of the `onError` key is not recommended +(but is permitted). The value of this key, if present, must be that of the +_error behavior_. + The response map may also contain an entry with key `extensions`. This entry, if set, must have a map as its value. This entry is reserved for implementers to extend the protocol however they see fit, and hence there are no additional @@ -73,8 +79,8 @@ present. It must contain at least one _request error_ indicating why no data was able to be returned. If the `data` entry in the response is present (including if it is the value -{null}), the `errors` entry must be present if and only if one or more _field -error_ was raised during execution. +{null}), the `errors` entry must be present if and only if one or more +_execution error_ was raised during execution. **Request Errors** @@ -89,18 +95,33 @@ If a request error is raised, the `data` entry in the response must not be present, the `errors` entry must include the error, and request execution should be halted. -**Field Errors** + + + + +**Execution Errors** + +:: An _execution error_ is an error raised during the execution of a particular +field which results in partial response data. This may occur due to failure to +coerce the arguments for the field, an internal error during value resolution, +or failure to coerce the resulting value. An _execution error_ may occur in any +_response position_. + +Note: In previous versions of this specification _execution error_ was called +_field error_. -:: A _field error_ is an error raised during the execution of a particular field -which results in partial response data. This may occur due to an internal error -during value resolution or failure to coerce the resulting value. +:: A _response position_ is an identifiable position in the response: either a +_field_, or a (potentially nested) list position within a field if the field has +a `List` type. An _execution error_ may only occur within a _response position_. +The _response position_ is indicated in the _response_ via the error's _path +entry_. -A field error is typically the fault of a GraphQL service. +An execution error is typically the fault of a GraphQL service. -If a field error is raised, execution attempts to continue and a partial result -is produced (see [Handling Field Errors](#sec-Handling-Field-Errors)). The -`data` entry in the response must be present. The `errors` entry should include -this error. +If an execution error is raised, execution attempts to continue and a partial +result is produced (see +[Handling Execution Errors](#sec-Handling-Execution-Errors)). The `data` entry +in the response must be present. The `errors` entry must include this error. **Error Result Format** @@ -250,8 +271,8 @@ discouraged. ### Path -:: A _path entry_ is an entry within an _error result_ that allows for -association with a particular field reached during GraphQL execution. +:: A _path entry_ is an entry within an _error result_ that indicates the +_response position_ at which the error occurred. The value for a _path entry_ must be a list of path segments starting at the root of the response and ending with the field to be associated with. Path