graphql
graphql copied to clipboard
Type references in `union` doesn't get resolved when used as return type for an Object field
Is there an existing issue for this?
- [X] I have searched the existing issues
Current behavior
References part of a union type can't be resolved by the GraphQL Gateway (federation) when is used as a type for a field. If the union type is used as return type for a Query or Mutation everything works as expected.
Minimum reproduction code
https://github.com/Gamote/nestjs-graphql-union-mrr
Steps to reproduce
Please check the minimum reproduction code for more info on the types.
When FavoriteItemUnion
is used as return type for a field in an Object type.
// Field definition in the `users` service
@ObjectType()
@Directive('@key(fields: "id")')
export class User {
@Field(() => Int)
id: number;
@Field()
firstName: string;
@Field()
lastName: string;
// This field returns a union type
@Field(() => FavoriteItemUnion)
favoriteItem: typeof FavoriteItemUnion;
}
// Query resolver defined in the `users` service
@Query(() => User)
async getUserById(@Args('id') id: number) {
return this.usersService.getById(id);
/*
^ For `userId=1` will return the following:
{
"id": 1,
"firstName": "John",
"lastName": "Doe",
"favoriteItem": {
"__typename": "Song",
"id": 1
}
}
*/
}
# Query to retrieve the user + his favorite item
query GetUserById {
getUserById(id: 1) {
id
firstName
lastName
favoriteItem {
... on Song {
id
title
}
}
}
}
{
"errors": [
{
"message": "Cannot return null for non-nullable field Song.title.",
"locations": [
{
"line": 9,
"column": 9
}
],
"path": [
"getUserById",
"favoriteItem",
"title"
]
}
],
"data": null
}
In this case, if we remove the title
from the query, we will get a response as GraphQL doesn't need to resolve any more data.
Expected behavior
I expect for this query:
query GetUserById {
getUserById(id: 1) {
id
firstName
lastName
favoriteItem {
... on Song {
id
title
}
}
}
}
to return this data:
{
"data": {
"getUserById": {
"id": 1,
"firstName": "John",
"lastName": "Doe",
"favoriteItem": {
"id": 1,
"title": "Song 1"
}
}
}
}
Package version
10.0.10
Graphql version
graphql
: 16.4.0
@nestjs/mercurius
: 10.0.9
@nestjs/platform-fastify
: 8.4.4
mercurius
: v9.4.0
NestJS version
8.0.0
Node.js version
v16.14.2
In which operating systems have you tested?
- [X] macOS
- [ ] Windows
- [ ] Linux
Other
- I have tried to identify the problem in the source code but without success
- I have used the plain GraphQL and the query was successful so the functionality it is supported by GraphQL
In addition to this, I have also experienced that it is not possible to make the union fields as nullable. They are always resolved as mandatory fields.
// This field returns a union type
@Field(() => FavoriteItemUnion, { nullalbe: true})
favoriteItem: typeof FavoriteItemUnion;
This makes favoruteItem
as a mandatory field and not nullable
I have the same issue as well still today.
The object type in question:
export const Rol = createUnionType({
name: 'Rol',
types: ()=> [Student, Profesor] as const,
resolveType(value) {
if (value.an) {
return 'Student';
}
return 'Profesor';
}
});
export type RolCreereInputType = StudentCreereInput | ProfesorCreereInput;
@Schema()
@ObjectType()
export class User extends Document {
@Prop()
@Field(()=> ID)
_id: string;
@Prop({required: true})
@Field(()=> String)
nume: string;
@Prop({required: true})
@Field(()=> String)
eMail: string;
@Prop({required: true})
@Field(()=> String)
numarTelefon: string;
@Prop({type: S.Types.ObjectId, refPath: 'onModel'})
@Field(()=> Rol)
rol: typeof Rol;
}
The query:
query GasireUser($gasireUserId: String!) {
gasireUser(id: $gasireUserId) {
nume
numarTelefon
eMail
rol {
... on Profesor {
_id
persoana {
nume
}
}
}
}
}
Query resolver:
@Query(()=> User)
async gasireUser(@Args("id") id: string) {
return await this.userService.gasireUser(id);
}
The result:
{
"errors": [
{
"message": "Cannot return null for non-nullable field Profesor.persoana.",
"locations": [
{
"line": 9,
"column": 9
}
],
"path": [
"gasireUser",
"rol",
"persoana"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"stacktrace": [
"Error: Cannot return null for non-nullable field Profesor.persoana.",
" at completeValue (D:\\Facultate\\Licenta\\utm-prezenta-api\\node_modules\\graphql\\execution\\execute.js:594:13)",
" at executeField (D:\\Facultate\\Licenta\\utm-prezenta-api\\node_modules\\graphql\\execution\\execute.js:489:19)",
" at executeFields (D:\\Facultate\\Licenta\\utm-prezenta-api\\node_modules\\graphql\\execution\\execute.js:413:20)",
" at completeObjectValue (D:\\Facultate\\Licenta\\utm-prezenta-api\\node_modules\\graphql\\execution\\execute.js:914:10)",
" at completeAbstractValue (D:\\Facultate\\Licenta\\utm-prezenta-api\\node_modules\\graphql\\execution\\execute.js:795:10)",
" at completeValue (D:\\Facultate\\Licenta\\utm-prezenta-api\\node_modules\\graphql\\execution\\execute.js:624:12)",
" at completeValue (D:\\Facultate\\Licenta\\utm-prezenta-api\\node_modules\\graphql\\execution\\execute.js:584:23)",
" at executeField (D:\\Facultate\\Licenta\\utm-prezenta-api\\node_modules\\graphql\\execution\\execute.js:489:19)",
" at executeFields (D:\\Facultate\\Licenta\\utm-prezenta-api\\node_modules\\graphql\\execution\\execute.js:413:20)",
" at completeObjectValue (D:\\Facultate\\Licenta\\utm-prezenta-api\\node_modules\\graphql\\execution\\execute.js:914:10)"
]
}
}
}
],
"data": null
}
My workaround was to create a separate Nullable type, smth like this:
@InputType('NullableDataInput')
@ObjectType()
export class NullableData {
@Field({ nullable: true })
isNull?: boolean;
}
and then use it in my union type, when we do resolveType, we always default to the NullableType
.
export const FavouriteItemUnion = createUnionType({
name: 'FavouriteItemUnion',
types: () =>
[
Banana,
Apple,
NullableData,
] as const,
resolveType: (value: any) => {
switch (value.xxx) {
case 'banana':
return Banana;
case 'apple':
return Apple;
...
default:
return NullableData;
}
},
});
Hope it helps!
I didn't totally helped me, but at least it helped me for debugging. I also work with mongoose. If I am to return a value like a string for the object I see that it has no problems, but I now see that it crashes when I am to return an object that needs populated. Still, thanks
Let's say I have this:
export const UserUnion = createUnionType({
name: 'OpponentUnion',
types: () => [Number, User],
});
where User is ObjectType. and later I do:
@Field(() => UserUnion)
defender: number | User;
Which I'm not able to do because of an error (const getObjectType = (item) => this.typeDefinitionsStorage.getObjectTypeByTarget(item).type;
). Even if I use a custom Scalar for the Number.
I'm able to do this without an error:
@Field((type) => [Int, User])
But later in the schema.gql I get this:
defender: [Int!]!
Which is not what expected.
Having the same issue here, for example:
import { InputType, Field } from '@nestjs/graphql';
import { createUnionType } from '@nestjs/graphql';
const StringOrArrayOfStringType = createUnionType({
name: 'StringOrArrayOfString',
types: () => [String],
resolveType(value) {
if (Array.isArray(value)) {
return [String];
}
return String;
},
});
@InputType()
export class MyInput {
@Field(() => StringOrArrayOfStringType)
myField: string | string[];
}
Will result to en error:
Error: Cannot determine a GraphQL input type null for the "name". Make sure your class is decorated with an appropriate decorator.
Having the same issue here, for example:
import { InputType, Field } from '@nestjs/graphql'; import { createUnionType } from '@nestjs/graphql'; const StringOrArrayOfStringType = createUnionType({ name: 'StringOrArrayOfString', types: () => [String], resolveType(value) { if (Array.isArray(value)) { return [String]; } return String; }, }); @InputType() export class MyInput { @Field(() => StringOrArrayOfStringType) myField: string | string[]; }
Will result to en error:
Error: Cannot determine a GraphQL input type null for the "name". Make sure your class is decorated with an appropriate decorator.
The same problem-(((
@kamilmysliwiec is there any update on this issue? This problem still exists. Unions would be super helpful to simplify API design, but with this bug there is very limited use of the union type. I understand that unions don't work as an input type (still not implemented in the standard), but a fix for this bug would be amazing.
@kamilmysliwiec Looks like unions doesn't work at all, I tried mostly the same as guys did above and it doesn't working. Is it planned to be fixed any time soon? Thanks! :)
Having the same issue here, for example:
import { InputType, Field } from '@nestjs/graphql'; import { createUnionType } from '@nestjs/graphql'; const StringOrArrayOfStringType = createUnionType({ name: 'StringOrArrayOfString', types: () => [String], resolveType(value) { if (Array.isArray(value)) { return [String]; } return String; }, }); @InputType() export class MyInput { @Field(() => StringOrArrayOfStringType) myField: string | string[]; }
Will result to en error:
Error: Cannot determine a GraphQL input type null for the "name". Make sure your class is decorated with an appropriate decorator.
I am also facing the same issue. I wanted object or array of objects