[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-wallet-core] branch master updated: bank api now return typed err
From: |
gnunet |
Subject: |
[taler-wallet-core] branch master updated: bank api now return typed errors for documented errors |
Date: |
Tue, 17 Oct 2023 16:18:05 +0200 |
This is an automated email from the git hooks/post-receive script.
sebasjm pushed a commit to branch master
in repository wallet-core.
The following commit(s) were added to refs/heads/master by this push:
new 503cbfbb9 bank api now return typed errors for documented errors
503cbfbb9 is described below
commit 503cbfbb95828677b83212816951eb501de2a8fe
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Tue Oct 17 11:17:18 2023 -0300
bank api now return typed errors for documented errors
---
packages/taler-util/src/errors.ts | 13 ++
packages/taler-util/src/http-client/bank-core.ts | 254 ++++++++++++++++++-----
packages/taler-util/src/http-client/index.ts | 0
packages/taler-util/src/http-client/utils.ts | 65 +++++-
packages/taler-util/src/http-common.ts | 31 +++
packages/web-util/src/utils/http-impl.sw.ts | 4 +-
6 files changed, 311 insertions(+), 56 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, codec [...]
-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 [...]
+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
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> {
diff --git a/packages/web-util/src/utils/http-impl.sw.ts
b/packages/web-util/src/utils/http-impl.sw.ts
index 5c15475ce..05696105c 100644
--- a/packages/web-util/src/utils/http-impl.sw.ts
+++ b/packages/web-util/src/utils/http-impl.sw.ts
@@ -148,7 +148,7 @@ function makeTextHandler(
requestUrl: string,
requestMethod: string,
) {
- return async function getJsonFromResponse(): Promise<any> {
+ return async function getTextFromResponse(): Promise<any> {
let respText;
try {
respText = await response.text();
@@ -160,7 +160,7 @@ function makeTextHandler(
requestMethod,
httpStatusCode: response.status,
},
- "Invalid JSON from HTTP response",
+ "Invalid text from HTTP response",
);
}
return respText;
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [taler-wallet-core] branch master updated: bank api now return typed errors for documented errors,
gnunet <=