Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot set currently-valued fields to null in update mutation #129

Open
zopf opened this issue Jul 29, 2016 · 2 comments
Open

Cannot set currently-valued fields to null in update mutation #129

zopf opened this issue Jul 29, 2016 · 2 comments

Comments

@zopf
Copy link
Contributor

zopf commented Jul 29, 2016

Overview of the Issue

I cannot easily update a field on an object that currently has a value to be null.

It seems this is because the graphql-js library is stripping null variables that get passed to it, since the GraphQL spec currently does not have a concept of a null field value. Please see graphql/graphql-js#133 and graphql/graphql-spec#83 for further information and hand-wringing.

Reproduce the Error

For example, if I have a model named User, and User has a field firstName, if I run an addUser mutation with firstName set to "Alex", but then want to set firstName on that same object to be null, I cannot.

I can try to send the following update mutation:

mutation updateMyUser($input: updateUserInput!) {
  updateUser(input: $input) {
    changedUser {
      firstName
    }
    clientMutationId
  }
}

with variables:

{
  "input": {
    "clientMutationId": "justTesting",
    "firstName": null,
    "id": "VXNlcjo1NzMzNDA4MDMwM2EzMTk4M2ExZjNmNGI="
  }
}

... but when I do, I will still receive:

{
  "data": {
    "updateUser": {
      "changedUser": {
        "firstName": "Alex"
      },
      "clientMutationId": "justTesting"
    }
  }
}

Suggest a Fix

As suggested in graphql/graphql-js#133 (comment), it seems like adding a deletions field is a viable possibility to work around the fact that the GraphQL folks seem very hesitant to implement a native null value. I'm working on that myself right now in the https://github.com/wellth-app/graffiti-mongoose fork (which has by now diverged quite a bit).

I'm also tossing around the idea of creating my own pre-graffiti middleware that notes all null values in the variables tree and adds their path to a top-level deletions array on the variables tree, so that our existing clients don't have to implement the new deletions field.

@zopf zopf changed the title Setting currently-valued fields to null in update mutation Setting currently-valued fields to null in update mutation does not work Jul 29, 2016
@zopf zopf changed the title Setting currently-valued fields to null in update mutation does not work Cannot set currently-valued fields to null in update mutation Jul 29, 2016
@zopf
Copy link
Contributor Author

zopf commented Aug 2, 2016

@zopf
Copy link
Contributor Author

zopf commented Aug 2, 2016

... and then I made this middleware that I can drop into my Koa stack to grab null variables from things that look like mutations and add their paths to the deletions argument:

function getPathsToNull(tree, parentPath) {
  const nullPaths = [];
  for (const key in tree) {
    if (tree[key] === null) {
      // add to null paths
      nullPaths.push(
        parentPath ?
          [parentPath, key].join('.') :
          key
      );
    } else if (tree[key] instanceof Object) {
      // descend
      nullPaths.push.apply(nullPaths, getPathsToNull(
        tree[key],
        parentPath ?
          [parentPath, key].join('.') :
          key
      ));
    }
  }
  return nullPaths;
}

function * handleNullVariables(next) {
  const body = this.request.body;
  const { query, variables } = Object.assign({}, body, this.query);
  // TODO: properly parse the full query AST to determine if actually an add/update mutation
  if (query && /mutation[\s\S]*{\s*(update|add)[a-zA-Z_]+\s*\(/i.test(query)) {
    const parsedVariables = (typeof variables === 'string' && variables.length > 0) ?
          JSON.parse(variables) : variables;

    for (const key in parsedVariables) {
      if ({}.hasOwnProperty.call(parsedVariables, key)) {
        const deletions = getPathsToNull(parsedVariables[key]);
        if (deletions && parsedVariables[key] !== null) {
          parsedVariables[key].deletions = deletions;
        }
      }
    }

    const correctedVariables = JSON.stringify(parsedVariables);
    // set both GET and POST vars
    this.request.body.variables = correctedVariables;
    this.query.variables = correctedVariables;
  }
  yield next;
}

export default handleNullVariables;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant