In web app development I would like a consistent way to catch and report error conditions. For example, a database update routine may detect a variety of error conditions and ideally I would like the application to capture them and report gracefully. The code below din't work because retdiag is undefined when error is thrown...
function saveData(app,e) {
var db ;
var retdiag = "";
var lock = LockService.getPublicLock();
lock.waitLock(30000);
try {
// e.parameters has all the data fields from form
// re obtain the data to be updated
db = databaseLib.getDb();
var result = db.query({table: 'serviceUser',"idx":e.parameter.id});
if (result.getSize() !== 1) {
throw ("DB error - service user " + e.parameter.id);
}
//should be one & only one
retdiag = 'Save Data Finished Ok';
}
catch (ex) {
retdiag= ex.message // undefined!
}
finally {
lock.releaseLock();
return retdiag;
}
}
Is there a good or best practice for this is GAS?
To have a full error object, with message and stacktrace you have to build one, and not just throw a string. e.g. throw new Error("DB error ...");
Now, a more "consistent" way I usually implement is to wrap all my client-side calls into a function that will treat any errors for me. e.g.
function wrapper_(f,args) {
try {
return f.apply(this,args);
} catch(err) {
;//log error here or send an email to yourself, etc
throw err.message || err; //re-throw the message, so the client-side knows an error happend
}
}
//real client side called functions get wrapped like this (just examples)
function fileSelected(file,type) { return wrapper_(fileSelected_,[file,type]); }
function loadSettings(id) { return wrapper_(loadSettings_,[id]); }
function fileSelected_(file,type) {
; //do your thing
}
function loadSettings_(id) {
; //just examples
throw new Error("DB error ...");
}
Related
I want to use web3.js to show revert reason to user, for example in the case of user trying to mint erc721 token that has already been minted. I am using try catch block and see the error message but I want to isolate the error message to show the user a meaningful reason. Thanks in advance.
The previous answer by #Petr Hejda didn't work for me, and neither did his suggestion in response to #Chakshu Jain's problem in the comments.
Instead, I removed some characters—from the start and the end, with slice()—that were causing the error when parsing the JSON, so I could handle the error message and get the error message.
if (err) {
var errorMessageInJson = JSON.parse(
err.message.slice(58, err.message.length - 2)
);
var errorMessageToShow = errorMessageInJson.data.data[Object.keys(errorMessageInJson.data.data)[0]].reason;
alert(errorMessageToShow);
return;
}
It's returned in the JS error object as data.<txHash>.reason.
This is a faulty Solidity code
pragma solidity ^0.8.0;
contract Test {
function foo() public {
revert('This is error message');
}
}
So a transaction calling the foo() function should revert with the message This is error message.
try {
await myContract.methods.foo().send();
} catch (e) {
const data = e.data;
const txHash = Object.keys(data)[0]; // TODO improve
const reason = data[txHash].reason;
console.log(reason); // prints "This is error message"
}
After trying out every solution on stackoverflow, random blogs, and even the officially documented "web3.eth.handleRevert = true", none is working for me.
I finally figured out after 25 failed attempts:
try {
await obj.methods.do_something().call({
gasLimit: String(GAS_LIMIT),
to: CONTRACT_ADDRESS,
from: wallet,
value: String(PRICE),
})
}
catch (err) {
const endIndex = err.message.search('{')
if (endIndex >= 0) {
throw err.message.substring(0, endIndex)
}
}
try {
const res = await obj.methods.do_something().send({
gasLimit: String(GAS_LIMIT),
to: CONTRACT_ADDRESS,
from: wallet,
value: String(PRICE),
})
return res.events.Transfer.returnValues.tokenId
}
catch (err) {
console.error(err)
throw err
}
The idea is to use call first. This method doesn't interact with your Metamask, but merely checks if your input arguments go through the contract method. If it can't go through, it will throw exception in the first catch block. If it does go through, we are safe to do use send. This method interacts with your Metamask for real. We have a second catch block in case there are wallet connection or gas fee issues
It is really perplexing why Solidity/Web3 don't have an easy way to extract the require/revert reason from the error object.
For me, the "require" reason is there in the message property of the error object, but it is surrounded by lot of other words which I don't need.
An example error message:
[ethjs-query] while formatting outputs from RPC '{"value":{"code":-32603,"data":{"message":"VM Exception while processing transaction: revert Voting is closed","code":-32000,"data":{"0xf901429f12096d3b5c23a80e56fd2230fa37411bb1f8d3cdbd5c8f91c2670771":{"error":"revert","program_counter":43,"return":"0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000165f5f5f566f74696e6720697320636c6f7365645f5f5f00000000000000000000","reason":"Voting is closed"},"stack":"RuntimeError: VM Exception while processing transaction: revert Voting is closed \n at Function.RuntimeError.fromResults (/tmp/.mount_ganachreY1gT/resources/static/node/node_modules/ganache-core/lib/utils/runtimeerror.js:94:13)\n at BlockchainDouble.processBlock (/tmp/.mount_ganachreY1gT/resources/static/node/node_modules/ganache-core/lib/blockchain_double.js:627:24)\n at runMicrotasks (<anonymous>)\n at processTicksAndRejections (internal/process/task_queues.js:93:5)","name":"RuntimeError"}}}}'
You can see the reason Voting is closed stuck in between. Not that user-friendly to read.
I've seen answers that use regex to extract the error reason.
For those like me, who are not a big fan of the regex way, here is my approach.
In your solidity contract, wrap the require reason with a unique delimiter of sorts. In my case, it is "___" (3 underscores).
contract MyContract{
...
...
function vote(address _addr) public payable{
require(votingOpen, "___Voting closed___");
...
}
...
...
}
Declare a helper function to extract the error using JavaScript string utilities. Here's where your delimiter coes in handy.
export const extractErrorCode = (str) => {
const delimiter = '___'; //Replace it with the delimiter you used in the Solidity Contract.
const firstOccurence = str.indexOf(delimiter);
if(firstOccurence == -1) {
return "An error occured";
}
const secondOccurence = str.indexOf(delimiter, firstOccurence + 1);
if(secondOccurence == -1) {
return "An error occured";
}
//Okay so far
return str.substring(firstOccurence + delimiter.length, secondOccurence);
}
Use this function where you catch the error in your frontend
const vote = async (_addr) => {
setLoading(true);
try {
await contest.methods.vote(_addr).send({
from: accounts[0],
})
}
catch (e) {
console.log('Voting failed with error object => ', e)
console.log('Voting failed with the error => ', extractErrorCode(e.message))
}
setLoading(false);
}
Until Solidity & Web3.js (and ether.js) come out with a clean way to parse errors, we are stuck with workarounds like this.
I prefer this workaround over others because I am not that great with regex, and additionally, this one does not depend on a fixed starting position to extract the error code.
Did you try something like this?
error.toString()
It works for me just to show the revert error in the Smart Contract, and return it as a string message.
try {
//Do something
} catch (error) {
res.send({
'status': false,
'result': error.toString()
});
}
I am coding a server-side app in node JS and I am using a database in MySQL.
I get "TypeError: Cannot read property 'activated' of undefined"
The request I do should say "empty set" when I do it manually in the MySQL terminal.
When I try to use it in my code an I input and invalid discord_key, it returns an error, but I want it to return just a false alarm so I can catch it and use that info.
function checkKey(key) {
var activated = "";
var sqlcheck = "SELECT activated from authentification where discord_ key = ?";
console.log("in function");
DB.query(sqlcheck, [key], function (err, result) {
if (err) throw (err);
activated = result[0].activated;
});
if (!activated) {
console.log("null");
return ("NULL");
} else {
console.log("used");
return ("used");
}
}
I should get :
that request sends an empty set, so the key doesn't exist.
thank you for your help!
In case no result you can write this:
if (err) throw (err);
activated = result.length ? result[0].activated : false;
That will return false in case of no result.
The Error
The error is telling you that a variable you are using is undefined. It tells you this because you attempt to read a property from an undefined variable.
You mentioned result is an empty array. This means that any index you attempt to access returns undefined. For example:
let result = []
console.log(result[0] === undefined) // prints true
And in javascript, if you try and access a property of undefined, you get your error. Continuing our example:
result[0].activated // Throws error: Cannot read property 'activated' of undefined.
Since there is no guarentee that result[0] has a value, you should make sure it is not undefined before accessing it's properties. As #NipunChawla shows, one way is to check the array has a length (i.e at lease one value):
if (result.length) { // Does result have values?
activated = result[0].activated
} else {
activated = false
}
Better yet, if you know you are working with result[0] only, check whether it is defined directly:
if (result[0]) { // Does result[0] have a value?
activated = result[0].activated
} else {
activated = false
}
You are still left with the possibility that result[0].activated does not exist. Meaning activated would be undefined.
if (result[0] && result[0].activated) { // ... and does the first value
// contain the property activated?
activated = result[0].activated
} else {
activated = false
}
So all together now:
DB.query(sqlcheck, [key], function (err, result) {
if (err) throw (err);
if (result[0] && result[0].activated) {
activated = result[0].activated
} else {
activated = false
}
})
Async Callbacks
To fix !activated in the second if statement always being true, you should look into how callbacks work. Basically DB.query goes off and does its thing. When it is done, it will execute the function you provided it as a callback. The order of execution looks something like this:
Call DB.query to send a request to your database
Continue execution of your script. i.e check if (!activated) { ...
DB.query has now finished and calls your callback, assigning activated = result[0].activated. i.e function(err, result)
A quick way you could fix this would be like so:
function checkKey(key) {
var activated = "";
var sqlcheck = "SELECT activated from authentification where discord_ key = ?";
console.log("in function");
DB.query(sqlcheck, [key], function (err, result) {
if (err) throw (err);
if (result[0] && result[0].activated) {
activated = result[0].activated
} else {
activated = false
}
doSomethingWithResult(activated)
});
}
function doStuffWithResult(activated) {
if (!activated) {
console.log("null");
// Do your !activated stuff
} else {
console.log("used");
// Do your activated stuff
}
}
See this question for more info.
I noticed that when an error is thrown in a try/catch block within a Google Apps Script, Logger.log(e instanceof Error) returns true. But, when that same object is passed back to the client in the catch statement, it logs false.
GAS sample
function isValid() {
return false
}
function testing() {
try {
if(!isValid()) { throw new Error("failure") }
return "success"
} catch(e) {
Logger.log(e instanceof Error) // true
return e // false in the client when tested with console.log(e instanceof Error)
}
}
client
function foo() {
google.script.run.withSuccessHandler(onSuccess).withFailureHandler(onFailure).testing();
}
function onSuccess(e) {
console.log(e instanceof Error) // false
console.log(e) // null
}
function onFailure(e) {
console.log(e instanceof Error) // no result
}
Is there a better way to test for an error returned from the script file?
From the client side, you use a .withFailureHandler(...) when calling the .run.myFunction() code. The failure handler is a function in your client side code that will be called if an exception (i.e. error) is thrown in your server-side code and not handled.
Failure handlers will only be called if an exception is thrown. Otherwise, the success handler receives the server-side function's return value.
.gs
function myFn() {
try {
throw new Error("failure");
}
catch (e) {
Logger.log(e);
// Must rethrow to activate the client's FailureHandler function.
throw e;
}
return "success"
}
.html
function foo() {
google.script.run.withFailureHandler(logError).withSuccessHandler(useReturnValue).myFn();
}
function logError(error) {
console.log(error);
}
function useReturnValue(value) {
// do stuff
}
In the client side console, you will see the error get logged.
The problem is that your e variable hasn't an object assigned. Please note that the official Guides use error as the error handling function argument but your code use data and instead of using it on the console statement you use e.
On your client-side code replace data by e or vice versa.
From https://developers.google.com/apps-script/guides/html/reference/run#withFailureHandler(Function)
Code.gs
function doGet() {
return HtmlService.createHtmlOutputFromFile('Index');
}
function getUnreadEmails() {
// 'got' instead of 'get' will throw an error.
return GmailApp.gotInboxUnreadCount();
}
index.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<script>
function onFailure(error) {
var div = document.getElementById('output');
div.innerHTML = "ERROR: " + error.message;
}
google.script.run.withFailureHandler(onFailure)
.getUnreadEmails();
</script>
</head>
<body>
<div id="output"></div>
</body>
</html>
Api Call
http://localhost:8888/api/v1/users/100 //doesn't exist
Html Call
http://localhost:8888/admin/users/100 //doesn't exist
Obviously, I don't want the Html Call exception to return json data and I don't want the Api Call to return Html Data.
I am not exception handling in the controller. I am exception handling in my UserRepository. As such, my controllers are just returning a result from the user repository.
class Sentry2UserRepository implements UserInterface {
public function findById($id) {
try {
return Sentry::findUserById($id);
}
catch (\Cartalyst\Sentry\Users\UserNotFoundException $e) {
// Do something here
return false;
}
}
}
Question 1: What is the normal / proper way of passing an error back to controller so that it will know what to display?
Question 2: Is there a standard json API format for exceptions / errors?
Question 3: Is it good practice for a Web UI to consume an internal JsonApi? Or am I doing things the right way at the moment with my WebUi controllers Querying the same Repositories as the Api?
Try this magic in your filters.php:
App::error(function(Exception $exception, $httpCode)
{
if (Request::is('api/*')){
return Response::json( ['code' => $exception->getCode(), 'error' => $exception->getMessage()], $httpCode );
}else{
$layout = View::make('layouts.main');
$layout->content = View::make('errors.error')->with('code', $exception->getCode())->with('error', $exception->getMessage())->with('httpCode',$httpCode);
return Response::make($layout, $httpCode);
}
});
First of all, I think your approach in Sentry2UserRepository is not bad, it's ok, IMO.
Question 1: What is the normal / proper way of passing an error back
to controller so that it will know what to display?
Well, IMO, depending on the application you should determine how you should handle exceptions. You mentioned so that it will know what to display and in this case it depends on how and what information you need from an exception to take the next action after an exception occured. now if you need the error message then you may return the return $e->getMessage() so you'll exactly know what actually happened. There are many ways to do this, for example, using a single catch :
try{
// ...
}
catch( Exception $e )
{
if ($e instanceof UserNotFoundException) {
// it's an instance of UserNotFoundException, return accordingly
}
elseif ($e instanceof SomethinElseException) {
// it's an instance of SomethinElseException, return accordingly
}
}
Also, you can use different custom exception classes and may use multiple catch blocks, i.e.
class AnException extends Exception
{
public function customErrorMessage()
{
return `AnException occurred!`
}
}
class AnotherException extends Exception
{
public function customErrorMessage()
{
return `AnotherException occurred!`
}
}
Then catch using multiple catch blocks, i.e.
try
{
// ...
}
catch(AnException $e)
{
return $e->customErrorMessage();
}
catch(AnotherException $e)
{
return $e->customErrorMessage();
}
catch(Exception $e)
{
return $e->getMessage();
}
Question 2: Is there a standard json API format for exceptions / errors?
Question 3: Is it good practice for a Web UI to consume an internal JsonApi? Or am I doing things the right way at the moment with my WebUi controllers Querying the same Repositories as the Api?
Actually I don't know about such an api and you are doing right, IMO. It's because, you have this
class Sentry2UserRepository implements UserInterface {
public function findById($id) {
try {
return Sentry::findUserById($id);
}
catch (\Cartalyst\Sentry\Users\UserNotFoundException $e) {
// Do something here
return false;
}
}
}
So, it's possible to write code in controller something like this
if(findById(5)) {
// found and dump it to the view
}
else {
// show "Not Found !", false will be back only for UserNotFoundException
}
But, if you had this in your UserNotFoundException catch
return $e; // or anything else (maybe an array containing status and message)
Then It's not possible to write this simple code
if(findById(5)) {
// found and dump it to the view
}
Because, is statement will be true for $e object oe for an array, so you have to check it again, using somrthing like this
$result = findById(5);
if($result && $result->code && $result->code === 0) {
// something according to code
}
Or maybe, something like this
if($result && $result->code) {
// error happened, now determine the code
switch($result->code){
case 0:
// show message
break;
case 1:
// show message
break;
}
}
So, IMO, why, you need to show the user, what error happened in your application, why not just to states, either you got data or you didn't get it. isn't it simple ? Just KISS. This is my opinion only, that's it.
Here's a Node.js function. It works, in the sense that bad JSON data is kicked out, but it also flashes the message that it failed. Why?
// Create document
app.post('/documents.:format?', loadUser, function(req, res) {
/////////////////////////added by adam
//tests to see if the inputed text is valid JSON data
data = req.body.d.data;
console.log("///////////" + data);
try {
type = JSON.parse(data);
console.log(type);
} catch (ex) {
console.log("bad json: "+data);
req.flash('Nope', 'Invalid JSON');
res.redirect('/documents');
return;
}
var d = new Document(req.body.d);
d.user_id = req.currentUser.id;
d.save(function() {
switch (req.params.format) {
case 'json':
res.send(d.toObject());
break;
default:
req.flash('info', 'Document created');
res.redirect('/documents');
}
});
The catch block contains both the error message and the 'bad JSON' logger, so they will always occur at the same time, due to the block scope.