Skip to content

Commit e297e92

Browse files
RFC: Allow interfaces to implement other interfaces (#373)
Co-authored-by: Lee Byron <[email protected]>
1 parent 97db7cd commit e297e92

File tree

3 files changed

+152
-34
lines changed

3 files changed

+152
-34
lines changed

spec/Section 3 -- Type System.md

Lines changed: 122 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -250,9 +250,10 @@ type in the system, allowing the definition of arbitrary type hierarchies.
250250

251251
GraphQL supports two abstract types: interfaces and unions.
252252

253-
An `Interface` defines a list of fields; `Object` types that implement that
254-
interface are guaranteed to implement those fields. Whenever the type system
255-
claims it will return an interface, it will return a valid implementing type.
253+
An `Interface` defines a list of fields; `Object` types and other Interface
254+
types which implement this Interface are guaranteed to implement those fields.
255+
Whenever a field claims it will return an Interface type, it will return a
256+
valid implementing Object type during execution.
256257

257258
A `Union` defines a list of possible types; similar to interfaces, whenever the
258259
type system claims a union will be returned, one of the possible types will be
@@ -810,31 +811,54 @@ of rules must be adhered to by every Object type in a GraphQL schema.
810811
characters {"__"} (two underscores).
811812
2. The argument must accept a type where {IsInputType(argumentType)}
812813
returns {true}.
813-
4. An object type may declare that it implements one or more unique interfaces.
814-
5. An object type must be a super-set of all interfaces it implements:
815-
1. The object type must include a field of the same name for every field
816-
defined in an interface.
817-
1. The object field must be of a type which is equal to or a sub-type of
818-
the interface field (covariant).
819-
1. An object field type is a valid sub-type if it is equal to (the same
820-
type as) the interface field type.
821-
2. An object field type is a valid sub-type if it is an Object type and
822-
the interface field type is either an Interface type or a Union type
823-
and the object field type is a possible type of the interface field
824-
type.
825-
3. An object field type is a valid sub-type if it is a List type and
826-
the interface field type is also a List type and the list-item type
827-
of the object field type is a valid sub-type of the list-item type
828-
of the interface field type.
829-
4. An object field type is a valid sub-type if it is a Non-Null variant
830-
of a valid sub-type of the interface field type.
831-
2. The object field must include an argument of the same name for every
832-
argument defined in the interface field.
833-
1. The object field argument must accept the same type (invariant) as
834-
the interface field argument.
835-
3. The object field may include additional arguments not defined in the
836-
interface field, but any additional argument must not be required, e.g.
837-
must not be of a non-nullable type.
814+
3. An object type may declare that it implements one or more unique interfaces.
815+
4. An object type must be a super-set of all interfaces it implements:
816+
1. Let this object type be {objectType}.
817+
2. For each interface declared implemented as {interfaceType},
818+
{IsValidImplementation(objectType, interfaceType)} must be {true}.
819+
820+
IsValidImplementation(type, implementedType):
821+
822+
1. If {implementedType} declares it implements any interfaces,
823+
{type} must also declare it implements those interfaces.
824+
2. {type} must include a field of the same name for every field
825+
defined in {implementedType}.
826+
1. Let {field} be that named field on {type}.
827+
2. Let {implementedField} be that named field on {implementedType}.
828+
1. {field} must include an argument of the same name for every argument
829+
defined in {implementedField}.
830+
1. That named argument on {field} must accept the same type
831+
(invariant) as that named argument on {implementedField}.
832+
2. {field} may include additional arguments not defined in
833+
{implementedField}, but any additional argument must not be required,
834+
e.g. must not be of a non-nullable type.
835+
3. {field} must return a type which is equal to or a sub-type of
836+
(covariant) the return type of {implementedField} field's return type:
837+
1. Let {fieldType} be the return type of {field}.
838+
2. Let {implementedFieldType} be the return type of {implementedField}.
839+
3. {IsValidImplementationFieldType(fieldType, implementedFieldType)}
840+
must be {true}.
841+
842+
IsValidImplementationFieldType(fieldType, implementedFieldType):
843+
1. If {fieldType} is a Non-Null type:
844+
1. Let {nullableType} be the unwrapped nullable type of {fieldType}.
845+
2. Let {implementedNullableType} be the unwrapped nullable type
846+
of {implementedFieldType} if it is a Non-Null type, otherwise let it be
847+
{implementedFieldType} directly.
848+
3. Return {IsValidImplementationFieldType(nullableType, implementedNullableType)}.
849+
2. If {fieldType} is a List type and {implementedFieldType} is also a List type:
850+
1. Let {itemType} be the unwrapped item type of {fieldType}.
851+
2. Let {implementedItemType} be the unwrapped item type
852+
of {implementedFieldType}.
853+
3. Return {IsValidImplementationFieldType(itemType, implementedItemType)}.
854+
3. If {fieldType} is the same type as {implementedFieldType} then return {true}.
855+
4. If {fieldType} is an Object type and {implementedFieldType} is
856+
a Union type and {fieldType} is a possible type of {implementedFieldType}
857+
then return {true}.
858+
5. If {fieldType} is an Object or Interface type and {implementedFieldType}
859+
is an Interface type and {fieldType} declares it implements
860+
{implementedFieldType} then return {true}.
861+
6. Otherwise return {false}.
838862

839863

840864
### Field Arguments
@@ -954,8 +978,8 @@ Object type extensions have the potential to be invalid if incorrectly defined.
954978
InterfaceTypeDefinition : Description? interface Name Directives[Const]? FieldsDefinition?
955979

956980
GraphQL interfaces represent a list of named fields and their arguments. GraphQL
957-
objects can then implement these interfaces which requires that the object type
958-
will define all fields defined by those interfaces.
981+
objects and interfaces can then implement these interfaces which requires that
982+
the implementing type will define all fields defined by those interfaces.
959983

960984
Fields on a GraphQL interface have the same rules as fields on a GraphQL object;
961985
their type can be Scalar, Object, Enum, Interface, or Union, or any wrapping
@@ -1045,6 +1069,63 @@ interface. Querying for `age` is only valid when the result of `entity` is a
10451069
}
10461070
```
10471071

1072+
**Interfaces Implementing Interfaces**
1073+
1074+
When defining an interface that implements another interface, the implementing
1075+
interface must define each field that is specified by the implemented interface.
1076+
For example, the interface Resource must define the field id to implement the
1077+
Node interface:
1078+
1079+
```graphql example
1080+
interface Node {
1081+
id: ID!
1082+
}
1083+
1084+
interface Resource implements Node {
1085+
id: ID!
1086+
url: String
1087+
}
1088+
```
1089+
1090+
Transitively implemented interfaces (interfaces implemented by the interface
1091+
that is being implemented) must also be defined on an implementing type or
1092+
interface. For example, `Image` cannot implement `Resource` without also
1093+
implementing `Node`:
1094+
1095+
```graphql example
1096+
interface Node {
1097+
id: ID!
1098+
}
1099+
1100+
interface Resource implements Node {
1101+
id: ID!
1102+
url: String
1103+
}
1104+
1105+
interface Image implements Resource & Node {
1106+
id: ID!
1107+
url: String
1108+
thumbnail: String
1109+
}
1110+
```
1111+
1112+
Interface definitions must not contain cyclic references nor implement
1113+
themselves. This example is invalid because `Node` and `Named` implement
1114+
themselves and each other:
1115+
1116+
```graphgl counter-example
1117+
interface Node implements Named & Node {
1118+
id: ID!
1119+
name: String
1120+
}
1121+
1122+
interface Named implements Node & Named {
1123+
id: ID!
1124+
name: String
1125+
}
1126+
```
1127+
1128+
10481129
**Result Coercion**
10491130

10501131
The interface type should have some way of determining which object a given
@@ -1072,6 +1153,12 @@ Interface types have the potential to be invalid if incorrectly defined.
10721153
characters {"__"} (two underscores).
10731154
2. The argument must accept a type where {IsInputType(argumentType)}
10741155
returns {true}.
1156+
3. An interface type may declare that it implements one or more unique
1157+
interfaces, but may not implement itself.
1158+
4. An interface type must be a super-set of all interfaces it implements:
1159+
1. Let this interface type be {implementingType}.
1160+
2. For each interface declared implemented as {implementedType},
1161+
{IsValidImplementation(implementingType, implementedType)} must be {true}.
10751162

10761163

10771164
### Interface Extensions
@@ -1121,11 +1208,13 @@ Interface type extensions have the potential to be invalid if incorrectly define
11211208
fields may share the same name.
11221209
3. Any fields of an Interface type extension must not be already defined on the
11231210
original Interface type.
1124-
4. Any Object type which implemented the original Interface type must also be a
1125-
super-set of the fields of the Interface type extension (which may be due to
1126-
Object type extension).
1211+
4. Any Object or Interface type which implemented the original Interface type
1212+
must also be a super-set of the fields of the Interface type extension (which
1213+
may be due to Object type extension).
11271214
5. Any non-repeatable directives provided must not already apply to the
11281215
original Interface type.
1216+
6. The resulting extended Interface type must be a super-set of all Interfaces
1217+
it implements.
11291218

11301219

11311220
## Unions

spec/Section 4 -- Introspection.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ type __Type {
132132
# should be non-null for OBJECT and INTERFACE only, must be null for the others
133133
fields(includeDeprecated: Boolean = false): [__Field!]
134134

135-
# should be non-null for OBJECT only, must be null for the others
135+
# should be non-null for OBJECT and INTERFACE only, must be null for the others
136136
interfaces: [__Type!]
137137

138138
# should be non-null for INTERFACE and UNION only, always null for the others
@@ -292,6 +292,7 @@ Fields
292292
* `fields`: The set of fields required by this interface.
293293
* Accepts the argument `includeDeprecated` which defaults to {false}. If
294294
{true}, deprecated fields are also returned.
295+
* `interfaces`: The set of interfaces that this interface implements.
295296
* `possibleTypes` returns the list of types that implement this interface.
296297
They must be object types.
297298
* All other fields must return {null}.

spec/Section 5 -- Validation.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1262,6 +1262,34 @@ is not valid because there exists no type that implements both {Pet}
12621262
and {Sentient}.
12631263

12641264

1265+
**Interface Spreads in implemented Interface Scope**
1266+
1267+
Additionally, an interface type fragment can always be spread into an
1268+
interface scope which it implements.
1269+
1270+
In the example below, the `...resourceFragment` fragments spreads is valid,
1271+
since `Resource` implements `Node`.
1272+
1273+
```graphql example
1274+
interface Node {
1275+
id: ID!
1276+
}
1277+
1278+
interface Resource implements Node {
1279+
id: ID!
1280+
url: String
1281+
}
1282+
1283+
fragment interfaceWithInterface on Node {
1284+
...resourceFragment
1285+
}
1286+
1287+
fragment resourceFragment on Resource {
1288+
url
1289+
}
1290+
```
1291+
1292+
12651293
## Values
12661294

12671295

0 commit comments

Comments
 (0)