/*
 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 { OperationError } from "../types/walletTypes";
import { HttpResponse } from "../util/http";
import { Codec } from "../util/codec";
/**
 * This exception is there to let the caller know that an error happened,
 * but the error has already been reported by writing it to the database.
 */
export class OperationFailedAndReportedError extends Error {
  constructor(public operationError: OperationError) {
    super(operationError.message);
    // Set the prototype explicitly.
    Object.setPrototypeOf(this, OperationFailedAndReportedError.prototype);
  }
}
/**
 * This exception is thrown when an error occured and the caller is
 * responsible for recording the failure in the database.
 */
export class OperationFailedError extends Error {
  constructor(public operationError: OperationError) {
    super(operationError.message);
    // Set the prototype explicitly.
    Object.setPrototypeOf(this, OperationFailedError.prototype);
  }
}
/**
 * Process an HTTP response that we expect to contain Taler-specific JSON.
 *
 * Depending on the status code, we throw an exception.  This function
 * will try to extract Taler-specific error information from the HTTP response
 * if possible.
 */
export async function scrutinizeTalerJsonResponse(
  resp: HttpResponse,
  codec: Codec,
): Promise {
  // FIXME: We should distinguish between different types of error status
  // to react differently (throttle, report permanent failure)
  // FIXME: Make sure that when we receive an error message,
  // it looks like a Taler error message
  if (resp.status !== 200) {
    let exc: OperationFailedError | undefined = undefined;
    try {
      const errorJson = await resp.json();
      const m = `received error response (status ${resp.status})`;
      exc = new OperationFailedError({
        type: "protocol",
        message: m,
        details: {
          httpStatusCode: resp.status,
          errorResponse: errorJson,
        },
      });
    } catch (e) {
      const m = "could not parse response JSON";
      exc = new OperationFailedError({
        type: "network",
        message: m,
        details: {
          status: resp.status,
        },
      });
    }
    throw exc;
  }
  let json: any;
  try {
    json = await resp.json();
  } catch (e) {
    const m = "could not parse response JSON";
    throw new OperationFailedError({
      type: "network",
      message: m,
      details: {
        status: resp.status,
      },
    });
  }
  return codec.decode(json);
}
/**
 * Run an operation and call the onOpError callback
 * when there was an exception or operation error that must be reported.
 * The cause will be re-thrown to the caller.
 */
export async function guardOperationException(
  op: () => Promise,
  onOpError: (e: OperationError) => Promise,
): Promise {
  try {
    return await op();
  } catch (e) {
    console.log("guard: caught exception");
    if (e instanceof OperationFailedAndReportedError) {
      throw e;
    }
    if (e instanceof OperationFailedError) {
      await onOpError(e.operationError);
      throw new OperationFailedAndReportedError(e.operationError);
    }
    if (e instanceof Error) {
      console.log("guard: caught Error");
      const opErr = {
        type: "exception",
        message: e.message,
        details: {},
      };
      await onOpError(opErr);
      throw new OperationFailedAndReportedError(opErr);
    }
    console.log("guard: caught something else");
    const opErr = {
      type: "exception",
      message: "unexpected exception thrown",
      details: {
        value: e.toString(),
      },
    };
    await onOpError(opErr);
    throw new OperationFailedAndReportedError(opErr);
  }
}