Skip to content

Creating User friendly Errors

Kate Sills edited this page Sep 11, 2021 · 5 revisions

In development, it's often necessary to handle a low-level error and transmute it into a more user-friendly error specific to the context in which the user will understand it.

As a concrete example, in ERTP, a user might try to withdraw an amount from a purse that is greater than the amount that the purse contains. For Nat values, this results in a low-level error from the Nat package such as -65000 is negative where 65000 is the overdrawn amount. However, the user needs to get an error message that isn't low-level, one that tells them that their withdrawal failed. Ideally, the message and error stack would contain enough information for the user to narrow down the action that triggered the error, as well as the purse and payment involved.

The current best practice for turning a low-level error into a user-friendly error is to annotate the user-friendly error with the low-level error, and throw the user-friendly error.

For example, in Zoe we do:

  const handleRejected = lowLevelError => {
    const userFriendlyError = assert.error(
      X`A Zoe invitation is required, not ${invitation}`,
    );
    assert.note(userFriendlyError, X`Due to ${lowLevelError}`);
    throw userFriendlyError;
  };

  return E.when(
    invitationIssuer.burn(invitation),
    handleFulfilled,
    handleRejected,
  );

So a solution to purses that are overdrawn might be:

    let newPurseBalance;
    try {
      newPurseBalance = subtract(currentBalance, amount);
    } catch (err) {
      const withdrawalError = Error(
        X`Withdrawal of ${amount} failed because the purse only contained ${currentBalance}`,
      );
      assert.note(withdrawalError, X`Caused by: ${err}`);
      throw withdrawalError;
    }

Or, in the case of errors that are assumed to be very common, you can preemptively assert that the error condition isn't present:

assert(
  AmountMath.isGTE(currentBalance, amount),
  X`Withdrawal of ${amount} failed because the purse only contained ${currentBalance}`,
);
const newPurseBalance = subtract(currentBalance, amount);

assert is a library written by Agoric that serves two purposes: assert makes it possible to follow error stacks over asynchronous boundaries, and assert protects against the unintentional exposure of object capabilities and other secrets to the caller. For more information on assert, see the error documentation in Endo.

Clone this wiki locally