GiiiZz
GiiiZz

Reputation: 33

Graphql/Apollo Errors output sometimes data: null sometimes data: { function: null}

I'm setting up testing for my graphql. While doing so i realise that sometimes the data object on error cases outputs like that :

{
   errors: [...],
   data: null
}

and sometimes :

{
     errors: [...],
     data: {
        updateCity: null
    }
}

Those test are on mutations. This is an example of both code :

resolver case 1 :

 updateUser(parent, args, context, info) {
      logger.debug('Mutation > updateUser resolver');
      return userController.user.update.user(parent, args.userInfo, context, info);
    },

schema case 1 :

extend type Mutation {
    updateUser(userInfo: UserInfo!): User!
}

controller case 1 :

user: async (parent, args, context, info) => {
    try {
      logger.debug('User Controller : update User');
      await controller.validate(args);
      const userModel = new UserModel('member');
      if (!(await userModel.findOne(args.id))) errorscb.userInputError('User does not exist');
      let id = args.id;
      let newArgs = args;
      delete newArgs.id;
      return userModel.updateById(id, newArgs);
    } catch (e) {
      logger.warn({ stack: e.stack, message: e.message });
      throw e;
    }
  },

schema case 2 :

extend type Mutation {

    updateCity(id: Int!, name: String, countryId: Int): City
}

resolver case 2 :

updateCity(obj, args, context, info) {
      logger.info('City > updateCity resolver');
      return cityController.city.update.city(obj, args, context, info);
    },

controller case 2 :

city: async (parent, args, context, info) => {
    try {
      logger.info('City Controller : update city');

      await controller.validate(args);
      const cityModel = new CityModel('city');
      if (!(await cityModel.findOne(args.id))) 
          errorscb.userInputError('City does not exist');
      let id = args.id;
      let newArgs = args;
      delete newArgs.id;
      return cityModel.updateById(id, newArgs);
    } catch (e) {
      logger.warn({ stack: e.stack, message: e.message });
      throw e;
    } 

I'd like to obtain a consistent output, anyone knows how to fix that ?

Upvotes: 3

Views: 1011

Answers (2)

Daniel Rearden
Daniel Rearden

Reputation: 84757

This is actually expected behavior.

The difference between updateUser and updateCity in your is that the latter returns a nullable type (City), while the former returns a non-null one (User!). The difference in the responses comes from the fact that errors propagate up the response until they hit a nullable field. From the spec:

If an error is thrown while resolving a field, it should be treated as though the field returned null, and an error must be added to the "errors" list in the response.

If the result of resolving a field is null (either because the function to resolve the field returned null or because an error occurred), and that field is of a Non-Null type, then a field error is thrown. The error must be added to the "errors" list in the response.

...

Since Non-Null type fields cannot be null, field errors are propagated to be handled by the parent field. If the parent field may be null then it resolves to null, otherwise if it is a Non-Null type, the field error is further propagated to it’s parent field.

In other words, by throwing an error during a field's resolution, we effectively resolve that field to null. But when we tell GraphQL a field has a Non-Null type, and that field resolves to null, GraphQL cannot return the field with a null value (because that would break the contract of the schema). So it makes the entire parent field null. If the parent field is also non-nullable, it nulls that field's parent field, and so on... until it either reaches a nullable field or the root of the request (the data field).

Compare: Schema 1

type Query {
  a: A
}

type A {
  b: B
}

type B {
  c: String
}

Schema 2

type Query {
  a: A
}

type A {
  b: B
}

type B {
  c: String!
}

Schema 3

type Query {
  a: A!
}

type A {
  b: B!
}

type B {
  c: String!
}

If we request the field c and the resolver for field c throws, the responses are as follows:

Schema 1

{
  "data": {
    "a": {
      "b": {
        "c": null
      }
    }
  }
}

Schema 2

{
  "data": {
    "a": {
      "b": null
    }
  }
}

Schema 3

{
  "data": null
}

Upvotes: 5

GiiiZz
GiiiZz

Reputation: 33

extend type Mutation {
    updateUser(userInfo: UserInfo!): User!
}

Found the fix, requiring User was unnecessary and removing the exclamation mark fixed my issue (eh... somehow)

updateUser(userInfo: UserInfo!): User!

Upvotes: 0

Related Questions