From 18e70e5e9dd21f533c2e08cd3ad6a51154c495dd Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Fri, 27 May 2022 10:20:09 +0300 Subject: [PATCH 1/4] allow unions to include interfaces and unions --- spec/Section 3 -- Type System.md | 73 ++++++++++++++++++++++++++---- spec/Section 4 -- Introspection.md | 10 ++-- 2 files changed, 71 insertions(+), 12 deletions(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index b539b936e..30bcf77b8 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -923,8 +923,8 @@ IsValidImplementationFieldType(fieldType, implementedFieldType): 3. Return {IsValidImplementationFieldType(itemType, implementedItemType)}. 3. If {fieldType} is the same type as {implementedFieldType} then return {true}. 4. If {fieldType} is an Object type and {implementedFieldType} is a Union type - and {fieldType} is a possible type of {implementedFieldType} then return - {true}. + and {fieldType} is a possible type or member type of {implementedFieldType} + then return {true}. 5. If {fieldType} is an Object or Interface type and {implementedFieldType} is an Interface type and {fieldType} declares it implements {implementedFieldType} then return {true}. @@ -1302,8 +1302,8 @@ UnionMemberTypes : - = `|`? NamedType GraphQL Unions represent an object that could be one of a list of GraphQL Object -types, but provides for no guaranteed fields between those types. They also -differ from interfaces in that Object types declare what interfaces they +types, providing for no guaranteed fields between those types. They differ from +interfaces in that Object and Interface types declare what interfaces they implement, but are not aware of what unions contain them. With interfaces and objects, only those fields defined on the type can be @@ -1370,6 +1370,61 @@ union SearchResult = | Person ``` +**Unions of Interfaces and Unions** + +A Union may declare interfaces or other unions as member types. The parent +Union's possible types transitively include all the possible types of any +abstract member types. For example, the following types are valid: + +```graphql example +union SearchResult = Photo | Named + +interface Named { + name: String +} + +type Person { + name: String + age: Int +} + +union Item = Photo | Video + +type Photo { + height: Int + width: Int +} + +type Video { + codec: String +} + +type SearchQuery { + firstSearchResult: SearchResult +} +``` + +And, given the above, the following operation is valid: + +```graphql example +{ + firstSearchResult { + ... on Named { + name + } + ... on Person { + age + } + ... on Photo { + height + } + ... on Video { + codec + } + } +} +``` + **Result Coercion** The union type should have some way of determining which object a given result @@ -1385,8 +1440,8 @@ Unions are never valid inputs. Union types have the potential to be invalid if incorrectly defined. 1. A Union type must include one or more unique member types. -2. The member types of a Union type must all be Object base types; Scalar, - Interface and Union types must not be member types of a Union. Similarly, +2. The member types of a Union type must all be Object, Interface or Union + types; Scalar and Enum types must not be member types of a Union. Similarly, wrapping types must not be member types of a Union. ### Union Extensions @@ -1406,9 +1461,9 @@ another GraphQL service. Union type extensions have the potential to be invalid if incorrectly defined. 1. The named type must already be defined and must be a Union type. -2. The member types of a Union type extension must all be Object base types; - Scalar, Interface and Union types must not be member types of a Union. - Similarly, wrapping types must not be member types of a Union. +2. The member types of a Union type must all be Object, Interface or Union + types; Scalar and Enum types must not be member types of a Union. Similarly, + wrapping types must not be member types of a Union. 3. All member types of a Union type extension must be unique. 4. All member types of a Union type extension must not already be a member of the original Union type. diff --git a/spec/Section 4 -- Introspection.md b/spec/Section 4 -- Introspection.md index 9b32133f8..da54cd93c 100644 --- a/spec/Section 4 -- Introspection.md +++ b/spec/Section 4 -- Introspection.md @@ -140,6 +140,8 @@ type __Type { fields(includeDeprecated: Boolean = false): [__Field!] # must be non-null for OBJECT and INTERFACE, otherwise null. interfaces: [__Type!] + # must be non-null for UNION, otherwise null. + memberTypes: [__Type!] # must be non-null for INTERFACE and UNION, otherwise null. possibleTypes: [__Type!] # must be non-null for ENUM, otherwise null. @@ -297,7 +299,8 @@ Fields\: **Union** -Unions are an abstract type where no common fields are declared. The possible +Unions are an abstract type where no common fields are declared. The declared +member types of a union are accessible via `memberTypes`. The possible Object types of a union are explicitly listed out in `possibleTypes`. Types can be made parts of unions without modification of that type. @@ -306,8 +309,9 @@ Fields\: - `kind` must return `__TypeKind.UNION`. - `name` must return a String. - `description` may return a String or {null}. -- `possibleTypes` returns the list of types that can be represented within this - union. They must be object types. +- `memberTypes` returns the list of member types declared by the union. +- `possibleTypes` returns the list of Object types that can be represented + within this union. - All other fields must return {null}. **Interface** From 2a4d3ed1ccce99ec6a1aa9e954107b9652b0bb01 Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Sun, 29 May 2022 22:06:22 +0300 Subject: [PATCH 2/4] add rule requiring explicit listing of member types of child unions --- spec/Section 3 -- Type System.md | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 30bcf77b8..79fd00cdf 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -1372,12 +1372,13 @@ union SearchResult = **Unions of Interfaces and Unions** -A Union may declare interfaces or other unions as member types. The parent -Union's possible types transitively include all the possible types of any -abstract member types. For example, the following types are valid: +A Union may declare interfaces or other unions as member types. Transitively +included object types (object types included within a union included by a union) +must also be included within the parent union. For example, the following types +are valid: ```graphql example -union SearchResult = Photo | Named +union SearchResult = Item | Photo | Video | Named interface Named { name: String @@ -1425,6 +1426,13 @@ And, given the above, the following operation is valid: } ``` +While the following union is invalid, because the member types of `Item` are not +explicitly included within `SearchResult`: + +```graphql counter-example +union SearchResult = Item | Named +``` + **Result Coercion** The union type should have some way of determining which object a given result @@ -1443,6 +1451,12 @@ Union types have the potential to be invalid if incorrectly defined. 2. The member types of a Union type must all be Object, Interface or Union types; Scalar and Enum types must not be member types of a Union. Similarly, wrapping types must not be member types of a Union. +3. A parent Union must explicitly include as member types of all child Union + members. + 1. Let this union type be {unionType}. + 2. For each {memberType} declared as a member of {unionType}, if {memberType} + is a Union type, all of the members of {memberType} must also be members + of {unionType}. ### Union Extensions @@ -1469,6 +1483,12 @@ Union type extensions have the potential to be invalid if incorrectly defined. the original Union type. 5. Any non-repeatable directives provided must not already apply to the original Union type. +6. A parent Union must explicitly include as member types of all child Union + members. + 1. Let this union type be {unionType}. + 2. For each {memberType} declared as a member of {unionType}, if {memberType} + is a Union type, all of the members of {memberType} must also be members + of {unionType}. ## Enums From ce4077336201792e8b2792eb77ce712bf9e0a598 Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Thu, 28 Jul 2022 04:33:07 +0300 Subject: [PATCH 3/4] add requirement that implementions of interfaces included by unions must be explicitly listed within the union --- spec/Section 3 -- Type System.md | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 79fd00cdf..0327237c2 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -1373,18 +1373,22 @@ union SearchResult = **Unions of Interfaces and Unions** A Union may declare interfaces or other unions as member types. Transitively -included object types (object types included within a union included by a union) -must also be included within the parent union. For example, the following types -are valid: +included types must also be explicitly included within the parent union, i.e. if +a parent union includes a child union, all types included by the child union +must be explicitly included by the parent union. Similarly, if a union includes +an interface, all types implementing the interface must be explicitly included +by the union. + +For example, the following types are valid: ```graphql example -union SearchResult = Item | Photo | Video | Named +union SearchResult = Item | Photo | Video | Named | Person interface Named { name: String } -type Person { +type Person implements Named { name: String age: Int } @@ -1426,11 +1430,18 @@ And, given the above, the following operation is valid: } ``` -While the following union is invalid, because the member types of `Item` are not -explicitly included within `SearchResult`: +While the following union is invalid, because `Photo` and `Video` are contained +by the union `Item` and are not explicitly included within `SearchResult`: + +```graphql counter-example +union SearchResult = Item | Named | Person +``` + +The following union is also invalid, because `Person` implements `Named`, but is +not explicitly included within `SearchResult`: ```graphql counter-example -union SearchResult = Item | Named +union SearchResult = Item | Photo | Video | Named ``` **Result Coercion** From d8e52f0794423b32874e8c6972211e3dcf5e027f Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Thu, 28 Jul 2022 06:19:56 +0300 Subject: [PATCH 4/4] update validation section to match text with regard to requirement to list all transitive possible types, including member interfaces --- spec/Section 3 -- Type System.md | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 0327237c2..80b6d02e3 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -1462,12 +1462,14 @@ Union types have the potential to be invalid if incorrectly defined. 2. The member types of a Union type must all be Object, Interface or Union types; Scalar and Enum types must not be member types of a Union. Similarly, wrapping types must not be member types of a Union. -3. A parent Union must explicitly include as member types of all child Union - members. +3. A Union type must explicitly include all possible types of any included + abstract types. 1. Let this union type be {unionType}. - 2. For each {memberType} declared as a member of {unionType}, if {memberType} - is a Union type, all of the members of {memberType} must also be members - of {unionType}. + 2. For each {memberType} declared as a member of {unionType}: + 1. If {memberType} is a Union type, all of the members of {memberType} + must also be members of {unionType}. + 2. If {memberType} is an Interface type, all implementations of + {memberType} must also be members of {unionType}. ### Union Extensions @@ -1494,12 +1496,14 @@ Union type extensions have the potential to be invalid if incorrectly defined. the original Union type. 5. Any non-repeatable directives provided must not already apply to the original Union type. -6. A parent Union must explicitly include as member types of all child Union - members. +6. A Union type must explicitly include all possible types of any included + abstract types. 1. Let this union type be {unionType}. - 2. For each {memberType} declared as a member of {unionType}, if {memberType} - is a Union type, all of the members of {memberType} must also be members - of {unionType}. + 2. For each {memberType} declared as a member of {unionType}: + 1. If {memberType} is a Union type, all of the members of {memberType} + must also be members of {unionType}. + 2. If {memberType} is an Interface type, all implementations of + {memberType} must also be members of {unionType}. ## Enums