diff --git a/spec/Appendix B -- Grammar Summary.md b/spec/Appendix B -- Grammar Summary.md index f937405c8..3c75f135a 100644 --- a/spec/Appendix B -- Grammar Summary.md +++ b/spec/Appendix B -- Grammar Summary.md @@ -129,12 +129,15 @@ Value[Const] : - FloatValue - StringValue - BooleanValue + - NullValue - EnumValue - ListValue[?Const] - ObjectValue[?Const] BooleanValue : one of `true` `false` +NullValue : `null` + EnumValue : Name but not `true`, `false` or `null` ListValue[Const] : diff --git a/spec/Section 2 -- Language.md b/spec/Section 2 -- Language.md index a728cde8f..151b47b48 100644 --- a/spec/Section 2 -- Language.md +++ b/spec/Section 2 -- Language.md @@ -581,6 +581,7 @@ Value[Const] : - FloatValue - StringValue - BooleanValue + - NullValue - EnumValue - ListValue[?Const] - ObjectValue[?Const] @@ -650,6 +651,31 @@ Strings are lists of characters wrapped in double-quotes `"`. (ex. `"Hello World"`). White space and other otherwise-ignored characters are significant within a string value. +#### Null Value + +NullValue: `null` + +Null values are represented as the keyword `null`. + +GraphQL has two similar but not-identical ways to represent the lack of a value, +by explicitly providing the `null` value, or by implicitly not providing a value +at all. + +For example, these two field calls are similar, but not identical: + +```graphql +{ + field(arg: null) + field +} +``` + +The first has explictly provided `null` to the argument "arg", while the second +has implicitly not provided a value to the argument "arg". A GraphQL service may +interpret the two forms differently, if it is advantageous. For example, to +represent deleting a field vs not altering a field during a +mutation, respectively. + #### Enum Value EnumValue : Name but not `true`, `false` or `null` @@ -659,9 +685,6 @@ recommended that Enum values be "all caps". Enum values are only used in contexts where the precise enumeration type is known. Therefore it's not necessary to supply an enumeration type name in the literal. -An enum value cannot be "null" in order to avoid confusion. GraphQL -does not supply a value literal to represent the concept {null}. - #### List Value ListValue[Const] : diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 0f95a4248..5ea772359 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -113,6 +113,9 @@ and floating-point values, they are interpreted as an integer input value if they have an empty fractional part (ex. `1.0`) and otherwise as floating-point input value. +For all types below, with the exception of Non-Null, if the explicit value +`null` is provided, then then the result of input coercion is `null`. + #### Built-in Scalars GraphQL provides a basic set of well-defined Scalar types. A GraphQL server @@ -649,14 +652,26 @@ An input object is never a valid result. **Input Coercion** The input to an input object should be an unordered map, otherwise an error -should be thrown. The result of the coercion is an unordered map, with an -entry for each input field, whose key is the name of the input field. -The value of an entry in the coerced map is the result of input coercing the -value of the entry in the input with the same key; if the input does not have a -corresponding entry, the value is the result of coercing null. The input -coercion above should be performed according to the input coercion rules of the +should be thrown. This unordered map should not contain any entries with names +not defined by a field of this input object type, otherwise an error should be +thrown. + +The result of coercion is an environment-specific unordered map or record struct +defining slots for each field of the input object type. + +For each field of the input object type, if the original value has an entry with +the same name, and the value at that entry is a literal value or a variable +which was provided a runtime value, an entry is added to the result with the +name of the field. + +The value of that entry in the result is the outcome of input coercing the +original entry value according to the input coercion rules of the type declared by the input field. +Note: there is a potential semantic difference between the input value +explicitly declaring an input field's value as the value `null` vs having not +declared the input field at all. GraphQL services may optionally interpret these +two cases differently if it is advantageous. ### Lists @@ -706,40 +721,41 @@ then an error should be raised. **Input Coercion** -Note that `null` is not a value in GraphQL, so a query cannot look like: +There are a number of ways in which a `null` value may be provided in GraphQL, +all of which must raise an error when provided to a Non-Null type. + +If a value is not provided, or the explicit `null` value is provided, an error +must be thrown. Otherwise, the result is the outcome of input coercing the value +according to the input coercion rules of the wrapped type. + +The explicit `null` value is not allowed given a non-null input type: ```!graphql { - field(arg: null) + fieldWithNonNullArg(nonNullArg: null) } ``` -to indicate that the argument is null. Instead, an argument would be null only -if it is omitted: +An omitted argument is not allowed given a non-null input type: -```graphql +```!graphql { - field + fieldWithNonNullArg } ``` -Or if passed a variable of a nullable type that at runtime was not provided -a value: +A variable of a nullable type that at runtime was not provided a value, or was +provided an explicit `null` value is not allowed given a non-null input type. ```graphql query withNullableVariable($var: String) { - field(arg: $var) + fieldWithNonNullArg(nonNullArg: $var) } ``` -Hence, if the value for a Non Null type is hard-coded in the query, it is always -coerced using the input coercion for the wrapped type. +Note: the Validation chapter also defines providing a nullable variable type to +a non-null input type as invalid. -When a Non Null input has its value set using a variable, the coerced value -should be `null` if the provided value is `null`-like in the provided -representation, or if the provided value is omitted. Otherwise, the coerced -value is the result of running the wrapped type's input coercion on the -provided value. ## Directives diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index 1b2093496..f57ff4660 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -518,11 +518,15 @@ fragment stringIntoInt on Arguments { * Let {argumentName} be the name of {definition} * Let {argument} be the argument in {arguments} named {argumentName} * {argument} must exist. + * Let {value} be the value of {argument} + * {value} must not be the `null` literal. ** Explanatory Text ** Arguments can be required. Arguments are required if the type of the argument -is non-null. If it is not non-null, the argument is optional. +is non-null. If it is not non-null, the argument is optional. When an argument +type is non-null, and is required, the explicit value `null` may also not +be provided. For example the following are valid: @@ -554,6 +558,14 @@ fragment missingRequiredArg on Arguments { } ``` +Providing the explicit value `null` is also not valid. + +```!graphql +fragment missingRequiredArg on Arguments { + notNullBooleanArgField(nonNullBooleanArg: null) +} +``` + ## Fragments ### Fragment Declarations diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 5f45cb5e7..00bcec857 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -137,20 +137,23 @@ GetFieldEntry(objectType, object, fields): {GetFieldTypeFromObjectType(objectType, firstField)}. * If {fieldType} is {null}, return {null}, indicating that no entry exists in the result map. - * Let {resolvedObject} be {ResolveFieldOnObject(objectType, object, fieldEntry)}. + * Let {resolvedObject} be {ResolveFieldOnObject(objectType, object, firstField)}. * If {resolvedObject} is {null}, return {tuple(responseKey, null)}, indicating that an entry exists in the result map whose value is `null`. * Let {subSelectionSet} be the result of calling {MergeSelectionSets(fields)}. * Let {responseValue} be the result of calling {CompleteValue(fieldType, resolvedObject, subSelectionSet)}. * Return {tuple(responseKey, responseValue)}. -GetFieldTypeFromObjectType(objectType, firstField): - * Call the method provided by the type system for determining the field type - on a given object type. +GetFieldTypeFromObjectType(objectType, field): + * Let {fieldName} be the name of {field}. + * Return the field type defined by {objectType} with the name {fieldName}. -ResolveFieldOnObject(objectType, object, firstField): - * Call the method provided by the type system for determining the resolution - of a field on a given object. +ResolveFieldOnObject(objectType, object, field): + * Let {fieldName} be the name of {field}. + * Let {arguments} be the result of {ResolveArguments(objectType, field)} + * Call the internal function provided by {objectType} for determining the + resolved value of a field named {fieldName} on a given {object} + provided {arguments}. MergeSelectionSets(fields): * Let {selectionSet} be an empty list. @@ -189,6 +192,36 @@ ResolveAbstractType(abstractType, objectValue): system for determining the Object type of {abstractType} given the value {objectValue}. +### Field Arguments + +Fields may include arguments which are provided to the underlying runtime in +order to correctly produce a value. These arguments are defined by the field in +the type system to have a specific input type: Scalars, Enum, Input Object, or +List or Non-Null wrapped of these three. + +At each argument position in a query may be a literal value or a variable to be +provided at runtime. + +ResolveArguments(objectType, field) + * Let {fieldName} be the name of {field}. + * Let {argTypes} be the arguments provided by {objectType} for the field + named {fieldName}. + * Let {argValues} be an empty Map. + * For each {argTypes} as {argName} and {argType}: + * Let {arg} be the argument in {field} named {argName}. + * If {arg} exists: + * Let {value} be the value defined by {arg}. + * If {value} is a Variable: + * If a value was provided for the variable {value}: + * Add an entry to {argValues} named {argName} with the runtime value + of the Variable {value}. + * Else: + * Let {coercedValue} be the result of coercing {value} according to the + input coercion rules of {argType}. + * Add an entry to {argValues} named {argName} with the + value {coercedValue}. + * Return {argValues}. + ### Normal evaluation When evaluating a grouped field set without a serial execution order requirement,