gnunet-svn
[Top][All Lists]
Advanced

[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.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]