/*
This file is part of GNU Taler
(C) 2023-2024 Taler Systems S.A.
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
*/
/**
* Imports.
*/
import {
HttpResponse,
readResponseJsonOrErrorCode,
readSuccessResponseJsonOrThrow,
readTalerErrorResponse,
} from "./http-common.js";
import {
Codec,
HttpStatusCode,
TalerError,
TalerErrorCode,
TalerErrorDetail,
} from "./index.js";
type OperationFailWithBodyOrNever =
ErrorEnum extends keyof ErrorMap ? OperationFailWithBody : never;
export type OperationResult =
| OperationOk
| OperationAlternative
| OperationFail
| OperationFailWithBodyOrNever;
export function isOperationOk(
c: OperationResult,
): c is OperationOk {
return c.type === "ok";
}
export function isOperationFail(
c: OperationResult,
): c is OperationFail {
return c.type === "fail";
}
/**
* successful operation
*/
export interface OperationOk {
type: "ok";
/**
* Parsed response body.
*/
body: BodyT;
}
/**
* unsuccessful operation, see details
*/
export interface OperationFail {
type: "fail";
/**
* Error case (either HTTP status code or TalerErrorCode)
*/
case: T;
detail: TalerErrorDetail;
}
/**
* unsuccessful operation, see body
*/
export interface OperationAlternative {
type: "fail";
case: T;
body: B;
}
export interface OperationFailWithBody {
type: "fail";
case: keyof B;
body: B[OperationFailWithBody["case"]];
}
export async function opSuccessFromHttp(
resp: HttpResponse,
codec: Codec,
): Promise> {
const body = await readSuccessResponseJsonOrThrow(resp, codec);
return { type: "ok" as const, body };
}
/**
* Success case, but instead of the body we're returning a fixed response
* to the client.
*/
export function opFixedSuccess(body: T): OperationOk {
return { type: "ok" as const, body };
}
export function opEmptySuccess(resp: HttpResponse): OperationOk {
return { type: "ok" as const, body: void 0 };
}
export async function opKnownFailureWithBody(
case_: keyof B,
body: B[typeof case_],
): Promise> {
return { type: "fail", case: case_, body };
}
export async function opKnownAlternativeFailure(
resp: HttpResponse,
s: T,
codec: Codec,
): Promise> {
const body = (await readResponseJsonOrErrorCode(resp, codec)).response;
return { type: "fail", case: s, body };
}
export async function opKnownHttpFailure(
s: T,
resp: HttpResponse,
): Promise> {
const detail = await readTalerErrorResponse(resp);
return { type: "fail", case: s, detail };
}
export function opKnownTalerFailure(
s: T,
detail: TalerErrorDetail,
): OperationFail {
return { type: "fail", case: s, detail };
}
export function opUnknownFailure(resp: HttpResponse, error: TalerErrorDetail): never {
throw TalerError.fromDetail(
TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR,
{
requestUrl: resp.requestUrl,
requestMethod: resp.requestMethod,
httpStatusCode: resp.status,
errorResponse: error,
},
`Unexpected HTTP status ${resp.status} in response`,
);
}
/**
* Convenience function to throw an error if the operation is not a success.
*/
export function narrowOpSuccessOrThrow(
opName: string,
opRes: OperationResult,
): asserts opRes is OperationOk {
if (opRes.type !== "ok") {
throw TalerError.fromDetail(
TalerErrorCode.GENERIC_CLIENT_INTERNAL_ERROR,
{
operation: opName,
error: String(opRes.case),
detail: "detail" in opRes ? opRes.detail : undefined,
},
`Operation ${opName} failed: ${String(opRes.case)}`,
);
}
}
export type ResultByMethod<
TT extends object,
p extends keyof TT,
> = TT[p] extends (...args: any[]) => infer Ret
? Ret extends Promise
? Result extends OperationResult
? Result
: never
: never //api always use Promises
: never; //error cases just for functions
export type FailCasesByMethod = Exclude<
ResultByMethod,
OperationOk
>;
export type RedirectResult = { redirectURL: URL }