wallet-core/packages/taler-wallet-core/src/errors.ts

164 lines
4.8 KiB
TypeScript

/*
This file is part of GNU Taler
(C) 2019-2020 Taler Systems SA
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
* Classes and helpers for error handling specific to wallet operations.
*
* @author Florian Dold <dold@taler.net>
*/
/**
* Imports.
*/
import {
TalerErrorCode,
TalerErrorDetail,
TransactionType,
} from "@gnu-taler/taler-util";
export interface DetailsMap {
[TalerErrorCode.WALLET_PENDING_OPERATION_FAILED]: {
innerError: TalerErrorDetail;
transactionId?: string;
};
[TalerErrorCode.WALLET_EXCHANGE_DENOMINATIONS_INSUFFICIENT]: {
exchangeBaseUrl: string;
};
[TalerErrorCode.WALLET_EXCHANGE_PROTOCOL_VERSION_INCOMPATIBLE]: {
exchangeProtocolVersion: string;
walletProtocolVersion: string;
};
[TalerErrorCode.WALLET_WITHDRAWAL_OPERATION_ABORTED_BY_BANK]: {};
[TalerErrorCode.WALLET_TIPPING_COIN_SIGNATURE_INVALID]: {};
[TalerErrorCode.WALLET_ORDER_ALREADY_CLAIMED]: {
orderId: string;
claimUrl: string;
};
[TalerErrorCode.WALLET_CONTRACT_TERMS_MALFORMED]: {};
[TalerErrorCode.WALLET_CONTRACT_TERMS_SIGNATURE_INVALID]: {
merchantPub: string;
orderId: string;
};
[TalerErrorCode.WALLET_CONTRACT_TERMS_BASE_URL_MISMATCH]: {
baseUrlForDownload: string;
baseUrlFromContractTerms: string;
};
[TalerErrorCode.WALLET_INVALID_TALER_PAY_URI]: {
talerPayUri: string;
};
[TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR]: {};
[TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION]: {};
[TalerErrorCode.WALLET_BANK_INTEGRATION_PROTOCOL_VERSION_INCOMPATIBLE]: {};
[TalerErrorCode.WALLET_CORE_API_OPERATION_UNKNOWN]: {};
[TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED]: {};
[TalerErrorCode.WALLET_NETWORK_ERROR]: {};
[TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE]: {};
[TalerErrorCode.WALLET_EXCHANGE_COIN_SIGNATURE_INVALID]: {};
[TalerErrorCode.WALLET_WITHDRAWAL_GROUP_INCOMPLETE]: {};
[TalerErrorCode.WALLET_CORE_NOT_AVAILABLE]: {};
[TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR]: {};
}
type ErrBody<Y> = Y extends keyof DetailsMap ? DetailsMap[Y] : never;
export function makeErrorDetail<C extends TalerErrorCode>(
code: C,
detail: ErrBody<C>,
hint?: string,
): TalerErrorDetail {
// FIXME: include default hint?
return { code, hint, ...detail };
}
export function makePendingOperationFailedError(
innerError: TalerErrorDetail,
tag: TransactionType,
uid: string,
): TalerError {
return TalerError.fromDetail(TalerErrorCode.WALLET_PENDING_OPERATION_FAILED, {
innerError,
transactionId: `${tag}:${uid}`,
});
}
export class TalerError<T = any> extends Error {
errorDetail: TalerErrorDetail & T;
private constructor(d: TalerErrorDetail & T) {
super();
this.errorDetail = d;
Object.setPrototypeOf(this, TalerError.prototype);
}
static fromDetail<C extends TalerErrorCode>(
code: C,
detail: ErrBody<C>,
hint?: string,
): TalerError {
// FIXME: include default hint?
return new TalerError<unknown>({ code, hint, ...detail });
}
static fromUncheckedDetail(d: TalerErrorDetail): TalerError {
return new TalerError<unknown>({ ...d });
}
static fromException(e: any): TalerError {
const errDetail = getErrorDetailFromException(e);
return new TalerError(errDetail);
}
hasErrorCode<C extends keyof DetailsMap>(
code: C,
): this is TalerError<DetailsMap[C]> {
return this.errorDetail.code === code;
}
}
/**
* Convert an exception (or anything that was thrown) into
* a TalerErrorDetail object.
*/
export function getErrorDetailFromException(e: any): TalerErrorDetail {
if (e instanceof TalerError) {
return e.errorDetail;
}
if (e instanceof Error) {
const err = makeErrorDetail(
TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION,
{
stack: e.stack,
},
`unexpected exception (message: ${e.message})`,
);
return err;
}
// Something was thrown that is not even an exception!
// Try to stringify it.
let excString: string;
try {
excString = e.toString();
} catch (e) {
// Something went horribly wrong.
excString = "can't stringify exception";
}
const err = makeErrorDetail(
TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION,
{},
`unexpected exception (not an exception, ${excString})`,
);
return err;
}