aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-util
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2023-10-17 11:17:18 -0300
committerSebastian <sebasjm@gmail.com>2023-10-17 11:17:41 -0300
commit503cbfbb95828677b83212816951eb501de2a8fe (patch)
tree9bbb59300069fba59baca66f543c5145911905b5 /packages/taler-util
parentaca3bc9423f15354913d0114cafbd4bd1782d801 (diff)
downloadwallet-core-503cbfbb95828677b83212816951eb501de2a8fe.tar.gz
wallet-core-503cbfbb95828677b83212816951eb501de2a8fe.tar.bz2
wallet-core-503cbfbb95828677b83212816951eb501de2a8fe.zip
bank api now return typed errors for documented errors
Diffstat (limited to 'packages/taler-util')
-rw-r--r--packages/taler-util/src/errors.ts13
-rw-r--r--packages/taler-util/src/http-client/bank-core.ts254
-rw-r--r--packages/taler-util/src/http-client/index.ts0
-rw-r--r--packages/taler-util/src/http-client/utils.ts65
-rw-r--r--packages/taler-util/src/http-common.ts31
5 files changed, 309 insertions, 54 deletions
diff --git a/packages/taler-util/src/errors.ts b/packages/taler-util/src/errors.ts
index 07a402413..dcdf56c39 100644
--- a/packages/taler-util/src/errors.ts
+++ b/packages/taler-util/src/errors.ts
@@ -34,6 +34,19 @@ import {
type empty = Record<string, never>;
+export interface HttpErrors {
+ // timeout
+ [TalerErrorCode.WALLET_HTTP_REQUEST_GENERIC_TIMEOUT]: empty;
+ // throttled
+ [TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED]: empty;
+ // parsing
+ [TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE]: empty;
+ // network
+ [TalerErrorCode.WALLET_NETWORK_ERROR]: empty;
+ // everything else
+ [TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR]: empty;
+}
+
export interface DetailsMap {
[TalerErrorCode.WALLET_PENDING_OPERATION_FAILED]: {
innerError: TalerErrorDetail;
diff --git a/packages/taler-util/src/http-client/bank-core.ts b/packages/taler-util/src/http-client/bank-core.ts
index c77f9ddda..7b4bb53d4 100644
--- a/packages/taler-util/src/http-client/bank-core.ts
+++ b/packages/taler-util/src/http-client/bank-core.ts
@@ -17,19 +17,20 @@
import {
AmountJson,
Amounts,
+ HttpStatusCode,
Logger
} from "@gnu-taler/taler-util";
import {
+ HttpRequestLibrary,
createPlatformHttpLib,
expectSuccessResponseOrThrow,
- HttpRequestLibrary,
readSuccessResponseJsonOrThrow
} from "@gnu-taler/taler-util/http";
-import { AccessToken, codecForAccountData, codecForBankAccountCreateWithdrawalResponse, codecForBankAccountGetWithdrawalResponse, codecForBankAccountTransactionInfo, codecForBankAccountTransactionsResponse, codecForCashoutConversionResponse, codecForCashoutPending, codecForCashouts, codecForCashoutStatusResponse, codecForConversionRatesResponse, codecForCoreBankConfig, codecForGlobalCashouts, codecForListBankAccountsResponse, codecForMonitorResponse, codecForPublicAccountsResponse, codecForTokenSuccessResponse, TalerAuthentication, TalerCorebankApi } from "./types.js";
-import { addPaginationParams, makeBasicAuthHeader, makeBearerTokenAuthHeader, PaginationParams, UserAndPassword, UserAndToken } from "./utils.js";
+import { TalerBankIntegrationHttpClient } from "./bank-integration.js";
import { TalerRevenueHttpClient } from "./bank-revenue.js";
import { TalerWireGatewayHttpClient } from "./bank-wire.js";
-import { TalerBankIntegrationHttpClient } from "./bank-integration.js";
+import { AccessToken, TalerAuthentication, TalerCorebankApi, codecForAccountData, codecForBankAccountCreateWithdrawalResponse, codecForBankAccountGetWithdrawalResponse, codecForBankAccountTransactionInfo, codecForBankAccountTransactionsResponse, codecForCashoutConversionResponse, codecForCashoutPending, codecForCashoutStatusResponse, codecForCashouts, codecForConversionRatesResponse, codecForCoreBankConfig, codecForGlobalCashouts, codecForListBankAccountsResponse, codecForMonitorResponse, codecForPublicAccountsResponse, codecForTokenSuccessResponse } from "./types.js";
+import { PaginationParams, UserAndPassword, UserAndToken, addPaginationParams, httpEmptySuccess, httpSuccess, knownFailure, makeBasicAuthHeader, makeBearerTokenAuthHeader, unknownFailure } from "./utils.js";
const logger = new Logger("http-client/core-bank.ts");
@@ -80,12 +81,16 @@ export class TalerCoreBankHttpClient {
* https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME
*
*/
- async getConfig(): Promise<TalerCorebankApi.Config> {
+ async getConfig() {
const url = new URL(`config`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "GET"
});
- return readSuccessResponseJsonOrThrow(resp, codecForCoreBankConfig());
+ switch (resp.status) {
+ //FIXME: missing in docs
+ case HttpStatusCode.Ok: return httpSuccess(resp, codecForCoreBankConfig())
+ default: return unknownFailure(url, resp)
+ }
}
//
@@ -96,7 +101,7 @@ export class TalerCoreBankHttpClient {
* https://docs.taler.net/core/api-corebank.html#post--accounts
*
*/
- async createAccount(auth: AccessToken, body: TalerCorebankApi.RegisterAccountRequest): Promise<void> {
+ async createAccount(auth: AccessToken, body: TalerCorebankApi.RegisterAccountRequest) {
const url = new URL(`accounts`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
@@ -105,14 +110,25 @@ export class TalerCoreBankHttpClient {
Authorization: makeBearerTokenAuthHeader(auth)
},
});
- return expectSuccessResponseOrThrow(resp);
+ switch (resp.status) {
+ case HttpStatusCode.NoContent: return httpEmptySuccess()
+ case HttpStatusCode.BadRequest: return knownFailure("invalid-input", resp);
+ case HttpStatusCode.Forbidden: {
+ if (body.username === "bank" || body.username === "admin") {
+ return knownFailure("unable-to-create", resp);
+ } else {
+ return knownFailure("unauthorized", resp);
+ }
+ }
+ case HttpStatusCode.Conflict: return knownFailure("already-exist", resp);
+ default: return unknownFailure(url, resp)
+ }
}
-
/**
* https://docs.taler.net/core/api-corebank.html#delete--accounts-$USERNAME
*
*/
- async deleteAccount(auth: UserAndToken): Promise<void> {
+ async deleteAccount(auth: UserAndToken) {
const url = new URL(`accounts/${auth.username}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "DELETE",
@@ -120,14 +136,26 @@ export class TalerCoreBankHttpClient {
Authorization: makeBearerTokenAuthHeader(auth.token)
},
});
- return expectSuccessResponseOrThrow(resp);
+ switch (resp.status) {
+ case HttpStatusCode.NoContent: return httpEmptySuccess()
+ case HttpStatusCode.NotFound: return knownFailure("not-found", resp);
+ case HttpStatusCode.Forbidden: {
+ if (auth.username === "bank" || auth.username === "admin") {
+ return knownFailure("unable-to-delete", resp);
+ } else {
+ return knownFailure("unauthorized", resp);
+ }
+ }
+ case HttpStatusCode.PreconditionFailed: return knownFailure("balance-not-zero", resp);
+ default: return unknownFailure(url, resp)
+ }
}
/**
* https://docs.taler.net/core/api-corebank.html#patch--accounts-$USERNAME
*
*/
- async updateAccount(auth: UserAndToken, body: TalerCorebankApi.AccountReconfiguration): Promise<void> {
+ async updateAccount(auth: UserAndToken, body: TalerCorebankApi.AccountReconfiguration) {
const url = new URL(`accounts/${auth.username}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "PATCH",
@@ -136,14 +164,19 @@ export class TalerCoreBankHttpClient {
Authorization: makeBearerTokenAuthHeader(auth.token)
},
});
- return expectSuccessResponseOrThrow(resp);
+ switch (resp.status) {
+ case HttpStatusCode.NoContent: return httpEmptySuccess()
+ case HttpStatusCode.NotFound: return knownFailure("not-found", resp);
+ case HttpStatusCode.Forbidden: return knownFailure("unauthorized", resp);
+ default: return unknownFailure(url, resp)
+ }
}
/**
* https://docs.taler.net/core/api-corebank.html#patch--accounts-$USERNAME-auth
*
*/
- async updatePassword(auth: UserAndToken, body: TalerCorebankApi.AccountPasswordChange): Promise<void> {
+ async updatePassword(auth: UserAndToken, body: TalerCorebankApi.AccountPasswordChange) {
const url = new URL(`accounts/${auth.username}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "PATCH",
@@ -152,28 +185,41 @@ export class TalerCoreBankHttpClient {
Authorization: makeBearerTokenAuthHeader(auth.token)
},
});
- return expectSuccessResponseOrThrow(resp);
+ switch (resp.status) {
+ case HttpStatusCode.NoContent: return httpEmptySuccess()
+ //FIXME: missing in docs
+ case HttpStatusCode.NotFound: return knownFailure("not-found", resp);
+ //FIXME: missing in docs
+ case HttpStatusCode.Forbidden: return knownFailure("unauthorized", resp);
+ default: return unknownFailure(url, resp)
+ }
}
/**
* https://docs.taler.net/core/get-$BANK_API_BASE_URL-public-accounts
*
*/
- async getPublicAccounts(): Promise<TalerCorebankApi.PublicAccountsResponse> {
+ async getPublicAccounts() {
const url = new URL(`public-accounts`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
headers: {
},
});
- return readSuccessResponseJsonOrThrow(resp, codecForPublicAccountsResponse());
+ switch (resp.status) {
+ //FIXME: missing in docs
+ case HttpStatusCode.Ok: return httpSuccess(resp, codecForPublicAccountsResponse())
+ //FIXME: missing in docs
+ case HttpStatusCode.NoContent: return httpEmptySuccess()
+ default: return unknownFailure(url, resp)
+ }
}
/**
* https://docs.taler.net/core/api-corebank.html#get--accounts
*
*/
- async getAccounts(auth: AccessToken): Promise<TalerCorebankApi.ListBankAccountsResponse> {
+ async getAccounts(auth: AccessToken) {
const url = new URL(`accounts`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
@@ -181,14 +227,19 @@ export class TalerCoreBankHttpClient {
Authorization: makeBearerTokenAuthHeader(auth)
},
});
- return readSuccessResponseJsonOrThrow(resp, codecForListBankAccountsResponse());
+ switch (resp.status) {
+ case HttpStatusCode.Ok: return httpSuccess(resp, codecForListBankAccountsResponse())
+ case HttpStatusCode.NoContent: return httpEmptySuccess()
+ case HttpStatusCode.Forbidden: return knownFailure("unauthorized", resp);
+ default: return unknownFailure(url, resp)
+ }
}
/**
* https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME
*
*/
- async getAccount(auth: UserAndToken): Promise<TalerCorebankApi.AccountData> {
+ async getAccount(auth: UserAndToken) {
const url = new URL(`accounts/${auth.username}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
@@ -196,7 +247,14 @@ export class TalerCoreBankHttpClient {
Authorization: makeBearerTokenAuthHeader(auth.token)
},
});
- return readSuccessResponseJsonOrThrow(resp, codecForAccountData());
+ switch (resp.status) {
+ case HttpStatusCode.Ok: return httpSuccess(resp, codecForAccountData())
+ //FIXME: missing in docs
+ case HttpStatusCode.NotFound: return knownFailure("not-found", resp);
+ //FIXME: missing in docs
+ case HttpStatusCode.Forbidden: return knownFailure("unauthorized", resp);
+ default: return unknownFailure(url, resp)
+ }
}
//
@@ -207,7 +265,7 @@ export class TalerCoreBankHttpClient {
* https://docs.taler.net/core/api-corebank.html#get-$BANK_API_BASE_URL-accounts-$account_name-transactions
*
*/
- async getTransactions(auth: UserAndToken, pagination?: PaginationParams): Promise<TalerCorebankApi.BankAccountTransactionsResponse> {
+ async getTransactions(auth: UserAndToken, pagination?: PaginationParams) {
const url = new URL(`accounts/${auth.username}/transactions`, this.baseUrl);
addPaginationParams(url, pagination)
const resp = await this.httpLib.fetch(url.href, {
@@ -216,14 +274,23 @@ export class TalerCoreBankHttpClient {
Authorization: makeBearerTokenAuthHeader(auth.token)
},
});
- return readSuccessResponseJsonOrThrow(resp, codecForBankAccountTransactionsResponse());
+ switch (resp.status) {
+ case HttpStatusCode.Ok: return httpSuccess(resp, codecForBankAccountTransactionsResponse())
+ //FIXME: missing in docs
+ case HttpStatusCode.NoContent: return httpEmptySuccess()
+ //FIXME: missing in docs
+ case HttpStatusCode.NotFound: return knownFailure("not-found", resp);
+ //FIXME: missing in docs
+ case HttpStatusCode.Forbidden: return knownFailure("unauthorized", resp);
+ default: return unknownFailure(url, resp)
+ }
}
/**
* https://docs.taler.net/core/api-corebank.html#get-$BANK_API_BASE_URL-accounts-$account_name-transactions-$transaction_id
*
*/
- async getTransactionById(auth: UserAndToken, txid: number): Promise<TalerCorebankApi.BankAccountTransactionInfo> {
+ async getTransactionById(auth: UserAndToken, txid: number) {
const url = new URL(`accounts/${auth.username}/transactions/${String(txid)}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
@@ -231,14 +298,21 @@ export class TalerCoreBankHttpClient {
Authorization: makeBearerTokenAuthHeader(auth.token)
},
});
- return readSuccessResponseJsonOrThrow(resp, codecForBankAccountTransactionInfo());
+ switch (resp.status) {
+ case HttpStatusCode.Ok: return httpSuccess(resp, codecForBankAccountTransactionInfo())
+ //FIXME: missing in docs
+ case HttpStatusCode.NotFound: return knownFailure("not-found", resp);
+ //FIXME: missing in docs
+ case HttpStatusCode.Forbidden: return knownFailure("unauthorized", resp);
+ default: return unknownFailure(url, resp)
+ }
}
/**
* https://docs.taler.net/core/api-corebank.html#post-$BANK_API_BASE_URL-accounts-$account_name-transactions
*
*/
- async createTransaction(auth: UserAndToken, body: TalerCorebankApi.CreateBankAccountTransactionCreate): Promise<void> {
+ async createTransaction(auth: UserAndToken, body: TalerCorebankApi.CreateBankAccountTransactionCreate) {
const url = new URL(`accounts/${auth.username}/transactions`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
@@ -247,7 +321,15 @@ export class TalerCoreBankHttpClient {
},
body,
});
- return expectSuccessResponseOrThrow(resp);
+ switch (resp.status) {
+ //FIXME: fix docs... it should be NoContent
+ case HttpStatusCode.Ok: return httpEmptySuccess()
+ case HttpStatusCode.NoContent: return httpEmptySuccess()
+ case HttpStatusCode.BadRequest: return knownFailure("invalid-input", resp);
+ //FIXME: missing in docs
+ case HttpStatusCode.Forbidden: return knownFailure("unauthorized", resp);
+ default: return unknownFailure(url, resp)
+ }
}
//
@@ -258,7 +340,7 @@ export class TalerCoreBankHttpClient {
* https://docs.taler.net/core/api-corebank.html#post-$BANK_API_BASE_URL-accounts-$account_name-withdrawals
*
*/
- async createWithdrawal(auth: UserAndToken, body: TalerCorebankApi.BankAccountCreateWithdrawalRequest): Promise<TalerCorebankApi.BankAccountCreateWithdrawalResponse> {
+ async createWithdrawal(auth: UserAndToken, body: TalerCorebankApi.BankAccountCreateWithdrawalRequest) {
const url = new URL(`accounts/${auth.username}/withdrawals`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
@@ -267,43 +349,65 @@ export class TalerCoreBankHttpClient {
},
body,
});
- return readSuccessResponseJsonOrThrow(resp, codecForBankAccountCreateWithdrawalResponse());
+ switch (resp.status) {
+ //FIXME: missing in docs
+ case HttpStatusCode.Ok: return httpSuccess(resp, codecForBankAccountCreateWithdrawalResponse())
+ case HttpStatusCode.Forbidden: return knownFailure("insufficient-funds", resp);
+ default: return unknownFailure(url, resp)
+ }
}
/**
* https://docs.taler.net/core/api-corebank.html#post-$BANK_API_BASE_URL-accounts-$account_name-withdrawals
*
*/
- async getWithdrawalById(wid: string): Promise<TalerCorebankApi.BankAccountGetWithdrawalResponse> {
+ async getWithdrawalById(wid: string) {
const url = new URL(`withdrawals/${wid}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
});
- return readSuccessResponseJsonOrThrow(resp, codecForBankAccountGetWithdrawalResponse());
+ switch (resp.status) {
+ //FIXME: missing in docs
+ case HttpStatusCode.Ok: return httpSuccess(resp, codecForBankAccountGetWithdrawalResponse())
+ default: return unknownFailure(url, resp)
+ }
}
/**
* https://docs.taler.net/core/api-corebank.html#post-$BANK_API_BASE_URL-withdrawals-$withdrawal_id-abort
*
*/
- async abortWithdrawalById(wid: string): Promise<void> {
+ async abortWithdrawalById(wid: string) {
const url = new URL(`withdrawals/${wid}/abort`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
});
- return expectSuccessResponseOrThrow(resp);
+ switch (resp.status) {
+ //FIXME: fix docs... it should be NoContent
+ case HttpStatusCode.Ok: return httpEmptySuccess()
+ case HttpStatusCode.NoContent: return httpEmptySuccess()
+ case HttpStatusCode.Conflict: return knownFailure("previously-confirmed", resp);
+ default: return unknownFailure(url, resp)
+ }
}
/**
* https://docs.taler.net/core/api-corebank.html#post-$BANK_API_BASE_URL-withdrawals-$withdrawal_id-confirm
*
*/
- async confirmWithdrawalById(wid: string): Promise<void> {
+ async confirmWithdrawalById(wid: string) {
const url = new URL(`withdrawals/${wid}/confirm`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
});
- return expectSuccessResponseOrThrow(resp);
+ switch (resp.status) {
+ //FIXME: fix docs... it should be NoContent
+ case HttpStatusCode.Ok: return httpEmptySuccess()
+ case HttpStatusCode.NoContent: return httpEmptySuccess()
+ case HttpStatusCode.Conflict: return knownFailure("previously-aborted", resp);
+ case HttpStatusCode.UnprocessableEntity: return knownFailure("no-exchange-or-reserve-selected", resp);
+ default: return unknownFailure(url, resp)
+ }
}
//
@@ -314,7 +418,7 @@ export class TalerCoreBankHttpClient {
* https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-cashouts
*
*/
- async createCashout(auth: UserAndToken, body: TalerCorebankApi.CashoutRequest): Promise<TalerCorebankApi.CashoutPending> {
+ async createCashout(auth: UserAndToken, body: TalerCorebankApi.CashoutRequest) {
const url = new URL(`accounts/${auth.username}/cashouts`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
@@ -323,14 +427,20 @@ export class TalerCoreBankHttpClient {
},
body,
});
- return readSuccessResponseJsonOrThrow(resp, codecForCashoutPending());
+ switch (resp.status) {
+ case HttpStatusCode.Accepted: return httpSuccess(resp, codecForCashoutPending())
+ //FIXME: it should be precondition-failed
+ case HttpStatusCode.Conflict: return knownFailure("invalid-state", resp);
+ case HttpStatusCode.ServiceUnavailable: return knownFailure("tan-not-supported", resp);
+ default: return unknownFailure(url, resp)
+ }
}
/**
* https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-cashouts-$CASHOUT_ID-abort
*
*/
- async abortCashoutById(auth: UserAndToken, cid: string): Promise<void> {
+ async abortCashoutById(auth: UserAndToken, cid: string) {
const url = new URL(`accounts/${auth.username}/cashouts/${cid}/abort`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
@@ -338,14 +448,19 @@ export class TalerCoreBankHttpClient {
Authorization: makeBearerTokenAuthHeader(auth.token)
},
});
- return expectSuccessResponseOrThrow(resp);
+ switch (resp.status) {
+ case HttpStatusCode.NoContent: return httpEmptySuccess()
+ case HttpStatusCode.NotFound: return knownFailure("not-found", resp);
+ case HttpStatusCode.Conflict: return knownFailure("already-confirmed", resp);
+ default: return unknownFailure(url, resp)
+ }
}
/**
* https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-cashouts-$CASHOUT_ID-confirm
*
*/
- async confirmCashoutById(auth: UserAndToken, cid: string, body: TalerCorebankApi.CashoutConfirmRequest): Promise<void> {
+ async confirmCashoutById(auth: UserAndToken, cid: string, body: TalerCorebankApi.CashoutConfirmRequest) {
const url = new URL(`accounts/${auth.username}/cashouts/${cid}/confirm`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
@@ -354,14 +469,20 @@ export class TalerCoreBankHttpClient {
},
body,
});
- return expectSuccessResponseOrThrow(resp);
+ switch (resp.status) {
+ case HttpStatusCode.NoContent: return httpEmptySuccess()
+ case HttpStatusCode.Forbidden: return knownFailure("wrong-tan-or-credential", resp);
+ case HttpStatusCode.NotFound: return knownFailure("not-found", resp);
+ case HttpStatusCode.Conflict: return knownFailure("cashout-address-changed", resp);
+ default: return unknownFailure(url, resp)
+ }
}
/**
* https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-cashouts-$CASHOUT_ID-confirm
*
*/
- async getCashoutRate(conversion: { debit?: AmountJson, credit?: AmountJson }): Promise<TalerCorebankApi.CashoutConversionResponse> {
+ async getCashoutRate(conversion: { debit?: AmountJson, credit?: AmountJson }) {
const url = new URL(`cashout-rate`, this.baseUrl);
if (conversion.debit) {
url.searchParams.set("amount_debit", Amounts.stringify(conversion.debit))
@@ -372,14 +493,19 @@ export class TalerCoreBankHttpClient {
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
});
- return readSuccessResponseJsonOrThrow(resp, codecForCashoutConversionResponse());
+ switch (resp.status) {
+ case HttpStatusCode.Ok: return httpSuccess(resp, codecForCashoutConversionResponse())
+ case HttpStatusCode.BadRequest: return knownFailure("wrong-calculation", resp);
+ case HttpStatusCode.NotFound: return knownFailure("not-supported", resp);
+ default: return unknownFailure(url, resp)
+ }
}
/**
* https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME-cashouts
*
*/
- async getAccountCashouts(auth: UserAndToken): Promise<TalerCorebankApi.Cashouts> {
+ async getAccountCashouts(auth: UserAndToken) {
const url = new URL(`accounts/${auth.username}/cashouts`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
@@ -387,14 +513,18 @@ export class TalerCoreBankHttpClient {
Authorization: makeBearerTokenAuthHeader(auth.token)
},
});
- return readSuccessResponseJsonOrThrow(resp, codecForCashouts());
+ switch (resp.status) {
+ case HttpStatusCode.Ok: return httpSuccess(resp, codecForCashouts())
+ case HttpStatusCode.NoContent: return httpEmptySuccess();
+ default: return unknownFailure(url, resp)
+ }
}
/**
* https://docs.taler.net/core/api-corebank.html#get--cashouts
*
*/
- async getGlobalCashouts(auth: AccessToken): Promise<TalerCorebankApi.GlobalCashouts> {
+ async getGlobalCashouts(auth: AccessToken) {
const url = new URL(`cashouts`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
@@ -402,14 +532,18 @@ export class TalerCoreBankHttpClient {
Authorization: makeBearerTokenAuthHeader(auth)
},
});
- return readSuccessResponseJsonOrThrow(resp, codecForGlobalCashouts());
+ switch (resp.status) {
+ case HttpStatusCode.Ok: return httpSuccess(resp, codecForGlobalCashouts())
+ case HttpStatusCode.NoContent: return httpEmptySuccess();
+ default: return unknownFailure(url, resp)
+ }
}
/**
* https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME-cashouts-$CASHOUT_ID
*
*/
- async getCashoutById(auth: UserAndToken, cid: string): Promise<TalerCorebankApi.CashoutStatusResponse> {
+ async getCashoutById(auth: UserAndToken, cid: string) {
const url = new URL(`accounts/${auth.username}/cashouts/${cid}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
@@ -417,7 +551,12 @@ export class TalerCoreBankHttpClient {
Authorization: makeBearerTokenAuthHeader(auth.token)
},
});
- return readSuccessResponseJsonOrThrow(resp, codecForCashoutStatusResponse());
+ switch (resp.status) {
+ //FIXME: missing in docs
+ case HttpStatusCode.Ok: return httpSuccess(resp, codecForCashoutStatusResponse())
+ case HttpStatusCode.NotFound: return knownFailure("already-aborted", resp);
+ default: return unknownFailure(url, resp)
+ }
}
//
@@ -428,12 +567,16 @@ export class TalerCoreBankHttpClient {
* https://docs.taler.net/core/api-corebank.html#get--conversion-rates
*
*/
- async getConversionRates(): Promise<TalerCorebankApi.ConversionRatesResponse> {
+ async getConversionRates() {
const url = new URL(`conversion-rates`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
});
- return readSuccessResponseJsonOrThrow(resp, codecForConversionRatesResponse());
+ switch (resp.status) {
+ case HttpStatusCode.Ok: return httpSuccess(resp, codecForConversionRatesResponse())
+ case HttpStatusCode.NotFound: return knownFailure("not-supported", resp);
+ default: return unknownFailure(url, resp)
+ }
}
//
@@ -444,14 +587,19 @@ export class TalerCoreBankHttpClient {
* https://docs.taler.net/core/api-corebank.html#get--monitor
*
*/
- async getMonitor(params: { timeframe: TalerCorebankApi.MonitorTimeframeParam, which: number }): Promise<TalerCorebankApi.MonitorResponse> {
+ async getMonitor(params: { timeframe: TalerCorebankApi.MonitorTimeframeParam, which: number }) {
const url = new URL(`monitor`, this.baseUrl);
url.searchParams.set("timeframe", params.timeframe.toString())
url.searchParams.set("which", String(params.which))
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
});
- return readSuccessResponseJsonOrThrow(resp, codecForMonitorResponse());
+ switch (resp.status) {
+ case HttpStatusCode.Ok: return httpSuccess(resp, codecForMonitorResponse())
+ case HttpStatusCode.NotFound: return knownFailure("not-supported", resp);
+ case HttpStatusCode.BadRequest: return knownFailure("invalid-input", resp);
+ default: return unknownFailure(url, resp)
+ }
}
//
diff --git a/packages/taler-util/src/http-client/index.ts b/packages/taler-util/src/http-client/index.ts
deleted file mode 100644
index e69de29bb..000000000
--- a/packages/taler-util/src/http-client/index.ts
+++ /dev/null
diff --git a/packages/taler-util/src/http-client/utils.ts b/packages/taler-util/src/http-client/utils.ts
index 4588f945c..f4af5ae03 100644
--- a/packages/taler-util/src/http-client/utils.ts
+++ b/packages/taler-util/src/http-client/utils.ts
@@ -1,6 +1,10 @@
import { base64FromArrayBuffer } from "../base64.js";
+import { HttpResponse, readErrorResponse, readSuccessResponseJsonOrThrow, readTalerErrorResponse } from "../http-common.js";
+import { HttpStatusCode } from "../http-status-codes.js";
+import { Codec } from "../index.js";
import { stringToBytes } from "../taler-crypto.js";
-import { AccessToken, TalerAuthentication } from "./types.js";
+import { TalerErrorDetail } from "../wallet-types.js";
+import { AccessToken } from "./types.js";
/**
* Helper function to generate the "Authorization" HTTP header.
@@ -66,3 +70,62 @@ export type PaginationParams = {
*/
order: "asc" | "dec"
}
+
+export type HttpResult<Body, ErrorEnum> =
+ | HttpOk<Body>
+ | HttpKnownFail<ErrorEnum>
+ | HttpUnkownFail;
+
+/**
+ * 200 < status < 204
+ */
+export interface HttpOk<T> {
+ type: "ok",
+ body: T;
+}
+
+/**
+ * 400 < status < 409
+ * and error documented
+ */
+export interface HttpKnownFail<T> {
+ type: "fail",
+ case: T,
+ detail: TalerErrorDetail,
+}
+
+/**
+ * 400 < status < 599
+ * and error NOT documented
+ * undefined behavior on this responses
+ */
+export interface HttpUnkownFail {
+ type: "fail-unknown",
+ url: URL;
+ status: HttpStatusCode;
+
+ // read from the body if exist
+ detail?: TalerErrorDetail;
+ body?: string;
+}
+
+export async function knownFailure<T extends string>(s: T, resp: HttpResponse): Promise<HttpKnownFail<T>> {
+ const detail = await readTalerErrorResponse(resp)
+ return { type: "fail", case: s, detail }
+}
+export async function httpSuccess<T>(resp: HttpResponse, codec: Codec<T>): Promise<HttpOk<T>> {
+ const body = await readSuccessResponseJsonOrThrow(resp, codec)
+ return { type: "ok" as const, body }
+}
+export function httpEmptySuccess(): HttpOk<void> {
+ return { type: "ok" as const, body: void 0 }
+}
+export async function unknownFailure(url: URL, resp: HttpResponse): Promise<HttpUnkownFail> {
+ if (resp.status >= 400 && resp.status < 500) {
+ const detail = await readTalerErrorResponse(resp)
+ return { type: "fail-unknown", url, status: resp.status, detail }
+ } else {
+ const { detail, body } = await readErrorResponse(resp)
+ return { type: "fail-unknown", url, status: resp.status, detail, body }
+ }
+}
diff --git a/packages/taler-util/src/http-common.ts b/packages/taler-util/src/http-common.ts
index da2fbb9da..817f2367f 100644
--- a/packages/taler-util/src/http-common.ts
+++ b/packages/taler-util/src/http-common.ts
@@ -180,6 +180,37 @@ export async function readTalerErrorResponse(
return errJson;
}
+export async function readErrorResponse(
+ httpResponse: HttpResponse,
+): Promise<{ detail: TalerErrorDetail | undefined, body: string }> {
+ let errString: string;
+ try {
+ errString = await httpResponse.text();
+ } catch (e: any) {
+ throw TalerError.fromDetail(
+ TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE,
+ {
+ requestUrl: httpResponse.requestUrl,
+ requestMethod: httpResponse.requestMethod,
+ httpStatusCode: httpResponse.status,
+ validationError: e.toString(),
+ },
+ "Couldn't parse JSON format from error response",
+ );
+ }
+ let errJson;
+ try {
+ errJson = JSON.parse(errString)
+ } catch (e) {
+ errJson = undefined
+ }
+
+ const talerErrorCode = errJson && errJson.code;
+ if (typeof talerErrorCode === "number") {
+ return { detail: errJson, body: errString }
+ }
+ return { detail: undefined, body: errString };
+}
export async function readUnexpectedResponseDetails(
httpResponse: HttpResponse,
): Promise<TalerErrorDetail> {