I'm fetching a query to a GraphQL server that throws an error if something goes wrong. Now, I want to have access to that error but instead Apollo Client is showing me the general Error: Network error: Response not successful: Received status code 500.
By adding the onError property to my query, I can see the Error object in the console but only if it's wrapped in {}, otherwise it's just text.
This is my code:
const { data, loadMore, loading, error, retry } = useRetryableQuery<
GetDevices,
DevicesQueryVariables
>(devicesQueries.GetDevices, {
onError: (networkError) => {
console.log(networkError); //this will show me the error as a whole text and not an object
console.log({ networkError }) // this will show me an object which its properties I cannot access
},
errorPolicy: "all",
notifyOnNetworkStatusChange: true,
paginationPath: ["paging"],
itemsPaths: [["filteredDevices", "rows"]],
variables: {
...queryVariables,
connectionPaging: {
offset: 0,
limit: PAGE_SIZE,
},
},
});
How can I access the properties nested inside the networkError object?
First screenshot is without {}
Second one is with {}
Thanks in advance!
The issue was related to this Apollo Client issue: useQuery() refetch throws on error instead of flowing through hook, so getting the error from the ApolloProvider directly allowed me to access the error sent from the backend:
const errorLink = useMemo(
() =>
onError((error) => {
const message = getApolloError({ graphQLErrors: error.graphQLErrors
});
if (message) {
localStorage.setItem("error", message);
} else {
localStorage.removeItem("error");
}
if (message === "Session expired.") {
logout(true);
}
}),
[logout]
);
const client = useMemo(
() =>
new ApolloClient({
cache,
link: ApolloLink.from([errorLink, authLink, httpLink]),
defaultOptions: {
watchQuery: {
fetchPolicy: "network-only",
},
query: {
fetchPolicy: "network-only",
},
},
}),
[httpLink, errorLink]
);
Related
I need your help to mock a twilio service which sends a message, using jest to mock the service
I have the next code:
import { SQSEvent } from "aws-lambda";
import { GetSecretValueResponse } from "aws-sdk/clients/secretsmanager";
export async function sendSms(event: SQSEvent, data: GetSecretValueResponse) {
const secrets = JSON.parse(data.SecretString);
const accountSid = secrets.TWILIO_ACCOUNT_SID;
const authToken = secrets.TWILIO_AUTH_TOKEN;
const twilioNumber = secrets.TWILIO_PHONE_NUMBER;
if (accountSid && authToken && twilioNumber) {
//Create a Twilio Client
const client = new Twilio(accountSid, authToken);
//Loop into al records of the event, every record is every message sent from Sqs
for (const record of event.Records) {
const body = JSON.parse(record.body);
const userNumber = "+" + body.number;
//SendMessage function
try {
const message = client.messages.create({
from: twilioNumber,
to: userNumber,
body: body.message,
});
return message;
} catch (error) {
return `Failed to send sms message. Error Code: ${error.errorCode} / Error Message: ${error.errorMessage}`;
}
}
} else {
return "You are missing one of the variables you need to send a message";
}
}
The I call this function from my index:
import { SQSEvent } from "aws-lambda";
import { sendSms } from "./services/sendSms/sendSms";
import { getSecret } from "./services/obtainSecrets/getSecret";
import { SecretsManager } from "aws-sdk";
export const lambdaHandler = async (event: SQSEvent) => {
try {
const obtainedSecret = await getSecret()
.then((credentials: SecretsManager.GetSecretValueResponse) => {
return credentials;
})
.catch(error => {
return error;
});
const response = sendSms(event, obtainedSecret)
.then(response => {
return response;
})
.catch(error => {
return error;
});
return {
message: "OK " + obtainedSecret + response,
code: 200,
};
} catch (error) {
throw new Error(error);
}
};
I have already make some tests, but them always makes a connection with Twilio api(requiring the real token, sid,etc), and I need to mock the Twilio service, so the function I call in my test.ts doesn't connects to internet.
import { Twilio } from "twilio";
import { MessageInstance } from "twilio/lib/rest/api/v2010/account/message";
import { sendSms } from "../../services/sendSms/sendSms";
//mock Twilio library and sendSms service
jest.mock("twilio");
jest.mock("../../services/sendSms/sendSms");
const smsMessageResultMock: Partial<MessageInstance> = {
status: "sent",
sid: "AC-lorem-ipsum",
errorCode: undefined,
errorMessage: undefined,
};
describe("SMS Service", () => {
describe("Send Message", () => {
it("Should fail", async () => {
// update smsMessageResultMock to simulate a faled response
const smsMessageMock = {
...smsMessageResultMock,
status: "failed",
errorCode: 123,
errorMessage: "lorem-ipsum",
};
// simulated response of secret management
let data = {
ARN: "arn:aws:secretsmanager:us-west-2:123456789012:secret:MyTestDatabaseSecret-a1b2c3",
Name: "MyTestDatabaseSecret",
SecretString:
'{"TWILIO_ACCOUNT_SID": "ACTWILIO_ACCOUNT_SID","TWILIO_AUTH_TOKEN": "TWILIO_AUTH_TOKEN","TWILIO_PHONE_NUMBER": "TWILIO_PHONE_NUMBER"}',
VersionId: "EXAMPLE1-90ab-cdef-fedc-ba987SECRET1",
VersionStages: ["AWSPREVIOUS"],
};
// simulated response of SqsEvent
let event = {
Records: [
{
messageId: "19dd0b57-b21e-4ac1-bd88-01bbb068cb78",
receiptHandle: "MessageReceiptHandle",
body: '{"message": "Hello world","number": "(506)88888888"}',
attributes: {
ApproximateReceiveCount: "1",
SentTimestamp: "1523232000000",
SenderId: "123456789012",
ApproximateFirstReceiveTimestamp: "1523232000001",
},
messageAttributes: {},
md5OfBody: "{{{md5_of_body}}}",
eventSource: "aws:sqs",
eventSourceARN: "arn:aws:sqs:us-east-1:123456789012:MyQueue",
awsRegion: "us-east-1",
},
],
};
// simulate tokens for Twilio
const accountSid = "ACfjhdskjfhdsiuy876hfijhfiudsh";
const authToken = "fjfuewfiuewfbodfiudfgifasdsad";
//create client with mocked Twilio
const client = new Twilio(accountSid, authToken);
//call messages.create of Twilio client, and give it the expected result created
client.messages.create = jest
.fn()
.mockResolvedValue({ ...smsMessageMock });
console.log(await sendSms(event, data));
//expectes the function sendSms(event, data) to throw an error
await expect(sendSms(event, data)).rejects.toThrowError(
`Failed to send sms message. Error Code: ${smsMessageMock.errorCode} / Error Message: ${smsMessageMock.errorMessage}`
);
});
});
});
(event and data are simulated responses of SqsEvent and GetSecretValueResponse)
The problem is that when I run the npm test it throws me an error of Twilio's authentication, an it is because I'm passing self created tokens.
Expected substring: "Failed to send sms message. Error Code: 123 / Error Message: lorem-ipsum"
Received message: "Authentication Error - invalid username"
at success (node_modules/twilio/lib/base/Version.js:135:15)
at Promise_then_fulfilled (node_modules/q/q.js:766:44)
at Promise_done_fulfilled (node_modules/q/q.js:835:31)
at Fulfilled_dispatch [as dispatch] (node_modules/q/q.js:1229:9)
at Pending_become_eachMessage_task (node_modules/q/q.js:1369:30)
at RawTask.Object.<anonymous>.RawTask.call (node_modules/asap/asap.js:40:19)
at flush (node_modules/asap/raw.js:50:29)
So what I suppose is that the test is connecting to internet and calling Twilio's api.
I appreciate if you could help me.
I think what you want to do is mock the class returned by the module, using jest.mock('twilio', mockImplementation) and in mockImplementation return a function to act as a constructor that will take your account SID and auth token arguments and then return a mockClient implementation, which in this case needs to return an object which has a messages property, which in turn is an object with a create property that is a mock function.
It's probably easier to just show the code.
const mockClient = {
messages: {
create: jest.fn().mockResolvedValue({ ...smsMessageMock });
}
};
jest.mock("twilio", () => {
return function(accountSid, authToken) {
return mockClient;
}
});
I'm working in TypeScript with the KafkaJS library locally, with a single kafka broker. I've connected a producer successfully, have verified that my topic was created, and am generating messages with:
const changeMessage = {
key: id,
value: JSON.stringify(person),
headers: {
changeType: status,
},
};
Now when I go to send the message:
try {
const sendResponse = await producer.send({
topic: topicName2,
messages: [changeMessage],
});
log.responseFragment(
{ id, topicName2 },
`Sending changed/added person ${id} to topic ${topicName2}`
);
} catch (error) {
log.error(
{ error }, `Could not send personChangedAdded ${id} to topic ${topicName2}`
);
}
Here's the error that I get back:
Could not send personChange to topic topicName2
error: {
"name": "KafkaJSError",
"retriable": true
}
I had failed to define log.responseFragment(), and when I changed it to a simple log.info() the problem was resolved.
The component renders the error state just fine, but the exception is displayed as uncaught in the console and a dialogue is displayed in next dev on the browser. Is there a way to handle expected errors to squelch this behavior?
import { useMutation, gql } from "#apollo/client";
import { useEffect } from "react";
const CONSUME_MAGIC_LINK = gql`
mutation ConsumeMagicLink($token: String!) {
consumeMagicLink(token: $token) {
token
member {
id
}
}
}
`;
export default function ConsumeMagicLink({ token }) {
const [consumeMagicLink, { data, loading, error }] =
useMutation(CONSUME_MAGIC_LINK);
console.log("DATA", data, "loading:", loading, "error:", error);
useEffect(() => {
try {
consumeMagicLink({ variables: { token } });
} catch (e) {
console.log(e);
}
}, []);
var text = "Link has expired or has been used previously";
if (data) text = "SUCCESS: REDIRECTING";
if (loading) text = "Processing";
if (error) text = "Link has expired or has been used previously";
return (
<div>
<h2>{text}</h2>
</div>
);
}
Console Results:
Error Displayed in Browser:
The error is from the client instead of the mutation so your try-catch cannot catch it. To handle this you can add the error handling to the client, for example:
const errorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.forEach(({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
),
);
if (networkError) console.log(`[Network error]: ${networkError}`);
});
const httpLink = new HttpLink({
uri: "some invalid link"
});
const client = new ApolloClient({
link:from([httpLink,errorLink]),
cache: new InMemoryCache()
})
And as you got authorization error, I suggest you check your headers.
You can find additional information and examples of this approach here:
enter link description here
I have the following useEffect call back function :-
import { initDB, useIndexedDB } from "react-indexed-db";
initDB(DBConfig);
const db = useIndexedDB("reads");
useEffect(() => {
db.getByIndex("hash", props.match.params.id).then((data) => {
setToken(data.token);
});
handleTextColorSelection(backgroundColor);
axios
.post(`${process.env.REACT_APP_API_URL}khatma/read`, {
khatma: props.match.params.id,
part: props.match.params.part,
section: props.match.params.section,
token: token,
})
.then((res) => {
if (res.data.token) {
db.add({
hash: props.match.params.id,
token: res.data.token,
}).then(
(event) => {},
(error) => {
console.log(error);
}
);
}
})
In axios post body i am sending token , if "token" state receive a value stored before in browser indexed db, if there is not object store data found , then a post token will be sent as null.
The problem here, that i noticed that before the db.getByIndex("hash", ... command get a result, an axios request run, and sending a token as null, even-though later on token state will get a value.
How can i run the db.getByIndex("hash", then if it finish , run axios post request ?
You could use .then
const axiosRequest = () => { /* your axios logic */ };
db.getByIndex(...).then(axiosRequest).catch(axiosRequest)
Anything you do inside the then and catch callbacks will be run after the getByIndex call
I was starting with GraphQL and I was unable to comprehend how we can throw errors in GraphQL
I went through a couple of articles on the web but almost all of them use Apollo and the code-structure looks very different than how I work.
Consider this piece of code, here where I am making a mutation, now how can send a response message with error and change headers status message in case of error?
AddNewPersonalInfo: {
type: userDashboardType,
args: {
parameter: {
type: userCreationlInputType
}
},
resolve: async (parent, args, context) => {
args.parameter.userId = context.req.headers.userId
//Check if user info already exsist
const checkIfUserInformationExsist = await getSelectedThingFromTable('CatsWork_personal', 'userId', `${userId}`)
if (checkIfUserInformationExsist[0]) {
const error = {
code: 403,
message: 'User info Already exsist'
}
throw new Error(error)
} else {
try {
const addLinkedinUser = await insertIntheTable('personal', payload)
return true
} catch (err) {
console.error(err)
throw new Error(err)
}
}
}
}
What I have faced in one of my projects, it is hard to set the status code of the response. So, I made some custom error response to identify correct statusCode using express-graphql
Below is the example (What I have used in one of my projects):
--------app.js file--------
const graphqlHTTP = require('express-graphql')
app.use('/graphql', (req, res) => {
graphqlHTTP({
schema: GraphQLSchema, //A GraphQLSchema instance from GraphQL.js. A schema must be provided.
graphiql: true,
context: { req },
formatError: (err) => {
const error = getErrorCode(err.message)
return ({ message: error.message, statusCode: error.statusCode })
}
})(req, res)
})
--------getErrorCode function implementation--------
const { errorType } = require('../constants')
const getErrorCode = errorName => {
return errorType[errorName]
}
module.exports = getErrorCode
--------Constant.js file--------
exports.errorName = {
USER_ALREADY_EXISTS: 'USER_ALREADY_EXISTS',
SERVER_ERROR: 'SERVER_ERROR'
}
exports.errorType = {
USER_ALREADY_EXISTS: {
message: 'User is already exists.',
statusCode: 403
},
SERVER_ERROR: {
message: 'Server error.',
statusCode: 500
}
}
Now, we are ready to use our setup.
From your query or mutation, you need to require constant file and return custom error:
const { errorName } = require('../constant')
AddNewPersonalInfo: {
type: userDashboardType,
args: {
parameter: {
type: userCreationlInputType
}
},
resolve: async (parent, args, context) => {
args.parameter.userId = context.req.headers.userId
//Check if user info already exsist
const checkIfUserInformationExsist = await getSelectedThingFromTable('CatsWork_personal', 'userId', `${userId}`)
if (checkIfUserInformationExsist[0]) {
const error = {
code: 403,
message: 'User info Already exsist'
}
throw new Error(errorName.USER_ALREADY_EXISTS) // Here you can use error from constatnt file
} else {
try {
const addLinkedinUser = await insertIntheTable('personal', payload)
return true
} catch (err) {
console.error(err)
throw new Error(errorName.SERVER_ERROR) // Here you can use error from constatnt file
}
}
}
}
--------Error response--------
{
error: [{
"statusCode": 403,
"message": "User is already exists."
}],
data: null
}
We just need to write custom error handling from FS side too.
Note:- formatError: is deprecated and replaced by customFormatErrorFn. It will be removed in version 1.0.0. You can refer customFormatErrorFn.
graphql should be an application level layer that shouldn't (see last paragraph why shouldn't and not doesn't) require http to work. Although in 99% of cases it runs on top of http, because of how convenient it is to do so, graphql is itself a layer 7 protocol.
What does that mean in your case? Well, it means you should not mix concepts from HTTP/REST with concepts from graphql and focus on the latter. The headers error code is a HTTP/REST concept, graphql sends errors in the errors field of the response and the nodejs implementation already catches all your errors and adds them to the list. The HTTP status will be always 200, and your clients shouldn't care and consume your graphql api and not a mix of REST with graphql.
That being said, there are couple of things that REST over HTTP does better. So people, including the developers of Apollo, kinda mixed concepts too, mainly because the graphql standard is not complete (aka, it doesn't have a standard/rule for solving all the problems you might encounter while building an API), so people improvised. I wouldn't recommend graphql yet for any serious project.
Reference
You can specify an error function inside graphqlHTTP like this:
app.use("/graphql", graphqlHTTP({
schema,
graphiql: true,
customFormatErrorFn: err => {
try {
err.details = JSON.parse(err.message);
err.message = Array.isArray(err.details.error) ? err.details.error.join(",") : err.details.error;
return err;
} catch {
return err;
}
}
}));
where err.message might contain a JSON object or a string.
you can use those function to generate specific client and server error functions:
const clientError = error => new Error(JSON.stringify({
success: false,
code: 400,
error
}));
const serverError = ({ name, message, stack }) => new Error(JSON.stringify({
success: false,
error: "Server Error",
code: 500,
name,
message,
stack
}));
const userValidationError = err => {
if (err.name === "ValidationError") return clientError(Object.values(err.errors).map(({ message }) => message));
return serverError(err);
}
module.exports = {
clientError,
serverError,
userValidationError
};
userValidationError function is useful if you have a mongodb validation error.
so that you would use it inside resolve function like this:
try {
const createdDocument = await MongooseDoc.create(data);
return createdDocument;
} catch (err) {
throw userValidationError(err);
}
the response would be
{
"errors": [
{
"message": "error details 1,error details 2",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"document"
],
"details": {
"success": false,
"code": 400,
"error": [
"error details 1",
"error details 2"
]
}
}
],
"data": {
"document": null
}
}
if you want to throw a clientError you throw it outside try catch.
Hopefully this code helps someone send dynamic error messages in graphql.