Lev
Lev

Reputation: 15734

How to return an array of errors with graphQL

How can I return multiple error messages like this ?

"errors": [
  {
    "message": "first error",
    "locations": [
      {
        "line": 2,
        "column": 3
      }
    ],
    "path": [
      "somePath"
    ]
  },
  {
    "message": "second error",
    "locations": [
      {
        "line": 8,
        "column": 9
      }
    ],
    "path": [
      "somePath"
    ]
  },
]

On my server, if I do throw('an error'), it returns.

"errors": [
  {
    "message": "an error",
    "locations": [
      {
      }
    ],
    "path": ["somePath"]
  }
]

I would like to return an array of all the errors in the query. How can I add multiple errors to the errors array ?

Upvotes: 13

Views: 7844

Answers (5)

Richard Scarrott
Richard Scarrott

Reputation: 7073

Using ApolloServer I've found multiple errors will be returned when querying an array of items and an optional field's resolver errors.

// Schema
gql`
  type Foo {
    id: ID!
    bar: String # Optional
  }

  type Query {
    foos: [Foo!]!
  }
`;

// Resolvers
const resolvers = {
  Query: {
    foos: () => [{ id: 1 }, { id: 2 }]
  }
  Foo: {
    bar: (foo) => {
       throw new Error(`Failed to get Foo.bar: ${foo.id}`);
    }
  }
}

// Query
gql`
  query Foos {
    foos {
      id
      bar
    }
  }
`;

// Response
{
  "data": {
    "foos": [{ id: 1, bar: null }, { id: 2, bar: null }]
  },
  "errors": [{
    "message": "Failed to get Foo.bar: 1"
  }, {
    "message": "Failed to get Foo.bar: 2"
  }]
}

If Foo.bar is not optional, it will return just the first error.

If you want to return many errors, at once, I would recommend MultiError from VError which allows you to represent many errors in one error instance.

Upvotes: 1

Ravi
Ravi

Reputation: 598

Throw an error object with errors:[] in it. The errors array should have all the errors you wanted to throw together. Use the formatError function to format the error output. In the below example, I am using Apollo UserInputError. You can use GraphQLError as well. It doesn't matter.

const error = new UserInputError()
error.errors = errorslist.map((i) => {
  const _error = new UserInputError()
  _error.path = i.path
  _error.message = i.type
  return _error
})
throw error

new ApolloServer({
  typeDefs,
  resolvers,
  formatError: ({ message, path }) => ({
    message,
    path,
  }),
})

//sample output response
{
  "data": {
    "createUser": null
  },
  "errors": [
    {
      "message": "format",
      "path": "username"
    },
    {
      "message": "min",
      "path": "phone"
    }
  ]
}

Upvotes: 1

Marco Daniel
Marco Daniel

Reputation: 5765

You can use the GraphQL Error Function, I have a example with TypeScript:

function throwError(message: string, path: any) {
    throw new GraphQLError(
        message,
        [],
        {body: '', name: ''},
        undefined,
        [path]
    )
}

And then I just call the function as many times as needed.

The JavaScript constructor looks like:

constructor(
    message: string,
    nodes?: $ReadOnlyArray<ASTNode> | ASTNode | void,
    source?: ?Source,
    positions?: ?$ReadOnlyArray<number>,
    path?: ?$ReadOnlyArray<string | number>,
    originalError?: ?Error,
    extensions?: ?{ [key: string]: mixed },
): void;

Check the graphql-js gitHub:

https://github.com/graphql/graphql-js/blob/master/src/error/GraphQLError.js#L22

Upvotes: 0

Thiago Mata
Thiago Mata

Reputation: 2959

Looks like the question it is not about to show many exceptions but about to show all the stack trace of the error. When one error is thrown up, the execution will not receive or throw up other error. In some languages, you can nativally set the parent exception to the current exception, but it is not the case of javascript, so far I can tell and looking to the docs https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error and https://nodejs.org/api/errors.html#errors_error_propagation_and_interception. You will need to create your own error class, what it is not that hard.

If the problem it is show trace

The stack trace in Javascript it is a string! What it is good if you just want to put it into some log but bad if you want to make a more meaningful reading structure, like a json.

If what you want to do it is really show the stack trace, probably you are going to need to convert the stack trace of the Error object into an array, using something like this: https://github.com/stacktracejs/error-stack-parser and then put this array inside of your error object.

After that, you can just save that object into your database. You still will be watching just one error, but you are going to have all the "location", "line", "path" of it trace, that sounds to me what you are looking for.

If the problem it is to show the parent errors message and trace

If you want to keep the parent Error of some trace, you will probably need to create your own error class.

   /**
    * Class MyError extends Error but add the parentError attribute
    */ 
   function MyError(message, parentError ) { 
      this.message = message; 
      this.stack = Error().stack; 
      this.parentError = parentError;
    }

    MyError.prototype = Object.create(Error.prototype); 
    MyError.prototype.name = "MyError";

    function a() {
      b();
    }

    function b() {
      try {
        c();
      } catch ( e ) {
        throw new MyError( "error on b", e );
      }
    }

    function c() {
      d();
    }

    function d() {
      throw new MyError("error on d");
    }

    function showError( e ) {
      var message = e.message + " " + e.stack;
      if ( e.parentError ) {
        return message + "\n" + showError( e.parentError );
      }
      return message;
    }

    try{
      a();  
    } catch ( e ) {
      console.log(showError( e ));
    }

If the problem it is show many errors messages and trace

If you want to keep many errors into a big package, for validation feedback, for example, you may extend the error class to create a package of errors. I created one simple example of each one of this classes.

   /**
    * Class MyErrorPackage extends Error 
    * but works like a error package
    */ 
    function MyErrorPackage(message, parentError ) { 
      this.packageErrors = [];
      this.message = "This package has errors. \n"; 
      this.isValid = true;
      this.stack = Error().stack; 
      this.parentError = parentError;
      this.addError = function addError( error ) {
         this.packageErrors.push( error );
         this.isValid = false;
         this.message += "PackageError(" + this.packageErrors.length + "): " + error.stack + error.stack + "\n";
         
      };
      this.validate = function validate() {
         if( ! this.isValid ) {
           throw this;
         }
      };
    }

    MyErrorPackage.prototype = Object.create(Error.prototype); 
    MyErrorPackage.prototype.name = "MyErrorPackage";
    
    function showError( e ) {
      var message = e.message + " " + e.stack;
      if ( e.parentError ) {
        return message + "\n" + showError( e.parentError );
      }
      return message;
    }

    function showPackageError( e ) {
      var message = e.message + " " + e.stack;
      if ( e.parentError ) {
        return message + "\n" + showError( e.parentError );
      }
      return message;
    }


    try{
      var p = new MyErrorPackage();
      try {
         throw new Error("error 1");
      } catch( e1 ) {
          p.addError(e1);
      }
      try {
         throw new Error("error 2");
      } catch( e2 ) {
          p.addError(e2);
      }
      try {
         throw new Error("error 3");
      } catch( e3 ) {
          p.addError(e3);
      }
      p.validate();
    } catch ( e4 ) {
      console.log(showError( e4 ));
    }

Upvotes: -1

Matt Greenberg
Matt Greenberg

Reputation: 2330

You would need to catch the errors without the throw statement because you don't want to interrupt your process. Instead, you can create an array called errors and .push() the errors into it. When you see fit, near the end of your process, you can check to see if there are errors inside the errors array. If there are, you can display them or handle them as you wish

// example
var errors = [];

doSomething(function(err,res){

    if(err){
        errors.push(err);
    }
    console.log("we did a thing");
    doSomethingElse(function(err,res2){

         if(err){
              errors.push(err);
         };
         console.log("we did another thing");

         // check and throw errors
         if(errors.length > 0){
             throw errors;
         }



    });

});

Upvotes: 0

Related Questions