aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-util/src/operation.ts
blob: 9d7594d14e02764408f4eaceda7578176e9e61ef (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import { HttpResponse, readSuccessResponseJsonOrThrow, readTalerErrorResponse } from "./http-common.js";
import { Codec, TalerError, TalerErrorCode, TalerErrorDetail } from "./index.js";

export type OperationResult<Body, ErrorEnum> =
  | OperationOk<Body>
  | OperationFail<ErrorEnum>;

export interface OperationOk<T> {
  type: "ok",
  body: T;
}
export function isOperationOk<T, E>(c: OperationResult<T, E>): c is OperationOk<T> {
  return c.type === "ok"
}
export function isOperationFail<T, E>(c: OperationResult<T, E>): c is OperationFail<E> {
  return c.type === "fail"
}
export interface OperationFail<T> {
  type: "fail",
  case: T,
  detail: TalerErrorDetail,
}

export async function opSuccess<T>(resp: HttpResponse, codec: Codec<T>): Promise<OperationOk<T>> {
  const body = await readSuccessResponseJsonOrThrow(resp, codec)
  return { type: "ok" as const, body }
}
export function opFixedSuccess<T>(body: T): OperationOk<T> {
  return { type: "ok" as const, body }
}
export function opEmptySuccess(): OperationOk<void> {
  return { type: "ok" as const, body: void 0 }
}
export async function opKnownFailure<T extends string>(s: T, resp: HttpResponse): Promise<OperationFail<T>> {
  const detail = await readTalerErrorResponse(resp)
  return { type: "fail", case: s, detail }
}
export function opUnknownFailure(resp: HttpResponse, text: string): never {
  throw TalerError.fromDetail(
    TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR,
    {
      requestUrl: resp.requestUrl,
      requestMethod: resp.requestMethod,
      httpStatusCode: resp.status,
      errorResponse: text,
    },
    `Unexpected HTTP status ${resp.status} in response`,
  );
}
export async function succeedOrThrow<R, E>(cb: () => Promise<OperationResult<R, E>>): Promise<R> {
  const resp = await cb()
  if (resp.type === "ok") return resp.body
  throw TalerError.fromUncheckedDetail({ ...resp.detail, case: resp.case })
}
export async function failOrThrow<E>(s: E, cb: () => Promise<OperationResult<unknown, E>>): Promise<TalerErrorDetail> {
  const resp = await cb()
  if (resp.type === "ok") {
    throw TalerError.fromException(new Error(`request succeed but failure "${s}" was expected`))
  }
  if (resp.case === s) {
    return resp.detail
  }
  throw TalerError.fromException(new Error(`request failed with "${resp.case}" but case "${s}" was expected`))
}



export type ResultByMethod<TT extends object, p extends keyof TT> = TT[p] extends (...args: any[]) => infer Ret ?
  Ret extends Promise<infer Result> ?
  Result extends OperationResult<any, any> ? Result : never :
  never : //api always use Promises
  never; //error cases just for functions

export type FailCasesByMethod<TT extends object, p extends keyof TT> = Exclude<ResultByMethod<TT, p>, OperationOk<any>>

type MethodsOfOperations<T extends object> = keyof {
  [P in keyof T as
  //when the property is a function
  T[P] extends (...args: any[]) => infer Ret ?
  // that returns a promise
  Ret extends Promise<infer Result> ?
  // of an operation
  Result extends OperationResult<any, any> ?
  P : never
  : never
  : never]: any
}

type AllKnownCases<t extends object, d extends keyof t> = "success" | FailCasesByMethod<t, d>["case"]

export type TestForApi<ApiType extends object> = {
  [OpType in MethodsOfOperations<ApiType> as `test_${OpType & string}`]: {
    [c in AllKnownCases<ApiType, OpType>]: undefined | ((api: ApiType) => Promise<void>);
  };
};