Hey.
Currently, per spec, paragraph 5.3.2, two fields of the same name cannot be selected on two different, mutually exclusive types, if field types or arguments differ. E.g. given this schema:
union CatOrDog = Cat | Dog
type Query {
findPet: CatOrDog!
findDog: Dog!
}
interface Pet {
name: String!
}
type Dog implements Pet {
name: String!
nickname: String
barkVolume: Int
anotherCommonlyNamedField: String
}
type Cat implements Pet {
name: String!
nickname: String
meowVolume: Int
anotherCommonlyNamedField: String!
}
this will result in an error (Example #125 in the spec):
fragment conflictingDifferingResponses on Pet {
... on Dog {
someValue: nickname
}
... on Cat {
someValue: meowVolume
}
}
and so will this:
query {
findPet {
__typename
... on Dog {
name
anotherCommonlyNamedField
}
... on Cat {
name
anotherCommonlyNamedField
}
}
}
due to anotherCommonlyNamedField field having different return types in Dog and Cat types.
It forces usage of aliases when objects are obtained through a union type. E.g. if client's uses a type, based on structure of a GraphQL type Dog, one they can obtain from calling findDog, they would no longer be able to have the same structure if they need to switch to findPet.
For example, let's say a client uses this query:
{
findDog {
name
nickname
barkVolume
anotherCommonlyNamedField
}
}
and they have a type like so, used in multiple places in the project:
type Dog = {
name: string
nickname: string | null
barkVolume: number | null
anotherCommonlyNamedField: string | null
}
and then Cat and findPet are introduced into the schema, the client needs to support both Cat and Dog, they'd need to switch to the new findPet field:
{
findPet {
__typename
... on Dog {
name
nickname
barkVolume
anotherCommonlyNamedField
}
... on Cat {
name
nickname
meowVolume
anotherCommonlyNamedField
}
}
}
which, unexpectedly, doesn't work. Instead, they have to introduce aliases:
{
findPet {
__typename
... on Dog {
name
nickname
barkVolume
anotherCommonlyNamedFieldNullable: anotherCommonlyNamedField
}
... on Cat {
name
nickname
meowVolume
anotherCommonlyNamedFieldNonNullable: anotherCommonlyNamedField
}
}
}
which breaks the original Dog interface the client had. Now, they either have to manually convert the type to a different structure (rename anotherCommonlyNamedFieldNullable and anotherCommonlyNamedFieldNonNullable back into anotherCommonlyNamedField), or change it across the entire client code.
This is suboptimal, as it forces the client to do extra work. In either situation, the client must check for the typename through __typename - because only dogs have barkVolume, and only cats have meowVolume. If this check is done either way, why would different types of a shared-name field matter?
In other words, if the client already has to differentiate between different, unrelated, mutually exclusive types, that may have entirely different number of fields and an entirely different structure, why do only fields that intersect between the two have to have a shared type? If response structure is expected to be the same, regardless of the actual type of an object, then shouldn't it also enforce that a Dog must have a meowVolume, and a Cat must have a barkVolume?
Reported elsewhere
This has been reported as a bug/problem in implementations of GraphQL:
The issues were closed as the spec dictates the specific behaviour a GraphQL server should follow.
Proposal
The proposal is to skip conflict checking for mutually exclusive types altogether. In terms of graphql-js implementation, a patch would look like so:
https://github.com/graphql/graphql-js/blob/v16.11.0/src/validation/rules/OverlappingFieldsCanBeMergedRule.ts#L606
- if (!areMutuallyExclusive) {
+ if (areMutuallyExclusive} {
+ return null;
+ }
An alternative proposal is to do the same, but only if __typename field was requested.
Hey.
Currently, per spec, paragraph 5.3.2, two fields of the same name cannot be selected on two different, mutually exclusive types, if field types or arguments differ. E.g. given this schema:
this will result in an error (Example #125 in the spec):
and so will this:
due to
anotherCommonlyNamedFieldfield having different return types inDogandCattypes.It forces usage of aliases when objects are obtained through a union type. E.g. if client's uses a type, based on structure of a GraphQL type
Dog, one they can obtain from callingfindDog, they would no longer be able to have the same structure if they need to switch tofindPet.For example, let's say a client uses this query:
{ findDog { name nickname barkVolume anotherCommonlyNamedField } }and they have a type like so, used in multiple places in the project:
and then
CatandfindPetare introduced into the schema, the client needs to support bothCatandDog, they'd need to switch to the newfindPetfield:{ findPet { __typename ... on Dog { name nickname barkVolume anotherCommonlyNamedField } ... on Cat { name nickname meowVolume anotherCommonlyNamedField } } }which, unexpectedly, doesn't work. Instead, they have to introduce aliases:
{ findPet { __typename ... on Dog { name nickname barkVolume anotherCommonlyNamedFieldNullable: anotherCommonlyNamedField } ... on Cat { name nickname meowVolume anotherCommonlyNamedFieldNonNullable: anotherCommonlyNamedField } } }which breaks the original
Doginterface the client had. Now, they either have to manually convert the type to a different structure (renameanotherCommonlyNamedFieldNullableandanotherCommonlyNamedFieldNonNullableback intoanotherCommonlyNamedField), or change it across the entire client code.This is suboptimal, as it forces the client to do extra work. In either situation, the client must check for the typename through
__typename- because only dogs havebarkVolume, and only cats havemeowVolume. If this check is done either way, why would different types of a shared-name field matter?In other words, if the client already has to differentiate between different, unrelated, mutually exclusive types, that may have entirely different number of fields and an entirely different structure, why do only fields that intersect between the two have to have a shared type? If response structure is expected to be the same, regardless of the actual type of an object, then shouldn't it also enforce that a
Dogmust have ameowVolume, and aCatmust have abarkVolume?Reported elsewhere
This has been reported as a bug/problem in implementations of GraphQL:
The issues were closed as the spec dictates the specific behaviour a GraphQL server should follow.
Proposal
The proposal is to skip conflict checking for mutually exclusive types altogether. In terms of
graphql-jsimplementation, a patch would look like so:https://github.com/graphql/graphql-js/blob/v16.11.0/src/validation/rules/OverlappingFieldsCanBeMergedRule.ts#L606
An alternative proposal is to do the same, but only if
__typenamefield was requested.