From 3d4f410402fddbecefe3212b02fc6fb625a3bc2a Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Sun, 1 May 2022 00:01:13 +0300 Subject: [PATCH] introduce new intersection type see https://github.com/graphql/graphql-js/pull/3550 --- spec/Appendix B -- Grammar Summary.md | 14 ++ spec/Section 2 -- Language.md | 4 +- spec/Section 3 -- Type System.md | 210 ++++++++++++++++++++++++-- spec/Section 4 -- Introspection.md | 36 ++++- spec/Section 5 -- Validation.md | 79 +++++++--- spec/Section 6 -- Execution.md | 14 +- 6 files changed, 310 insertions(+), 47 deletions(-) diff --git a/spec/Appendix B -- Grammar Summary.md b/spec/Appendix B -- Grammar Summary.md index 7afe9e11b..329380cb0 100644 --- a/spec/Appendix B -- Grammar Summary.md +++ b/spec/Appendix B -- Grammar Summary.md @@ -343,6 +343,19 @@ UnionTypeExtension : - extend union Name Directives[Const]? UnionMemberTypes - extend union Name Directives[Const] +IntersectionTypeDefinition : Description? intersection Name Directives[Const]? +IntersectionMemberTypes? + +UnionMemberTypes : + +- IntersectionMemberTypes | NamedType +- = `|`? NamedType + +IntersectionTypeExtension : + +- extend intersection Name Directives[Const]? IntersectionMemberTypes +- extend intersection Name Directives[Const] + EnumTypeDefinition : - Description? enum Name Directives[Const]? EnumValuesDefinition @@ -402,6 +415,7 @@ TypeSystemDirectiveLocation : one of - `ARGUMENT_DEFINITION` - `INTERFACE` - `UNION` +- `INTERSECTION` - `ENUM` - `ENUM_VALUE` - `INPUT_OBJECT` diff --git a/spec/Section 2 -- Language.md b/spec/Section 2 -- Language.md index bd5e1c3d5..1554d0087 100644 --- a/spec/Section 2 -- Language.md +++ b/spec/Section 2 -- Language.md @@ -528,8 +528,8 @@ Fragments are the primary unit of composition in GraphQL. Fragments allow for the reuse of common repeated selections of fields, reducing duplicated text in the document. Inline Fragments can be used directly within a -selection to condition upon a type condition when querying against an interface -or union. +selection to condition upon a type condition when querying against an interface, +union, or intersection. For example, if we wanted to fetch some common information about mutual friends as well as friends of some user: diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index b539b936e..bb5e7b524 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -261,6 +261,7 @@ TypeDefinition : - ObjectTypeDefinition - InterfaceTypeDefinition - UnionTypeDefinition +- IntersectionTypeDefinition - EnumTypeDefinition - InputObjectTypeDefinition @@ -276,7 +277,7 @@ Scalars and Enums form the leaves in response trees; the intermediate levels are `Object` types, which define a set of fields, where each field is another type in the system, allowing the definition of arbitrary type hierarchies. -GraphQL supports two abstract types: interfaces and unions. +GraphQL supports three abstract types: interfaces, unions and intersections. An `Interface` defines a list of fields; `Object` types and other Interface types which implement this Interface are guaranteed to implement those fields. @@ -287,6 +288,11 @@ A `Union` defines a list of possible types; similar to interfaces, whenever the type system claims a union will be returned, one of the possible types will be returned. +An `Intersection` defines a list of constraining abstract types. If a field +claims it returns an Intersection type, it will return only types that are +contained within all of the Intersections's Unions and types that implement all +of the Intersection's Interfaces. + Finally, oftentimes it is useful to provide complex structs as inputs to GraphQL field arguments or variables; the `Input Object` type allows the schema to define exactly what data is expected. @@ -314,9 +320,9 @@ to arguments and variables as well as the values output by fields. These two uses categorize types as _input types_ and _output types_. Some kinds of types, like Scalar and Enum types, can be used as both input types and output types; other kinds of types can only be used in one or the other. Input Object types -can only be used as input types. Object, Interface, and Union types can only be -used as output types. Lists and Non-Null types may be used as input types or -output types depending on how the wrapped type may be used. +can only be used as input types. Object, Interface, Union, and Intersection +types can only be used as output types. Lists and Non-Null types may be used as +input types or output types depending on how the wrapped type may be used. IsInputType(type) : @@ -332,7 +338,7 @@ IsOutputType(type) : - If {type} is a List type or Non-Null type: - Let {unwrappedType} be the unwrapped type of {type}. - Return IsOutputType({unwrappedType}) -- If {type} is a Scalar, Object, Interface, Union, or Enum type: +- If {type} is a Scalar, Object, Interface, Union, Intersection, or Enum type: - Return {true} - Return {false} @@ -706,8 +712,8 @@ Must only yield exactly that subset: ``` A field of an Object type may be a Scalar, Enum, another Object type, an -Interface, or a Union. Additionally, it may be any wrapping type whose -underlying base type is one of those five. +Interface, a Union, or an Intersection. Additionally, it may be any wrapping +type whose underlying base type is one of those five. For example, the `Person` type might include a `relationship`: @@ -928,7 +934,13 @@ IsValidImplementationFieldType(fieldType, implementedFieldType): 5. If {fieldType} is an Object or Interface type and {implementedFieldType} is an Interface type and {fieldType} declares it implements {implementedFieldType} then return {true}. -6. Otherwise return {false}. +6. If {fieldType} is an IntersectionType and {implementedFieldType} is an Union + type and {fieldType} is a member type of {implementedFieldType} then return + {true}. +7. If {fieldType} is an IntersectionType and {implementedFieldType} is an + Interface type and at least one of the members of {fieldType} declares it + implements {implementedFieldType} then return {true}. +8. Otherwise return {false}. ### Field Arguments @@ -977,7 +989,7 @@ May return the result: ``` The type of an object field argument must be an input type (any type except an -Object, Interface, or Union type). +Object, Interface, Union, or Intersection type). ### Field Deprecation @@ -1054,8 +1066,8 @@ objects and interfaces can then implement these interfaces which requires that the implementing type will define all fields defined by those interfaces. Fields on a GraphQL interface have the same rules as fields on a GraphQL object; -their type can be Scalar, Object, Enum, Interface, or Union, or any wrapping -type whose base type is one of those five. +their type can be Scalar, Object, Enum, Interface, Union, or Intersection, or +any wrapping type whose base type is one of those five. For example, an interface `NamedEntity` may describe a required field and types such as `Person` or `Business` may then implement this interface to guarantee @@ -1415,6 +1427,181 @@ Union type extensions have the potential to be invalid if incorrectly defined. 5. Any non-repeatable directives provided must not already apply to the original Union type. +## Intersections + +IntersectionTypeDefinition : Description? intersection Name Directives[Const]? +IntersectionMemberTypes? + +IntersectionMemberTypes : + +- IntersectionMemberTypes | NamedType +- = `|`? NamedType + +GraphQL Intersections are higher order abstract types that represent objects +satisfying the requirements of all of the Intersection's abstract member types. +Intersections combine the features of interfaces and unions; the objects +represented by an Intersection must implement all of the Intersection's +interfaces and must also be included within all of its unions. + +Just as with unions, intersections do not directly define any fields, so **no** +fields may be queried on this type without the use of type refining fragments or +inline fragments (with the exception of the meta-field {\_\_typename}). + +For example, we might define the following types: + +```graphql example +interface Link { + link: Downloadable +} + +type SearchResultLink implements Link { + link: DownloadableSearchResult +} + +interface Downloadable { + url: string +} + +union SearchResult = Photo | Person + +intersection DownloadableSearchResult = SearchResult & Downloadable + +type Person implements Downloadable { + url: String + name: String + age: Int +} + +type Photo implements Downloadable { + url: String + height: Int + width: Int +} + +type SearchQuery { + firstDownloadableSearchResult: DownloadableSearchResult +} +``` + +Because intersection `DownloadableSearchResult` includes interface +`Downloadable` as a constraining member type, the possible types of the +intersection implement the `Downloadable` interface. The `link` field within +`SearchResultLink` therefore implements the `link` field of interface `Link`. + +Just as with unions, the below could be ambiguous and is invalid. + +```graphql counter-example +{ + firstDownloadableSearchResult { + url + name + height + } +} +``` + +A valid operation includes typed fragments (in this example, inline fragments): + +```graphql example +{ + firstDownloadableSearchResult { + ... on Downloadable { + url + } + ... on Person { + name + } + ... on Photo { + height + } + } +} +``` + +Intersection members may be defined with an optional leading `&` character to +aid formatting when representing a longer list of constraining types: + +```raw graphql example +intersection DownloadableSearchResult = + & Downloadable + & SearchResult +``` + +Interfaces that are transitively included within an intersection (interfaces +implemented by an included interface) must also be included within the +intersection. For example, `AuthoredPublicContent` cannot include `Authored` +without also including `Node`: + +```raw graphql example +interface Node { + id: ID! +} + +interface Authored implements Node { + author: String +} + +union PublicContent = Document | Image + +intersection AuthoredPublicContent = PublicContent & Authored & Node +``` + +**Result Coercion** + +The intersection type should have some way of determining which object a given +result corresponds to. Once it has done so, the result coercion of the +intersection is the same as the result coercion of the object. + +**Input Coercion** + +Intersections are never valid inputs. + +**Type Validation** + +Intersection types have the potential to be invalid if incorrectly defined. + +1. An Intersection type must include one or more unique member types. +2. The member types of a Intersection type must all be Interface or Union base + types; Scalar, Enum, Object and other Intersection types must not be member + types of an Intersection. Similarly, wrapping types must not be member types + of an Intersection. +3. For each member type of an Intersection type: + 1. Let this member type be {memberType}. + 2. If {memberType} is an interface, all interfaces that {memberType} declares + it implements must also be member types of the Intersection. + +### Intersection Extensions + +IntersectionTypeExtension : + +- extend intersection Name Directives[Const]? IntersectionMemberTypes +- extend intersection Name Directives[Const] + +Intersection type extensions are used to represent an intersection type which +has been extended from some original intersection type. Similar to unions, this +might by utilized by a GraphQL service which is itself an extension of another +GraphQL service. + +**Type Validation** + +Intersection type extensions have the potential to be invalid if incorrectly +defined. + +1. The named type must already be defined and must be a Intersection type. +2. The member types of a Intersection type must all be Interface or Union base + types; Scalar, Enum, Object and other Intersection types must not be member + types of an Intersection. Similarly, wrapping types must not be member types + of an Intersection. +3. For each member type of an Intersection type: + 1. Let this member type be {memberType}. + 2. If {memberType} is an interface, all interfaces that {memberType} declares + it implements must also be member types of the Intersection. +4. All member types of an Intersection type extension must be unique. +5. All member types of an Intersection type extension must not already be a + member of the original Intersection type. +6. Any non-repeatable directives provided must not already apply to the original + Intersection type. + ## Enums EnumTypeDefinition : @@ -1876,6 +2063,7 @@ TypeSystemDirectiveLocation : one of - `ARGUMENT_DEFINITION` - `INTERFACE` - `UNION` +- `INTERSECTION` - `ENUM` - `ENUM_VALUE` - `INPUT_OBJECT` diff --git a/spec/Section 4 -- Introspection.md b/spec/Section 4 -- Introspection.md index 9b32133f8..5f8a444df 100644 --- a/spec/Section 4 -- Introspection.md +++ b/spec/Section 4 -- Introspection.md @@ -71,11 +71,12 @@ underscores {"\_\_"}. GraphQL supports type name introspection within any selection set in an operation, with the single exception of selections at the root of a subscription operation. Type name introspection is accomplished via the meta-field -`__typename: String!` on any Object, Interface, or Union. It returns the name of -the concrete Object type at that point during execution. +`__typename: String!` on any Object, Interface, Union, Intersection. It returns +the name of the concrete Object type at that point during execution. -This is most often used when querying against Interface or Union types to -identify which actual Object type of the possible types has been returned. +This is most often used when querying against Interface, Union, or Intersection +types to identify which actual Object type of the possible types has been +returned. As a meta-field, `__typename` is implicit and does not appear in the fields list in any defined type. @@ -140,7 +141,9 @@ type __Type { fields(includeDeprecated: Boolean = false): [__Field!] # must be non-null for OBJECT and INTERFACE, otherwise null. interfaces: [__Type!] - # must be non-null for INTERFACE and UNION, otherwise null. + # must be non-null for INTERSECTION, otherwise null. + memberTypes: [__Type!] + # must be non-null for INTERFACE, UNION, and INTERSECTION, otherwise null. possibleTypes: [__Type!] # must be non-null for ENUM, otherwise null. enumValues(includeDeprecated: Boolean = false): [__EnumValue!] @@ -157,6 +160,7 @@ enum __TypeKind { OBJECT INTERFACE UNION + INTERSECTION ENUM INPUT_OBJECT LIST @@ -210,6 +214,7 @@ enum __DirectiveLocation { ARGUMENT_DEFINITION INTERFACE UNION + INTERSECTION ENUM ENUM_VALUE INPUT_OBJECT @@ -256,6 +261,7 @@ possible value of the `__TypeKind` enum: - {"OBJECT"} - {"INTERFACE"} - {"UNION"} +- {"INTERSECTION"} - {"ENUM"} - {"INPUT_OBJECT"} - {"LIST"} @@ -310,6 +316,25 @@ Fields\: union. They must be object types. - All other fields must return {null}. +**Intersection** + +Intersections are a higher-order abstract type defined by a list of constraining +abstract types. The list of constraining abstract member types is accessible in +`memberTypes`. The possible types of an intersection are explicitly listed out +in `possibleTypes`. Types can be made parts of intersections without +modification of that type. + +Fields\: + +- `kind` must return `__TypeKind.INTERSECTION`. +- `name` must return a String. +- `description` may return a String or {null}. +- `memberTypes` returns the list of constraining abstract types defined by the + intersection. They must be interface or union types. +- `possibleTypes` returns the list of types that can be represented within this + intersection. They must be object types. +- All other fields must return {null}. + **Interface** Interfaces are an abstract type where there are common fields declared. Any type @@ -470,6 +495,7 @@ supported. All possible locations are listed in the `__DirectiveLocation` enum: - {"ARGUMENT_DEFINITION"} - {"INTERFACE"} - {"UNION"} +- {"INTERSECTION"} - {"ENUM"} - {"ENUM_VALUE"} - {"INPUT_OBJECT"} diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index 4eda8e7b4..753de1411 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -87,6 +87,9 @@ type Cat implements Pet { union CatOrDog = Cat | Dog union DogOrHuman = Dog | Human union HumanOrAlien = Human | Alien + +intersection CatOrDogPet = CatOrDog & Pet +intersection FriendlySmallAnimal = CatOrDog & DogOrHuman ``` ## Documents @@ -330,7 +333,7 @@ must provide the operation name as described in {GetOperation()}. ### Field Selections -Field selections must exist on Object, Interface, and Union types. +Field selections must exist on Object, Interface, Union, and Intersection types. **Formal Specification** @@ -375,10 +378,10 @@ fragment definedOnImplementorsButNotInterface on Pet { } ``` -Because unions do not define fields, fields may not be directly selected from a -union-typed selection set, with the exception of the meta-field {\_\_typename}. -Fields from a union-typed selection set must only be queried indirectly via a -fragment. +Because unions and intersections do not define fields, fields may not be +directly selected from a union-typed selection set, with the exception of the +meta-field {\_\_typename}. Fields from a union or intersection-typed selection +set must only be queried indirectly via a fragment. For example the following is valid: @@ -394,13 +397,18 @@ fragment inDirectFieldSelectionOnUnion on CatOrDog { } ``` -But the following is invalid: +But the following fragments are invalid: ```graphql counter-example fragment directFieldSelectionOnUnion on CatOrDog { name barkVolume } + +fragment directFieldSelectionOnIntersection on FriendlySmallAnimal { + name + barkVolume +} ``` ### Field Selection Merging @@ -572,7 +580,7 @@ fragment conflictingDifferingResponses on Pet { - Let {selectionType} be the result type of {selection} - If {selectionType} is a scalar or enum: - The subselection set of that selection must be empty -- If {selectionType} is an interface, union, or object +- If {selectionType} is an interface, union, intersection, or object - The subselection set of that selection must NOT BE empty **Explanatory Text** @@ -899,7 +907,8 @@ fragment inlineNotExistingType on Dog { **Formal Specification** - For each {fragment} defined in the document. -- The target type of fragment must have kind {UNION}, {INTERFACE}, or {OBJECT}. +- The target type of fragment must have kind {UNION}, {INTERFACE}, + {INTERSECTION}, or {OBJECT}. **Explanatory Text** @@ -1093,6 +1102,10 @@ GetPossibleTypes(type): - If {type} is an object type, return a set containing {type} - If {type} is an interface type, return the set of types implementing {type} - If {type} is a union type, return the set of possible types of {type} +- If {type} is an intersection type: + - Let {memberTypes} be the set of abstract types defined by the intersection. + - Return the intersection of {GetPossibleTypes(memberType)} for each + {memberType} of {memberTypes}. **Explanatory Text** @@ -1128,8 +1141,9 @@ fragment catInDogFragmentInvalid on Dog { ##### Abstract Spreads in Object Scope -In scope of an object type, unions or interface spreads can be used if the -object type implements the interface or is a member of the union. +In scope of an object type, union, interface, or intersection spreads can be +used if the object type implements the interface or is a member of the union or +intersection. For example @@ -1157,18 +1171,23 @@ fragment catOrDogNameFragment on CatOrDog { fragment unionWithObjectFragment on Dog { ...catOrDogNameFragment } + +fragment intersectionWithObjectFragment on FriendlySmallAnimal { + ...catOrDogNameFragment +} ``` -is valid because {Dog} is a member of the {CatOrDog} union. It is worth noting -that if one inspected the contents of the {CatOrDogNameFragment} you could note -that no valid results would ever be returned. However we do not specify this as -invalid because we only consider the fragment declaration, not its body. +is valid because {Dog} is a member of the {CatOrDog} union and +{FriendlySmallAnimal} intersection. It is worth noting that if one inspected the +contents of the {CatOrDogNameFragment} you could note that no valid results +would ever be returned. However we do not specify this as invalid because we +only consider the fragment declaration, not its body. ##### Object Spreads In Abstract Scope -Union or interface spreads can be used within the context of an object type -fragment, but only if the object type is one of the possible types of that -interface or union. +Union, interface, or intersection spreads can be used within the context of an +object type fragment, but only if the object type is one of the possible types +of that interface or union. For example, the following fragments are valid: @@ -1185,10 +1204,18 @@ fragment catOrDogFragment on CatOrDog { meowVolume } } + +fragment friendlySmallAnimalFragment on FriendlySmallAnimal { + ... on Dog { + barkVolume + } +} ``` {petFragment} is valid because {Dog} implements the interface {Pet}. {catOrDogFragment} is valid because {Cat} is a member of the {CatOrDog} union. +{friendlySmallAnimalFragment} is valid because {Dog} is a member of the +{FriendlySmallAnimal} intersection. By contrast the following fragments are invalid: @@ -1204,18 +1231,26 @@ fragment humanOrAlienFragment on HumanOrAlien { meowVolume } } + +fragment friendlySmallAnimalFragment on FriendlySmallAnimal { + ... on Cat { + meowVolume + } +} ``` {Dog} does not implement the interface {Sentient} and therefore {sentientFragment} can never return meaningful results. Therefore the fragment -is invalid. Likewise {Cat} is not a member of the union {HumanOrAlien}, and it -can also never return meaningful results, making it invalid. +is invalid. Likewise {Cat} is not a member of the union {HumanOrAlien} or the +intersection {FriendlySmallAnimal}, and it can also never return meaningful +results, making the remaining fragments invalid. ##### Abstract Spreads in Abstract Scope -Union or interfaces fragments can be used within each other. As long as there -exists at least _one_ object type that exists in the intersection of the -possible types of the scope and the spread, the spread is considered valid. +Union, interface, and intersection fragments can be used within each other. As +long as there exists at least _one_ object type that exists in the intersection +of the possible types of the scope and the spread, the spread is considered +valid. So for example diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index a0f3400d4..f925d22f2 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -551,7 +551,7 @@ DoesFragmentTypeApply(objectType, fragmentType): - If {fragmentType} is an Interface Type: - if {objectType} is an implementation of {fragmentType}, return {true} otherwise return {false}. -- If {fragmentType} is a Union: +- If {fragmentType} is a Union or Intersection: - if {objectType} is a possible type of {fragmentType}, return {true} otherwise return {false}. @@ -680,10 +680,10 @@ CompleteValue(fieldType, fields, result, variableValues): {resultItem} is each item in {result}. - If {fieldType} is a Scalar or Enum type: - Return the result of {CoerceResult(fieldType, result)}. -- If {fieldType} is an Object, Interface, or Union type: +- If {fieldType} is an Object, Interface, Union, or Intersection type: - If {fieldType} is an Object type. - Let {objectType} be {fieldType}. - - Otherwise if {fieldType} is an Interface or Union type. + - Otherwise if {fieldType} is an Interface, Union, or Intersection type. - Let {objectType} be {ResolveAbstractType(fieldType, result)}. - Let {subSelectionSet} be the result of calling {MergeSelectionSets(fields)}. - Return the result of evaluating {ExecuteSelectionSet(subSelectionSet, @@ -716,10 +716,10 @@ and output of {CoerceResult()} must not be {null}. **Resolving Abstract Types** -When completing a field with an abstract return type, that is an Interface or -Union return type, first the abstract type must be resolved to a relevant Object -type. This determination is made by the internal system using whatever means -appropriate. +When completing a field with an abstract return type, that is an Interface, +Union or Intersection return type, first the abstract type must be resolved to a +relevant Object type. This determination is made by the internal system using +whatever means appropriate. Note: A common method of determining the Object type for an {objectValue} in object-oriented environments, such as Java or C#, is to use the class name of