How to return an array of errors with graphQL - node.js

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 ?

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"
}
]
}

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.

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;
}
});
});

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

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 ));
}

Related

NodeJS String.replace() problem while filtering

I can't modify the filtering parameter with
String.replace()
I can get the filtering keys from the URL as an object but its badly fromatted from me.
Filtering: {{URL}}/api/v1/bootcamps?averageCost[lt]=10000
Current format: { averageCost: { lt: '10000' } }
Right fromat: { averageCost: { $lt: '10000' } }
So I tried to convert it as a String and replace that value. But that value can be: lt, lte, gt, gte, in but it has some problem because the line after the .replace() method doesnt executed and of course the catch block cathes the error...
My code snippet:
try {
console.log(req.query);
const queryStr = JSON.stringify(req.query);
console.log(queryStr); //thats the last thing I get
queryStr = queryStr.replace(
/\b(gt|gte|lt|lte|in)\b/g,
match => `$${match}`
);
console.log(queryStr); // I dont get this
const bootcamps = await Bootcamp.find();
res.status(200).json({
succes: true,
count: bootcamps.length,
data: bootcamps
});
} catch (err) {
return res.status(404).json({ succes: false });
}
To replace it correctly you should use something like this:
let queryStr = '{ "averageCost": { "lt": "10000" }, "test": { "gt": "12345"} }';
const regex = /\b(gt|gte|lt|lte|in)\b/g;
queryStr = queryStr.replace(regex, '$$' + "$1"); // <-- here is the correct replace
This will replace queryStr with:
{ "averageCost": { "$lt": "10000" }, "test": { "$gt": "12345"} }
JSFiddle https://jsfiddle.net/c52z8ewr/
If you need the object back just do JSON.parse(queryStr)
You can also Try This
const queryCpy = { ...this.query };
// console.log(queryCpy, 'before filter');
console.log(queryCpy, 'after filter');
let queryString = JSON.stringify(queryCpy);
queryString = queryString.replace(
/\b(gt|gte|lt|lte)\b/g,
(rep) => `$${rep}`,
);
console.log(queryString, 'after filter');
If want an object in return do:
const bootcamps = await Bootcamp.find(JSON.parse(queryStr));

Unable to write item(s) to DynamoDB table utilizing DocumentClient - Nodejs

I'm absolutely brand new to DynamoDb and I'm trying to simply write an object from a NodeJS Lambda. Based on what I've read and researched I should probably be using DocumentClient from the aws-sdk. I also found the following question here regarding issues with DocumentClient, but it doesn't seem to address my specific issue....which I can't really find/pinpoint unfortunately. I've set up a debugger to help with SAM local development, but it appears to be only providing some of the errors.
The code's implementation is shown here.
var params = {
TableName: "March-Madness-Teams",
Item: {
"Id": {"S": randstring.generate(9)},
"School":{"S": team_name},
"Seed": {"S": seed},
"ESPN_Id": {"S": espn_id}
}
}
console.log(JSON.stringify(params))
dynamodb.put(params, (error,data) => {
if (error) {
console.log("Error ", error)
} else {
console.log("Success! ", data)
}
})
Basically I'm scrubbing a website utilizing cheerio library and cherry picking values from the DOM and saving them into the json object shown below.
{
"TableName": "March-Madness-Teams",
"Item": {
"Id": {
"S": "ED311Oi3N"
},
"School": {
"S": "BAYLOR"
},
"Seed": {
"S": "1"
},
"ESPN_Id": {
"S": "239"
}
}
}
When I attempt to push this json object to Dynamo, I get errors says
Error MultipleValidationErrors: There were 2 validation errors:
* MissingRequiredParameter: Missing required key 'TableName' in params
* MissingRequiredParameter: Missing required key 'Item' in params
The above error is all good in well....I assume it didn't like the fact that I had wrapped those to keys in strings, so I removed the quotes and sent the following
{
TableName: "March-Madness-Teams",
Item: {
"Id": {
"S": "ED311Oi3N"
},
"School": {
"S": "BAYLOR"
},
"Seed": {
"S": "1"
},
"ESPN_Id": {
"S": "239"
}
}
}
However, when I do that...I kind of get nothing.
Here is a larger code snippet.
return new Promise((resolve,reject) => {
axios.get('http://www.espn.com/mens-college-basketball/bracketology')
.then(html => {
const dynamodb = new aws.DynamoDB.DocumentClient()
let $ = cheerio.load(html.data)
$('.region').each(async function(index, element){
var preregion = $(element).children('h3,b').text()
var region = preregion.substr(0, preregion.indexOf('(') - 1)
$(element).find('a').each(async function(index2, element2){
var seed = $(element2).siblings('span.rank').text()
if (seed.length > 2){
seed = $(element2).siblings('span.rank').text().substring(0, 2)
}
var espn_id = $(element2).attr('href').split('/').slice(-2)[0]
var team_name = $(element2).text()
var params = {
TableName: "March-Madness-Teams",
Item: {
"Id": randstring.generate(9),
"School":team_name,
"Seed": seed,
"ESPN_Id": espn_id
}
}
console.log(JSON.stringify(params))
// dynamodb.put(params)
// .then(function(data) {
// console.log(`Success`, data)
// })
})
})
})
})
Can you try without the type?
Instead of
"School":{"S": team_name},
for example, use
"School": team_name,
From your code, I can see the mis promise on the dynamodb request. Try to change your lines :
dynamodb.put(params).then(function(data) {
console.log(`Success`, data)
})
to be :
dynamodb.put(params).promise().then(function(data) {
console.log(`Success`, data)
})
you can combine with await too :
await dynamodb.put(params).promise().then(function(data) {
console.log(`Success`, data)
})
exports.lambdaHandler = async (event, context) => {
const html = await axios.get('http://www.espn.com/mens-college-basketball/bracketology')
let $ = cheerio.load(html.data)
const schools = buildCompleteSchoolObject(html, $)
try {
await writeSchoolsToDynamo(schools)
return { statusCode: 200 }
} catch (error) {
return { statusCode: 400, message: error.message }
}
}
const writeSchoolsToDynamo = async (schools) => {
const promises = schools.map(async school => {
await dynamodb.put(school).promise()
})
await Promise.all(promises)
}
const buildCompleteSchoolObject = (html, $) => {
const schools = []
$('.region').each(loopThroughSubRegions(schools, $))
return schools
}
const loopThroughSubRegions = (schools, $) => {
return (index, element) => {
var preregion = $(element).children('h3,b').text()
var region = preregion.substr(0, preregion.indexOf('(') - 1)
$(element).find('a').each(populateSchoolObjects(schools, $))
}
}
const populateSchoolObjects = (schools, $) => {
return (index, element) => {
var seed = $(element).siblings('span.rank').text()
if (seed.length > 2) {
seed = $(element).siblings('span.rank').text().substring(0, 2)
}
var espn_id = $(element).attr('href').split('/').slice(-2)[0]
var team_name = $(element).text()
schools.push({
TableName: "March-Madness-Teams",
Item: {
"Id": randstring.generate(9),
"School": team_name,
"Seed": seed,
"ESPN_Id": espn_id
}
})
}
}
I know this is drastically different from what I started with but I did some more digging and kind of kind of worked to this...I'm not sure if this is the best way, but I seemed to get it to work...Let me know if something should change!
Oh I understand what you want.
Maybe you can see the code above works, but there is one concept you have to improve here about async - await and promise especially on lambda function.
I have some notes here from your code above, maybe can be your consideration to improve your lambda :
Using await for every promise in lambda is not the best approach because we know the lambda time limitation. But sometimes we can do that for other case.
Maybe you can change the dynamodb.put method to be dynamodb.batchWriteItem :
The BatchWriteItem operation puts or deletes multiple items in one or more tables.
Or If you have to use dynamodb.put instead, try to get improve the code to be like so :
const writeSchoolsToDynamo = async (schools) => {
const promises = schools.map(school => {
dynamodb.put(school).promise()
})
return Promise.all(promises)
}

Order of function execution in Node async/await

I have a doubt in making sure the functions run in sequence and the result from the first call is used in second call.
DB Function
async runquery(){
try {
...
const results = await db.statementExecPromisified(statement, []);
return results;
} catch (e) {
console.log("Error - " +JSON.stringify(e));
return e;
}
}
Group id
async function groupByID(approvers) {
const group = _.groupBy(approvers, 'ID');
return Object.keys(group).map(ID=> {
return group[ID].reduce((approvers, cur, idx) => ({
...approvers,
['NAME' + (idx + 1)]: cur.ID,
}), { ID});
})
Final Function
async function preparePayload() {
if(levels.length != 0 ){
let statement= `SELECT * FROM ITEMS `
list = await runquery(statement) ;
id = await groupByID(list) ;
}
let result={}
result.ID=id;
}
Output from DB : [{ID:1,NAME:'F1'},{ID:2,NAME:'F2'},{ID:1,NAME:'F3'}]
Expected Output : [{ID:1,NAME1:'F1',NAME2:'F2'},{ID:2,NAME:'F2'}]
But the output is not coming as expected.
[{
"ID": "2"
},
{
"ID": "1"
}]
I am guessing this is due to sequence of the function execution because when i do console.log(id) works as expected but when i assign to the result variable it gives unexpected output.
I am not sure if I put the question correctly .Kindly let me know if it needs to be more detail.
I don't see anything wrong with your code apart from two small changes
groupByID doesn't need to be async and hence don't need to be awaited
['NAME' + (idx + 1)]: cur.ID, needs to be changed to ['NAME' + (idx + 1)]: cur.NAME,
For simplicity I have removed the db call and replaced it with a promise which returns the result which you have mentioned in your post.
See the working code here - https://repl.it/repls/PepperyGrossForms
It gives me this result
{
ID: [ { ID: '1', NAME1: 'F1', NAME2: 'F3' }, { ID: '2', NAME1: 'F2' } ]
}
Hope this helps.

can't use a member function inside fetch call

I get a response from a fetch call and i'm trying to check if the respond correspond to a kind of error my server is likely to respond me if the username or the email sent already exist in my database. If this is the case, i try to use a member function of my class to show a error message to the user. But when i try to use my member function i get the error:
Unhandled Rejection (TypeError): Cannot read property 'showValidationErr' of undefined.
I think that i can't use a member function in that case, how can i call my member function when i catch a error from my server ?
class RegisterBox extends React.Component {
constructor(props) {
super(props);
this.state = {username: "",
email: "",
password: "",
confirmPassword: "",
errors: [],
pwdState: null};
}
showValidationErr(elm, msg) {
this.setState((prevState) => ( {errors: [...prevState.errors, {elm, msg} ] } ) );
}
submitRegister(e) {
fetch("http://localhost:8080/register?user=" + this.state.username + "&psw=" + this.state.password + "&email=" + this.state.email)
.then(function (respond) {
return respond.text()
.then(function(text) {
if (text === "RegisterError : username") {
this.showValidationErr("username", "username allready taken.");
}
if (text === "RegisterError : email") {
this.showValidationErr("email", "email allready used.");
}
})
});
}
}
I found how to correct it. i needed to add the line "var that = this;" at the beginning of my function and use "that.showValidationErr();" instead.
In this context, this refers to the anonymous callback function() that you're passing to the then(). Try using the arrow function syntax instead which keeps the this as the surrounding context.
.then((respond) => {
return respond.text()
.then((text) => {
if (text === "RegisterError : username") {
this.showValidationErr("username", "username allready taken.");
}
if (text === "RegisterError : email") {
this.showValidationErr("email", "email allready used.");
}
})

How to return grpc error in nodejs

I want to return grpc error code and description in server-side. I have tried this
function sayHello(call, callback) {
callback({error: {code: 400, message: "invalid input"});
}
but I got this exception from client
{ Error: Unknown Error
at /home/thanh/email/node_modules/grpc/src/node/src/client.js:434:17 code: 2, metadata: Metadata { _internal_repr: {} } }
If I don't want to include error field in message definition like this.
message Hello {
string name = 1;
string error = 2; // don't want this
}
Then what is the proper way to send grpc error back to client ?
Change it to:
return callback({
code: 400,
message: "invalid input",
status: grpc.status.INTERNAL
})
As a supplement, GRPC only allowed 16 kinds of error. You can check all error code from its site: https://grpc.io/docs/guides/error/#error-status-codes.
And here I found an example code for NodeJs error handling: https://github.com/avinassh/grpc-errors/blob/master/node/server.js
To clarify what #avi and #murgatroid99 have said what you want to do is structure you callback like so:
import * as grpc from '#grpc/grpc-js';
try{
somethingThatThrowsAnError();
}catch(e){
return callback(
{
message: e ,
code: grpc.status.NOT_FOUND
},
null,
)
}
grpc.status.NOT_FOUND is just a integer, 5, and when the client gets an error response from the server you can read it off the err prop returned e.g.
const client = new MyServiceConstructor(
address,
grpc.credentials.createInsecure()
);
client.myMethod(
myRequest,
metadata ?? new grpc.Metadata(),
(error: string, response: T_RESPONSE_TYPE) => {
if (error) {
if(error.code === grpc.status.NOT_FOUND) {
return handleNotFound(error, myRequest)
}
return unknownError(error, myRequest)
}
return response
},
);

Resources