/*
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
*/
/**
* Classes and helpers for error handling specific to wallet operations.
*
* @author Florian Dold
*/
/**
* 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]: {};
[TalerErrorCode.WALLET_PAY_MERCHANT_SERVER_ERROR]: {
requestError: TalerErrorDetail;
};
}
type ErrBody = Y extends keyof DetailsMap ? DetailsMap[Y] : never;
export function makeErrorDetail(
code: C,
detail: ErrBody,
hint?: string,
): TalerErrorDetail {
if (!hint && !(detail as any).hint) {
hint = getDefaultHint(code);
}
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 function summarizeTalerErrorDetail(ed: TalerErrorDetail): string {
const errName = TalerErrorCode[ed.code] ?? "";
return `Error (${ed.code}/${errName})`;
}
function getDefaultHint(code: number): string {
const errName = TalerErrorCode[code];
if (errName) {
return `Error (${errName})`;
} else {
return `Error ()`;
}
}
export class TalerProtocolViolationError extends Error {
constructor(hint?: string) {
let msg: string;
if (hint) {
msg = `Taler protocol violation error (${hint})`;
} else {
msg = `Taler protocol violation error`;
}
super(msg);
Object.setPrototypeOf(this, TalerProtocolViolationError.prototype);
}
}
export class TalerError extends Error {
errorDetail: TalerErrorDetail & T;
private constructor(d: TalerErrorDetail & T) {
super(d.hint ?? `Error (code ${d.code})`);
this.errorDetail = d;
Object.setPrototypeOf(this, TalerError.prototype);
}
static fromDetail(
code: C,
detail: ErrBody,
hint?: string,
): TalerError {
if (!hint) {
hint = getDefaultHint(code);
}
return new TalerError({ code, hint, ...detail });
}
static fromUncheckedDetail(d: TalerErrorDetail): TalerError {
return new TalerError({ ...d });
}
static fromException(e: any): TalerError {
const errDetail = getErrorDetailFromException(e);
return new TalerError(errDetail);
}
hasErrorCode(
code: C,
): this is TalerError {
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;
}