Normalized GraphQL Documents
State of this document
This specification shall be considered a working draft. The authors of this document reserve the right to make changes of any kind without notice. This includes changes that are not backward compattible.
In the future, there will exists ratified versions of this specification. Each version will be immutable and won’t receive any further changes.
Changes between versions may not always be backwards compatible. However, it is the intent of the authors of this specification to minimize the amount of changes that are not backward compatible.
Introduction
GraphQL as a language is intentionally designed to give maximum flexibility to API consumers. This means that there are infinite possible ways for the same execution result to be produced by different query documents, given the same variables as inputs.
A similar thing can be said about the type system definition language defined by the GraphQL specification. A single GraphQL schema can be defined using infinite different representation of that language.
This specification intends to formalize a method of normalizing any GraphQL document. This includes both executable documents as well as type system documents. Two documents that generate the same execution result or that describe the same GraphQL schema will generate the same output when following the normalization procedures defined by this specification.
Usage
The primary motivation for writing this specifications is persisted operations, where instead of sending the full document to a GraphQL server for execution, the server instead receives a hash that can be uniquely mapped to a certain operation. For efficiency reasons, two operations should produce the same hash if they are semantically equivalent. Without being able to normalize GraphQL documents, the server needs to store multiple hashes for equivalent operations.
For example, the following two documents would produce the same execution result, yet their stringified versions are different, hence they would result in different values when being hashes.
Example № 1{
user(id: 4) {
name
}
}
Example № 2{
user(id: 4) {
...UserData
}
}
fragment UserData on User {
name
}
There are likely more use cases for normalizing GraphQL documents which this preface might include in the future.
1Overview
Defining normalization for GraphQL documents only refers to the textual representation or the AST derived from the textual representation. This specification makes no assumptions of how GraphQL server implementations should handle documents internally.
All the following sections define a set of rules. A GraphQL document is considered normalized if any only if it adheres to all these rules.
1.1Printing Normalized GraphQL Documents
The stringified display of normalized GraphQL documents optimizes for string length as a primary goal and legibility as secondary goal. The string must use UTF-8 for encoding.
1.1.1Minimize ignored tokens
To meet the primary goal of small length, a normalized GraphQL document in stringified form must not contain excessive ignored tokens. The only non-excessive ignored token is a single space character (U+0020) that must be inserted between two tokens for which ShouldSeparateWithSpace returns true.
- If secondToken is of the kind Punctuator and is equal to the tripple-dot (...):
- If firstToken is a lexical token and not of the kind Punctuator:
- Return true.
- Return false.
- If firstToken is a lexical token and not of the kind Punctuator:
- If firstToken is a lexical token and not of the kind Punctuator:
- If secondToken is a lexical token and not of the kind Punctuator:
- Return true.
- If secondToken is a lexical token and not of the kind Punctuator:
- Return false.
Example
Consider the following document string that contains excessive ingored tokens.
Example № 3{
add(numbers: [1, -2]) {
__typename
... on Success {
result
}
... on Error {
message
code
}
}
}
The string correctly representing the normalized GraphQL document looks like this.
Example № 4{add(numbers:[1 -2]){__typename ...on Success{result}...on Error{message code}}}
There are two space tokens in this string which are not strictly necessary to preserve the ability of the string to be parsed as GraphQL document:
- The space between
1
and-2
in the integer array - The space between
__typename
and the tripple-dot punctuator...
In other words, the following string contains the actually minimal amount of ignored tokens and would parse to the same GraphQL document like the one above.
Counter Example № 5{add(numbers:[1-2]){__typename...on Success{result}...on Error{message code}}}
The second string suffers significantly in terms of legibility. For a human, the token 1-2
might not be understood as two separate integers. Similarly, having the tripple-dot punctuator join two Name tokens might be considered ambiguous or might be perceived as a single Name token.
1.1.2Printing strings
There are two ways of representing a StringValue in GraphQL: Wrapping the string with two single-quotation-marks, and wrapping the string with two tripple-quotation-marks. The latter are known as block strings. We will refer to the former as regular strings.
When displaying a normalized GraphQL document in stringified form, all StringValue tokens must be printed as regular strings.
The following control characters must be printed using their escape sequence:
- U+0008 must be printed as
\b
- U+0009 must be printed as
\t
- U+000A must be printed as
\n
- U+000C must be printed as
\f
- U+000D must be printed as
\r
All control characters other than the five mentioned above must be printed using their escaped unicode sequence. This includes:
- All characters from U+0000 up to and including U+0007
- The character U+000B
- All characters from U+000E up to and including U+001F
- All characters from U+007F up to and including U+009F
The quote character U+0022 must be printed with a leading backslash (\"
).
The backslash character U+005C must be printed with a leading backslash (\\
).
All other characters must be printed using their unicode character.
2Executable Documents
This section contains normalization rules specifically for ExecutableDocuments. Each rule provides a non-normalized GraphQL document as counter-example and shows the normalized version of that document.
This specification only applies to executable documents that would produce no validation errors in the context of a given GraphQL schema.
2.1Selections
2.1.1No Redundant Field Alias
A normalized GraphQL document must not contain a Field with an Alias that has the same name as the field.
Example
Counter Example № 6{
user(id: 4) {
name: name
}
}
Example № 7{
user(id: 4) {
name
}
}
2.1.2No Duplicate Selections
A normalized GraphQL document must not contain two equivalent Selections. Two Selections selectionA and selectionB are considered equivalent if SelectionsAreEquivalent(selectionA, selectionB) is equal to true.
- If selectionA is a Field:
- If selectionB is not a Field:
- Return false.
- Otherwise, return FieldsAreEquivalent(selectionA, selectionB).
- If selectionB is not a Field:
- If selectionA is an InlineFragment:
- If selectionB is not an InlineFragment:
- Return false.
- Otherwise, return InlineFragmentsAreEquivalent(selectionA, selectionB).
- If selectionB is not an InlineFragment:
- If selectionA is a FragmentSpread:
- If selectionB is not a FragmentSpread:
- Return false.
- Otherwise, return FragmentSpreadsAreEqual(selectionA, selectionB).
- If selectionB is not a FragmentSpread:
- Let responseKeyA be the response key of fieldA (Alias if exists, otherwise Field name), and let responseKeyB be the response key of fieldB.
- If responseKeyA is not equal to responseKeyB:
- Return false.
- Let argumentsA be the unordered set of Arguments passed to fieldA, and let argumentsB be the unordered set of Arguments passed to fieldB.
- If ArgumentsAreEquivalent(argumentsA, argumentsB) is equal to false:
- Return false.
- Let directivesA be the ordered list of Directives passed to fieldA, and let directivesB be the ordered list of Directives passed to fieldB.
- If DirectivesAreEquivalent(directivesA, directivesB) is equal to false:
- Return false.
- Return true.
- Let typeConditionA be the TypeCondition of inlineFragmentA, and let typeConditionB be the TypeCondition of inlineFragmentB.
- If typeConditionA does not exist:
- If typeConditionB exists:
- Return false.
- If typeConditionB exists:
- If typeConditionA exists:
- If typeConditionB does not exist:
- Return false.
- Let typeA be the name of the type from typeConditionA, and let typeB be the name of the type from typeConditionB.
- If typeA is not equal to typeB:
- Return false.
- If typeConditionB does not exist:
- Let directivesA be the ordered list of Directives passed to inlineFragmentA, and let directivesB be the ordered list of Directives passed to inlineFragmentB.
- If DirectivesAreEquivalent(directivesA, directivesB) is equal to false:
- Return false.
- Return true.
- Let fragmentNameA be the fragment name of fragmentSpreadA, and let fragmentNameB be the fragment name of fragmentSpreadB.
- If fragmentNameA is not equal to fragmentNameB:
- Return false.
- Let directivesA be the ordered list of Directives passed to fragmentSpreadA, and let directivesB be the ordered list of Directives passed to fragmentSpreadB.
- If DirectivesAreEquivalent(directivesA, directivesB) is equal to false:
- Return false.
- Return true.
- Let lengthA be the length of the ordered list directivesA, and let lengthB be the length of the ordered list directivesB.
- If lengthA does not equal lengthB:
- Return false.
- For each directiveA in directivesA:
- Let directiveB be the list item in directivesB with the same index as directiveA in directivesA.
- Let directiveNameA be the name of directiveA, and let directiveNameB be the name of directiveB.
- If directiveNameA is not equal to directiveNameB:
- Return false.
- Let argumentsA be the unordered set of Arguments passed to directiveA, and let argumentsB be the unordered set of Arguments passed to directiveB.
- If ArgumentsAreEquivalent(argumentsA, argumentsB) is equal to false:
- Return false.
- Return true.
- Let sizeA be the size of the unordered set argumentsA, and let sizeB be the size of the unordered set argumentsB.
- If sizeA does not equal sizeB:
- Return false.
- For each argumentA in argumentsA:
- Let argumentB be the set item from argumentsB where argumentB has the same name as argumentA.
- If argumentB does not exist:
- Return false.
- Let valueA be the Value of argumentA, and let valueB be the Value of argumentB.
- If ValuesAreEquivalent(valueA, valueB) is equal to false:
- Return false.
- Return true.
- If both valueA and valueB are a Variable :
- Return true if the name of valueA is equal to the name of valueB, otherwise return false.
- If both valueA and valueB are an IntValue:
- Return true if the integer represented by valueA is equal to the integer represented by valueB, otherwise return false.
- If both valueA and valueB are a FloatValue:
- Return true if the float value represented by valueA is equal to the float value represented by valueB, otherwise return false.
- If both valueA and valueB are a StringValue:
- Return true if the string value represented by valueA is equal to the string value represented by valueB, otherwise return false.
- If both valueA and valueB are a BooleanValue:
- Return true if the boolean value represented by valueA is equal to the boolean value represented by valueB, otherwise return false.
- If both valueA and valueB are a NullValue:
- Return true.
- If both valueA and valueB are a EnumValue:
- Return true if the name of valueA is equal to the name of valueB, otherwise return false.
- If both valueA and valueB are a ListValue:
- Let lengthA be the length of valueA, and let lengthB be the length of valueB.
- If lengthA is not equal to lengthB:
- Return false.
- For each listItemA in valueA:
- Let listItemB be the list item in valueB with the same index as listItemA in valueA.
- If ValuesAreEquivalent(listItemA, listItemB) is equal to false:
- Return false.
- Return true.
- If both valueA and valueB are an ObjectValue:
- Let sizeA be the size of valueA, and let sizeB be the size of valueB.
- If sizeA is not equal to sizeB:
- Return false.
- For each objectFieldA in valueA:
- Let objectFieldB be the ObjectField from valueB where objectFieldB has the same name as objectFieldA.
- If objectFieldB does not exist:
- Return false.
- Let objectValueA be the Value of objectFieldA, and let objectValueB be the Value of objectFieldB.
- If ValuesAreEquivalent(objectValueA, objectValueB) is equal to false:
- Return false.
- Return true.
- Return false.
Example
Counter Example № 8{
user(id: 4) {
name
friends {
name
}
name
friends {
birthday
name @uppercase
}
nameWithAlias: name
}
}
Example № 9{
user(id: 4) {
name
friends {
name
birthday
name @uppercase
}
nameWithAlias: name
}
}
2.1.3No Fragment Definitions
A normalized GraphQL document must not contain any FragmentDefinition.
Example
Counter Example № 10{
user(id: 4) {
...UserData
}
}
fragment UserData on User {
name
}
Example № 11{
user(id: 4) {
name
}
}
2.1.4No Inline Fragments With Redundant Type Condition
A normalized GraphQL document must not contain any InlineFragment that does not have any Directives and that has a TypeCondition defined on the same type as the surrounding selection set.
Example
This example assumes that that the return type of the user
field is User
.
Counter Example № 12{
user(id: 4) {
... on User {
name
}
}
}
Example № 13{
user(id: 4) {
name
}
}
2.1.5No Inline Fragments Without Context
A normalized GraphQL document must not contain any InlineFragment that does not have a TypeCondition and that does not have any Directives.
Example
Counter Example № 14{
user(id: 4) {
... {
name
}
}
}
Example № 15{
user(id: 4) {
name
}
}
2.1.6No Leading Redundant Interface Selections
A normalized GraphQL document must not contain any leading redundant Selection inside an InlineFragment defined inside a selection set of a parent field that returns an interface type, where an equal selection is already defined in said selection set somewhere before the InlineFragment.
Formally, a selection is considered leading redundant if SelectionIsLeadingRedundant returns true.
- Let selectionSet be the selection set in which selection is defined.
- Let parentNode be the node that contains selectionSet.
- If parentNode is not an InlineFragments:
- Return false.
- Let parentSelectionSet be the selection set that contains parentNode.
- Let parentType be the result of TypeForSelectionSet(parentSelectionSet).
- If parentType is not an interface type:
- Return false.
- Let previousSelections be the list of selection in parentSelectionSet before parentNode.
- If previousSelections is empty:
- Return false.
- For each previousSelection in previousSelections:
- If SelectionsAreEqual(selection, previousSelection) is equal to true:
- Return true.
- If SelectionsAreEqual(selection, previousSelection) is equal to true:
- Return false.
- Let parentNode be the node that contains selectionSet.
- If parentNode is an OperationDefinition:
- Let operationType be the operation type of parentNode.
- Let rootOperationType be the named type defined in the RootOperationTypeDefinition for operationType.
- Return rootOperationType.
- If parentNode is a Field:
- Let fieldDefinition be the FieldDefinition that defines the field parentNode.
- Let outputType be the type of fieldDefinition.
- Let unwrappedType be the unwrapped type of outputType.
- Return unwrappedType.
- If parentNode is an InlineFragment:
- Let typeCondition be the TypeCondition of parentNode.
- If typeCondition exists:
- Let type be the type of typeCondition.
- Return type.
- If typeCondition does not exist:
- Let parentSelectionSet be the selection set that contains parentNode.
- Return TypeForSelectionSet(parentSelectionSet).
- If parentNode is a FragmentSpread:
- Let typeCondition be the TypeCondition of parentNode.
- Let type be the type of typeCondition.
- Return type.
- If both selectionA and selectionB are Fields:
- Return FieldsAreEqual(selectionA, selectionB)
- If both selectionA and selectionB are InlineFragments:
- Return InlineFragmentsAreEqual(selectionA, selectionB)
- If both selectionA and selectionB are FragmentSpreads:
- Return FragmentSpreadsAreEqual(selectionA, selectionB)
- Return false.
- If FieldsAreEquivalent(fieldA, fieldB) is equal to false:
- Return false.
- Let selectionSetA be the selection set of fieldA, and let selectionSetB be the selection set of fieldB.
- If selectionSetA does not exist:
- Return true if selectionSetB does not exist, otherwise return false.
- If selectionSetB does not exist:
- Return false.
- Return SelectionSetsAreEqual(selectionSetA, selectionSetB).
- If InlineFragmentsAreEquivalent(inlineFragmentA, inlineFragmentB) is equal to false:
- Return false.
- Let selectionSetA be the selection set of inlineFragmentA, and let selectionSetB be the selection set of inlineFragmentB.
- Return SelectionSetsAreEqual(selectionSetA, selectionSetB).
- If the count of selections in selectionSetA is not equal to the count of selections in selectionSetB:
- Return false.
- For each Selection selectionA in selectionSetA:
- Let selectionB be the Selection from selectionSetB at the same index as selectionA in selectionSetA.
- If SelectionsAreEqual(selectionA, selectionB) is equal to false:
- Return false.
- Return true.
Example
Counter Example № 16{
profile(id: 4) {
handle
... on User {
handle
name
}
}
}
Example № 17{
profile(id: 4) {
handle
... on User {
name
}
}
}
2.1.7No Lagging Redundant Interface Selections
A normalized GraphQL document must not contain any lagging redundant Selection that is the first selection inside an InlineFragment defined inside a selection set of a parent field that returns an interface type, where an equal selection is already defined in said selection set immediately after the InlineFragment.
A lagging redundant selection should be replaced by only specifying the selection once immediately before said InlineFragment.
Formally, a selection is considered lagging redundant if SelectionIsLaggingRedundant returns true.
- Let selectionSet be the selection set in which selection is defined.
- If selection is not the first Selection in selectionSet:
- Return false.
- Let parentNode be the node that contains selectionSet.
- If parentNode is not an InlineFragments:
- Return false.
- Let parentSelectionSet be the selection set that contains parentNode.
- Let parentType be the result of TypeForSelectionSet(parentSelectionSet).
- If parentType is not an interface type:
- Return false.
- Let nextSelection be the selection in parentSelectionSet after parentNode.
- If nextSelection does not exist:
- Return false.
- Return SelectionsAreEqual(selection, nextSelection).
Example
Counter Example № 18{
profile(id: 4) {
... on User {
handle
friends {
name
}
}
handle
}
}
Example № 19{
profile(id: 4) {
handle
... on User {
friends {
name
}
}
}
}
2.1.8No Lagging Redundant Interface Selection List
A normalized GraphQL document must not contain any lagging redundant Selection list that is located at the end of a selection set inside an InlineFragment defined inside a selection set of a parent field that returns an interface type, where an equal list of selections is already defined in said selection set immediately after the InlineFragment.
Formally, a list of consecutive selections is considered lagging redundant if SelectionListIsLaggingRedundant returns true.
- Let selectionSet be the selection set in which the Selections from selections are defined.
- If there exists one ore more Selections in selectionSet after selections:
- Return false.
- Let parentNode be the node that contains selectionSet.
- If parentNode is not an InlineFragments:
- Return false.
- Let parentSelectionSet be the selection set that contains parentNode.
- Let parentType be the result of TypeForSelectionSet(parentSelectionSet).
- If parentType is not an interface type:
- Return false.
- Let nextSelections be the list of selections in parentSelectionSet after parentNode with the same length as selections.
- If the length of nextSelections is smaller than the length of selections:
- Return false.
- For each selection in selections:
- Let nextSelection be the selection from nextSelections with the same index as selection in selections.
- If SelectionsAreEqual(selection, nextSelection) is equal to false:
- Return false.
- Return true.
Example
Counter Example № 20{
profile(id: 4) {
... on User {
friends {
name
}
__typename
handle
}
__typename
handle
}
}
Example № 21{
profile(id: 4) {
... on User {
friends {
name
}
}
__typename
handle
}
}
2.1.9No Repeated Interface Selections in Exhaustive Fragment List
A normalized GraphQL document must not contain any lists of adjacent and exhaustive InlineFragments inside of a selection set of a field that returns an interface type where all of the following conditions hold:
- Either all the first Selections or all the last Selections inside each of the InlineFragments are equal (i.e. SelectionsAreEqual returns true).
- The Selection identified by the previous condition is defined on the given interface type
- If the Selection is a Field, that means the field is defined in the interface type.
- If the Selection is an InlineFragment, that means the InlineFragment either has no type condition or the type from its type condition is equal to the given interface type. interface type.
- All InlineFragments do not have any custom directive applied to them.
Instead, such a Selection identified by the above conditions should only be specified once, namely before (in case it’s the first field in all InlineFragments) or after (in case it’s the last field in all InlineFragments) the list of adjacent and exhaustive InlineFragments.
Given a selection set for a field that returns an interface type interface and a list of adjacent InlineFragments fragments inside this selection set, the list fragments is considered exhaustive if FragmentsAreExhaustive(fragments, interface) returns true.
- Let fragmentTypes be an empty set.
- For each fragment in fragments:
- Let typeCondition be the type condition of fragment.
- If typeCondition does not exist:
- Continue to the next fragment.
- Let type be the name of the type from typeCondition.
- If type is an object type:
- Add type to fragmentTypes.
- If type is an interface type:
- Let implementors be the result of Implementors(type).
- Add all set items of implementors to fragmentTypes.
- Let interfaceImplementors be the result of Implementors(interfaceType).
- For each implementor in interfaceImplementors:
- If fragmentTypes does not contain implementor:
- Return false.
- If fragmentTypes does not contain implementor:
- Return true.
- Let objectImplementors be the set of object types that implement the interface type interfaceType.
- Let interfaceImplementors be the set of interface types that implement the interface type interfaceType.
- For each interface in interfaceImplementors:
- Let objects be the result of calling Implementors(interface).
- Add all entries of the set objects to the set objectImplementors.
- Return objectImplementors.
Example
For this example, assume that the field profile
returns an interface type Profile
, and that there exist exactly two object types User
and Organization
that implement this interface type. Furthermore, assume that the Profile
interface defines a field named handle
.
Counter Example № 22{
profile(id: 4) {
... on Organization {
handle
members {
name
}
}
... on User {
handle
name
}
}
}
Example № 23{
profile(id: 4) {
handle
... on Organization {
members {
name
}
}
... on User {
name
}
}
}
Influencer
that also implements the interface Profile
. Then the counter example above would actually be normalized because the list of inline fragments would not be exhaustive, and there does not exist a selection for the field handle
for the type Influencer
.2.1.10No Constant @skip Directive
A normalized GraphQL document must not contain any @skip directive where the Value passed to the if argument is a BooleanValue.
Example
Counter Example № 24{
user(id: 4) {
name
... @skip(if: true) {
birthday
}
... @skip(if: false) {
friends {
name
}
}
}
}
Example № 25{
user(id: 4) {
name
friends {
name
}
}
}
2.1.11No Constant @include Directive
A normalized GraphQL document must not contain any @include directive where the Value passed to the if argument is a BooleanValue.
Example
Counter Example № 26{
user(id: 4) {
name
... @include(if: true) {
birthday
}
... @include(if: false) {
friends {
name
}
}
}
}
Example № 27{
user(id: 4) {
name
birthday
}
}
2.2Ordering
In the GraphQL query language there exist unordered sets of items where the order in textual representation does not affect the semantics of the set. All such sets must be ordered alphabetically by some identifier according to the rules in this section.
2.2.1Ordered Definitions
In order to be normalized, all OperationDefinitions in a GraphQL document must be ordered alphabetically by their name. An OperationDefinition without a name must be the first definition in the GraphQL document. All Definitions that are not an OperationDefinition must be ordered after all OperationDefinitions.
Example
Counter Example № 28query User {
user(id: 4) {
name
}
}
query Profile {
profile(userId: 4) {
handle
}
}
{
user(id: 5) {
birthday
}
}
Example № 29{
user(id: 5) {
birthday
}
}
query Profile {
profile(userId: 4) {
handle
}
}
query User {
user(id: 4) {
name
}
}
2.2.2Ordered Variable Definitions
In order to be normalized, all VariableDefinitions in a GraphQL document must be ordered alphabetically by Variable name.
Example
Counter Example № 30query ($id: Int, $friendName: String) {
user(id: $id) {
friend(name: $friendName) {
birthday
}
}
}
Example № 31query ($friendName: String, $id: Int) {
user(id: $id) {
friend(name: $friendName) {
birthday
}
}
}
2.2.3Ordered Arguments
In order to be normalized, all Arguments in a GraphQL document must be ordered alphabetically by Argument name.
Example
Counter Example № 32{
user(name: "Bill", birthday: "1955-10-28") {
name
}
}
Example № 33{
user(birthday: "1955-10-28", name: "Bill") {
name
}
}
2.2.4Ordered Input Object Values
In order to be normalized, all ObjectValue in a GraphQL document must be ordered alphabetically by ObjectField name.
Example
Counter Example № 34{
user(input: { name: "Bill", birthday: "1955-10-28" }) {
name
}
}
Example № 35{
user(input: { birthday: "1955-10-28", name: "Bill" }) {
name
}
}
2.2.5Ordered Non-Overlapping Inline Fragments
In order to be normalized, any two adjacent InlineFragments in a GraphQL document that are non-overlapping and that don’t have any custom directive applied must be ordered alphabetically by the type name of their TypeCondition. Two InlineFragments are considered non-overlapping if InlineFragmentsOverlap returns false.
- Let typeConditionA be the type condition of inlineFragmentA, and let typeConditionB be the type condition of inlineFragmentB.
- If typeConditionA does not exist:
- Return true.
- If typeConditionB does not exist:
- Return true.
- Let typeNameA be the name of the type from typeConditionA, and let typeNameB be the name of the type from typeConditionB.
- Let typeA be the schema type with the name typeNameA, and let typeB be the schema type with the name typeNameB.
- Return TypesOverlap(typeA, typeB).
- If both typeA and typeB are object types:
- Return true if typeA is equal to typeB, otherwise return false.
- If both typeA and typeB are interface types:
- Let implementorsA be the result of Implementors(typeA), and let implementorsB be the result of Implementors(typeB).
- Return true if the intersection of the sets implementorsA and implementorsB is not an empty set, otherwise return false.
- If both typeA and typeB are union types:
- Let memberTypesA be the set of union member types of typeA, and let memberTypesB be the set of union member types of typeB.
- Return true if the intersection of the sets memberTypesA and memberTypesB is not an empty set, otherwise return false.
- If one of typeA and typeB is an object type, and one of typeA and typeB is an interface type:
- Let objectType be the one type of typeA and typeB that is an object type.
- Let interfaceType be the one type of typeA and typeB that is an interface type.
- Let implementors be the result of calling Implementors(interfaceType).
- Return true if implementors contains objectType, otherwise return false.
- If one of typeA and typeB is an object type, and one of typeA and typeB is a union type:
- Let objectType be the one type of typeA and typeB that is an object type.
- Let unionType be the one type of typeA and typeB that is an union type.
- Let memberTypes be the set of union member types of unionType.
- Return true if memberTypes contains objectType, otherwise return false.
- If one of typeA and typeB is an interface type, and one of typeA and typeB is a union type:
- Let interfaceType be the one type of typeA and typeB that is an interface type.
- Let unionType be the one type of typeA and typeB that is an union type.
- Let implementors be the result of Implementors(interfaceType).
- Let memberTypes be the set of union member types of unionType.
- Return true if the intersection of the sets implementors and memberTypes is not an empty set, otherwise return false.
- Return false.
Example for Interface Types
For this example, assume that the field profile
returns an interface type Profile
, and that there exist two object types User
and Organization
that implement this interface type.
Counter Example № 36{
profile(id: 4) {
handle
... on User {
name
}
... on Organization {
members {
name
}
}
}
}
Example № 37{
profile(id: 4) {
handle
... on Organization {
members {
name
}
}
... on User {
name
}
}
}
Example for Union Types
For this example, assume that the field userResult
returns a union type with two member types User
and Error
.
Counter Example № 38{
userResult(id: 4) {
... on User {
name
}
... on Error {
message
}
}
}
Example № 39{
userResult(id: 4) {
... on Error {
message
}
... on User {
name
}
}
}
Example for Nested Interface Types
For this example, assume the following GraphQL schema.
type Query {
node(id: ID): Node
}
interface Node {
id: ID
}
interface InterfaceA implements Node {
id: ID
fieldA: String
}
interface InterfaceB implements Node {
id: ID
fieldB: String
}
type ObjectA implements Node & InterfaceA {
id: ID
fieldA: String
}
type ObjectB implements Node & InterfaceB {
id: ID
fieldB: String
}
type ObjectAB implements Node & InterfaceA & InterfaceB {
id: ID
fieldA: String
fieldB: String
}
Then the following query is normalized.
Example № 40{
node(id: 4) {
... on InterfaceB {
fieldB
}
... on InterfaceA {
fieldA
}
}
}
The two inline fragments cannot be reordered without potentially changing the order of fields in the execution result. If the returned node if of type ObjectAB
then both type conditions of the two inline fragments will match, and the fields are returned in the order in which the inline fragments are included in the selection set.
ObjectAB
would not exist, then the example query above would not be normalized, because InterfaceA
and InterfaceB
would not overlap anymore. In other words, for all possible object types returned by the node
field only one of the inline fragments could be applied, but never both.3Type System Documents
§Index
- ArgumentsAreEquivalent
- DirectivesAreEquivalent
- FieldsAreEqual
- FieldsAreEquivalent
- FragmentsAreExhaustive
- FragmentSpreadsAreEqual
- Implementors
- InlineFragmentsAreEqual
- InlineFragmentsAreEquivalent
- InlineFragmentsOverlap
- SelectionIsLaggingRedundant
- SelectionIsLeadingRedundant
- SelectionListIsLaggingRedundant
- SelectionsAreEqual
- SelectionsAreEquivalent
- SelectionSetsAreEqual
- ShouldSeparateWithSpace
- TypeForSelectionSet
- TypesOverlap
- ValuesAreEquivalent