gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] 01/02: add exchange key in the http lib and default


From: gnunet
Subject: [taler-wallet-core] 01/02: add exchange key in the http lib and default toString for taler error
Date: Thu, 15 Feb 2024 21:00:57 +0100

This is an automated email from the git hooks/post-receive script.

sebasjm pushed a commit to branch master
in repository wallet-core.

commit fd45d189259cef0a3a51683bb12412cd8c6fb9eb
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Thu Feb 15 16:59:56 2024 -0300

    add exchange key in the http lib and default toString for taler error
---
 packages/taler-util/src/errors.ts               |  17 +-
 packages/taler-util/src/http-client/exchange.ts |  20 +-
 packages/taler-util/src/http-client/types.ts    | 424 +++++++++++++++++++++++-
 packages/taler-util/src/http-impl.node.ts       |   7 +-
 packages/taler-util/src/logging.ts              |   8 +-
 packages/taler-util/src/operation.ts            |  26 +-
 packages/taler-util/src/transactions-types.ts   |   5 +-
 7 files changed, 488 insertions(+), 19 deletions(-)

diff --git a/packages/taler-util/src/errors.ts 
b/packages/taler-util/src/errors.ts
index 69990d41f..3370825f4 100644
--- a/packages/taler-util/src/errors.ts
+++ b/packages/taler-util/src/errors.ts
@@ -222,9 +222,11 @@ export type TalerHttpError =
 
 export class TalerError<T = any> extends Error {
   errorDetail: TalerErrorDetail & T;
-  private constructor(d: TalerErrorDetail & T) {
+  cause: Error | undefined;
+  private constructor(d: TalerErrorDetail & T, cause?: Error) {
     super(d.hint ?? `Error (code ${d.code})`);
     this.errorDetail = d;
+    this.cause = cause;
     Object.setPrototypeOf(this, TalerError.prototype);
   }
 
@@ -232,21 +234,22 @@ export class TalerError<T = any> extends Error {
     code: C,
     detail: ErrBody<C>,
     hint?: string,
+    cause?: Error,
   ): TalerError {
     if (!hint) {
       hint = getDefaultHint(code);
     }
     const when = AbsoluteTime.now();
-    return new TalerError<unknown>({ code, when, hint, ...detail });
+    return new TalerError<unknown>({ code, when, hint, ...detail }, cause);
   }
 
-  static fromUncheckedDetail(d: TalerErrorDetail): TalerError {
-    return new TalerError<unknown>({ ...d });
+  static fromUncheckedDetail(d: TalerErrorDetail, c?: Error): TalerError {
+    return new TalerError<unknown>({ ...d }, c);
   }
 
   static fromException(e: any): TalerError {
     const errDetail = getErrorDetailFromException(e);
-    return new TalerError(errDetail);
+    return new TalerError(errDetail, e);
   }
 
   hasErrorCode<C extends keyof DetailsMap>(
@@ -254,6 +257,10 @@ export class TalerError<T = any> extends Error {
   ): this is TalerError<DetailsMap[C]> {
     return this.errorDetail.code === code;
   }
+
+  toString(): string {
+    return `TalerError: ${JSON.stringify(this.errorDetail)}`;
+  }
 }
 
 /**
diff --git a/packages/taler-util/src/http-client/exchange.ts 
b/packages/taler-util/src/http-client/exchange.ts
index 726c28204..003410ddb 100644
--- a/packages/taler-util/src/http-client/exchange.ts
+++ b/packages/taler-util/src/http-client/exchange.ts
@@ -31,6 +31,7 @@ import {
   codecForAmlDecisionDetails,
   codecForAmlRecords,
   codecForExchangeConfig,
+  codecForExchangeKeys,
 } from "./types.js";
 import { addPaginationParams } from "./utils.js";
 
@@ -59,7 +60,7 @@ export class TalerExchangeHttpClient {
     return compare?.compatible ?? false;
   }
   /**
-   * https://docs.taler.net/core/api-merchant.html#get--config
+   * https://docs.taler.net/core/api-exchange.html#get--config
    *
    */
   async getConfig() {
@@ -74,6 +75,23 @@ export class TalerExchangeHttpClient {
         return opUnknownFailure(resp, await resp.text());
     }
   }
+  /**
+   * https://docs.taler.net/core/api-merchant.html#get--config
+   *
+   * PARTIALLY IMPLEMENTED!!
+   */
+  async getKeys() {
+    const url = new URL(`keys`, this.baseUrl);
+    const resp = await this.httpLib.fetch(url.href, {
+      method: "GET",
+    });
+    switch (resp.status) {
+      case HttpStatusCode.Ok:
+        return opSuccess(resp, codecForExchangeKeys());
+      default:
+        return opUnknownFailure(resp, await resp.text());
+    }
+  }
 
   // TERMS
 
diff --git a/packages/taler-util/src/http-client/types.ts 
b/packages/taler-util/src/http-client/types.ts
index 6c8bf4efd..05fce4a49 100644
--- a/packages/taler-util/src/http-client/types.ts
+++ b/packages/taler-util/src/http-client/types.ts
@@ -243,6 +243,7 @@ export interface CurrencySpecification {
   alt_unit_names: { [log10: string]: string };
 }
 
+//FIXME: implement this codec
 export const codecForAccessToken = codecForString as () => Codec<AccessToken>;
 export const codecForTokenSuccessResponse =
   (): Codec<TalerAuthentication.TokenSuccessResponse> =>
@@ -294,6 +295,9 @@ export const codecForCoreBankConfig = (): 
Codec<TalerCorebankApi.Config> =>
     .property("wire_type", codecForString())
     .build("TalerCorebankApi.Config");
 
+//FIXME: implement this codec
+export const codecForURN = codecForString;
+
 export const codecForMerchantConfig =
   (): Codec<TalerMerchantApi.VersionResponse> =>
     buildCodecForObject<TalerMerchantApi.VersionResponse>()
@@ -308,11 +312,20 @@ export const codecForExchangeConfig =
     buildCodecForObject<TalerExchangeApi.ExchangeVersionResponse>()
       .property("version", codecForString())
       .property("name", codecForConstString("taler-exchange"))
+      .property("implementation", codecOptional(codecForURN()))
       .property("currency", codecForString())
       .property("currency_specification", codecForCurrencySpecificiation())
       .property("supported_kyc_requirements", codecForList(codecForString()))
       .build("TalerExchangeApi.ExchangeVersionResponse");
 
+export const codecForExchangeKeys =
+  (): Codec<TalerExchangeApi.ExchangeKeysResponse> =>
+    buildCodecForObject<TalerExchangeApi.ExchangeKeysResponse>()
+      .property("version", codecForString())
+      .property("base_url", codecForString())
+      .property("currency", codecForString())
+      .build("TalerExchangeApi.ExchangeKeysResponse");
+
 const codecForBalance = (): Codec<TalerCorebankApi.Balance> =>
   buildCodecForObject<TalerCorebankApi.Balance>()
     .property("amount", codecForAmountString())
@@ -867,6 +880,8 @@ type Base32 = string;
 
 type DecimalNumber = string;
 type RsaSignature = string;
+type Float = number;
+type LibtoolVersion = string;
 // The type of a coin's blinded envelope depends on the cipher that is used
 // for signing with a denomination key.
 type CoinEnvelope = RSACoinEnvelope | CSCoinEnvelope;
@@ -891,9 +906,13 @@ interface CSCoinEnvelope {
 // a 256-bit nonce, converted to Crockford Base32.
 type DenominationBlindingKeyP = string;
 
+//FIXME: implement this codec
 const codecForURL = codecForString;
+//FIXME: implement this codec
 const codecForLibtoolVersion = codecForString;
+//FIXME: implement this codec
 const codecForCurrencyName = codecForString;
+//FIXME: implement this codec
 const codecForDecimalNumber = codecForString;
 
 export type WithdrawalOperationStatus =
@@ -1892,7 +1911,12 @@ export namespace TalerExchangeApi {
     // Name of the protocol.
     name: "taler-exchange";
 
-    // Currency supported by this exchange.
+    // URN of the implementation (needed to interpret 'revision' in version).
+    // @since v18, may become mandatory in the future.
+    implementation?: string;
+
+    // Currency supported by this exchange, given
+    // as a currency code ("USD" or "EUR").
     currency: string;
 
     // How wallets should render this currency.
@@ -1959,6 +1983,404 @@ export namespace TalerExchangeApi {
     // with purpose TALER_SIGNATURE_MASTER_WIRE_DETAILS.
     master_sig: EddsaSignature;
   }
+
+  export interface ExchangeKeysResponse {
+    // libtool-style representation of the Exchange protocol version, see
+    // 
https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning
+    // The format is "current:revision:age".
+    version: string;
+
+    // The exchange's base URL.
+    base_url: string;
+
+    // The exchange's currency or asset unit.
+    currency: string;
+
+    /**
+     * FIXME: PARTIALLY IMPLEMENTED!!
+     */
+
+    // How wallets should render this currency.
+    // currency_specification: CurrencySpecification;
+
+    // // Absolute cost offset for the STEFAN curve used
+    // // to (over) approximate fees payable by amount.
+    // stefan_abs: AmountString;
+
+    // // Factor to multiply the logarithm of the amount
+    // // with to (over) approximate fees payable by amount.
+    // // Note that the total to be paid is first to be
+    // // divided by the smallest denomination to obtain
+    // // the value that the logarithm is to be taken of.
+    // stefan_log: AmountString;
+
+    // // Linear cost factor for the STEFAN curve used
+    // // to (over) approximate fees payable by amount.
+    // //
+    // // Note that this is a scalar, as it is multiplied
+    // // with the actual amount.
+    // stefan_lin: Float;
+
+    // // Type of the asset. "fiat", "crypto", "regional"
+    // // or "stock".  Wallets should adjust their UI/UX
+    // // based on this value.
+    // asset_type: string;
+
+    // // Array of wire accounts operated by the exchange for
+    // // incoming wire transfers.
+    // accounts: WireAccount[];
+
+    // // Object mapping names of wire methods (i.e. "iban" or "x-taler-bank")
+    // // to wire fees.
+    // wire_fees: { method: AggregateTransferFee[] };
+
+    // // List of exchanges that this exchange is partnering
+    // // with to enable wallet-to-wallet transfers.
+    // wads: ExchangePartner[];
+
+    // // Set to true if this exchange allows the use
+    // // of reserves for rewards.
+    // // @deprecated in protocol v18.
+    // rewards_allowed: false;
+
+    // // EdDSA master public key of the exchange, used to sign entries
+    // // in denoms and signkeys.
+    // master_public_key: EddsaPublicKey;
+
+    // // Relative duration until inactive reserves are closed;
+    // // not signed (!), can change without notice.
+    // reserve_closing_delay: RelativeTime;
+
+    // // Threshold amounts beyond which wallet should
+    // // trigger the KYC process of the issuing
+    // // exchange.  Optional option, if not given there is no limit.
+    // // Currency must match currency.
+    // wallet_balance_limit_without_kyc?: AmountString[];
+
+    // // Denominations offered by this exchange
+    // denominations: DenomGroup[];
+
+    // // Compact EdDSA signature (binary-only) over the
+    // // contatentation of all of the master_sigs (in reverse
+    // // chronological order by group) in the arrays under
+    // // "denominations".  Signature of TALER_ExchangeKeySetPS
+    // exchange_sig: EddsaSignature;
+
+    // // Public EdDSA key of the exchange that was used to generate the 
signature.
+    // // Should match one of the exchange's signing keys from signkeys.  It 
is given
+    // // explicitly as the client might otherwise be confused by clock skew 
as to
+    // // which signing key was used for the exchange_sig.
+    // exchange_pub: EddsaPublicKey;
+
+    // // Denominations for which the exchange currently offers/requests 
recoup.
+    // recoup: Recoup[];
+
+    // // Array of globally applicable fees by time range.
+    // global_fees: GlobalFees[];
+
+    // // The date when the denomination keys were last updated.
+    // list_issue_date: Timestamp;
+
+    // // Auditors of the exchange.
+    // auditors: AuditorKeys[];
+
+    // // The exchange's signing keys.
+    // signkeys: SignKey[];
+
+    // // Optional field with a dictionary of (name, object) pairs defining the
+    // // supported and enabled extensions, such as age_restriction.
+    // extensions?: { name: ExtensionManifest };
+
+    // // Signature by the exchange master key of the SHA-256 hash of the
+    // // normalized JSON-object of field extensions, if it was set.
+    // // The signature has purpose TALER_SIGNATURE_MASTER_EXTENSIONS.
+    // extensions_sig?: EddsaSignature;
+  }
+
+  interface ExtensionManifest {
+    // The criticality of the extension MUST be provided.  It has the same
+    // semantics as "critical" has for extensions in X.509:
+    // - if "true", the client must "understand" the extension before
+    //   proceeding,
+    // - if "false", clients can safely skip extensions they do not
+    //   understand.
+    // (see https://datatracker.ietf.org/doc/html/rfc5280#section-4.2)
+    critical: boolean;
+
+    // The version information MUST be provided in Taler's protocol version
+    // ranges notation, see
+    // https://docs.taler.net/core/api-common.html#protocol-version-ranges
+    version: LibtoolVersion;
+
+    // Optional configuration object, defined by the feature itself
+    config?: object;
+  }
+
+  interface SignKey {
+    // The actual exchange's EdDSA signing public key.
+    key: EddsaPublicKey;
+
+    // Initial validity date for the signing key.
+    stamp_start: Timestamp;
+
+    // Date when the exchange will stop using the signing key, allowed to 
overlap
+    // slightly with the next signing key's validity to allow for clock skew.
+    stamp_expire: Timestamp;
+
+    // Date when all signatures made by the signing key expire and should
+    // henceforth no longer be considered valid in legal disputes.
+    stamp_end: Timestamp;
+
+    // Signature over key and stamp_expire by the exchange master key.
+    // Signature of TALER_ExchangeSigningKeyValidityPS.
+    // Must have purpose TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY.
+    master_sig: EddsaSignature;
+  }
+
+  interface AuditorKeys {
+    // The auditor's EdDSA signing public key.
+    auditor_pub: EddsaPublicKey;
+
+    // The auditor's URL.
+    auditor_url: string;
+
+    // The auditor's name (for humans).
+    auditor_name: string;
+
+    // An array of denomination keys the auditor affirms with its signature.
+    // Note that the message only includes the hash of the public key, while 
the
+    // signature is actually over the expanded information including expiration
+    // times and fees.  The exact format is described below.
+    denomination_keys: AuditorDenominationKey[];
+  }
+  interface AuditorDenominationKey {
+    // Hash of the public RSA key used to sign coins of the respective
+    // denomination.  Note that the auditor's signature covers more than just
+    // the hash, but this other information is already provided in denoms and
+    // thus not repeated here.
+    denom_pub_h: HashCode;
+
+    // Signature of TALER_ExchangeKeyValidityPS.
+    auditor_sig: EddsaSignature;
+  }
+
+  interface GlobalFees {
+    // What date (inclusive) does these fees go into effect?
+    start_date: Timestamp;
+
+    // What date (exclusive) does this fees stop going into effect?
+    end_date: Timestamp;
+
+    // Account history fee, charged when a user wants to
+    // obtain a reserve/account history.
+    history_fee: AmountString;
+
+    // Annual fee charged for having an open account at the
+    // exchange.  Charged to the account.  If the account
+    // balance is insufficient to cover this fee, the account
+    // is automatically deleted/closed. (Note that the exchange
+    // will keep the account history around for longer for
+    // regulatory reasons.)
+    account_fee: AmountString;
+
+    // Purse fee, charged only if a purse is abandoned
+    // and was not covered by the account limit.
+    purse_fee: AmountString;
+
+    // How long will the exchange preserve the account history?
+    // After an account was deleted/closed, the exchange will
+    // retain the account history for legal reasons until this time.
+    history_expiration: RelativeTime;
+
+    // Non-negative number of concurrent purses that any
+    // account holder is allowed to create without having
+    // to pay the purse_fee.
+    purse_account_limit: Integer;
+
+    // How long does an exchange keep a purse around after a purse
+    // has expired (or been successfully merged)?  A 'GET' request
+    // for a purse will succeed until the purse expiration time
+    // plus this value.
+    purse_timeout: RelativeTime;
+
+    // Signature of TALER_GlobalFeesPS.
+    master_sig: EddsaSignature;
+  }
+
+  interface Recoup {
+    // Hash of the public key of the denomination that is being revoked under
+    // emergency protocol (see /recoup).
+    h_denom_pub: HashCode;
+
+    // We do not include any signature here, as the primary use-case for
+    // this emergency involves the exchange having lost its signing keys,
+    // so such a signature here would be pretty worthless.  However, the
+    // exchange will not honor /recoup requests unless they are for
+    // denomination keys listed here.
+  }
+
+  interface AggregateTransferFee {
+    // Per transfer wire transfer fee.
+    wire_fee: AmountString;
+
+    // Per transfer closing fee.
+    closing_fee: AmountString;
+
+    // What date (inclusive) does this fee go into effect?
+    // The different fees must cover the full time period in which
+    // any of the denomination keys are valid without overlap.
+    start_date: Timestamp;
+
+    // What date (exclusive) does this fee stop going into effect?
+    // The different fees must cover the full time period in which
+    // any of the denomination keys are valid without overlap.
+    end_date: Timestamp;
+
+    // Signature of TALER_MasterWireFeePS with
+    // purpose TALER_SIGNATURE_MASTER_WIRE_FEES.
+    sig: EddsaSignature;
+  }
+
+  interface ExchangePartner {
+    // Base URL of the partner exchange.
+    partner_base_url: string;
+
+    // Public master key of the partner exchange.
+    partner_master_pub: EddsaPublicKey;
+
+    // Per exchange-to-exchange transfer (wad) fee.
+    wad_fee: AmountString;
+
+    // Exchange-to-exchange wad (wire) transfer frequency.
+    wad_frequency: RelativeTime;
+
+    // When did this partnership begin (under these conditions)?
+    start_date: Timestamp;
+
+    // How long is this partnership expected to last?
+    end_date: Timestamp;
+
+    // Signature using the exchange's offline key over
+    // TALER_WadPartnerSignaturePS
+    // with purpose TALER_SIGNATURE_MASTER_PARTNER_DETAILS.
+    master_sig: EddsaSignature;
+  }
+
+  type DenomGroup =
+    | DenomGroupRsa
+    | DenomGroupCs
+    | DenomGroupRsaAgeRestricted
+    | DenomGroupCsAgeRestricted;
+  interface DenomGroupRsa extends DenomGroupCommon {
+    cipher: "RSA";
+
+    denoms: ({
+      rsa_pub: RsaPublicKey;
+    } & DenomCommon)[];
+  }
+  interface DenomGroupCs extends DenomGroupCommon {
+    cipher: "CS";
+
+    denoms: ({
+      cs_pub: Cs25519Point;
+    } & DenomCommon)[];
+  }
+
+  // Binary representation of the age groups.
+  // The bits set in the mask mark the edges at the beginning of a next age
+  // group.  F.e. for the age groups
+  //     0-7, 8-9, 10-11, 12-13, 14-15, 16-17, 18-21, 21-*
+  // the following bits are set:
+  //
+  //   31     24        16        8         0
+  //   |      |         |         |         |
+  //   oooooooo  oo1oo1o1  o1o1o1o1  ooooooo1
+  //
+  // A value of 0 means that the exchange does not support the extension for
+  // age-restriction.
+  type AgeMask = Integer;
+
+  interface DenomGroupRsaAgeRestricted extends DenomGroupCommon {
+    cipher: "RSA+age_restricted";
+    age_mask: AgeMask;
+
+    denoms: ({
+      rsa_pub: RsaPublicKey;
+    } & DenomCommon)[];
+  }
+  interface DenomGroupCsAgeRestricted extends DenomGroupCommon {
+    cipher: "CS+age_restricted";
+    age_mask: AgeMask;
+
+    denoms: ({
+      cs_pub: Cs25519Point;
+    } & DenomCommon)[];
+  }
+  // Common attributes for all denomination groups
+  interface DenomGroupCommon {
+    // How much are coins of this denomination worth?
+    value: AmountString;
+
+    // Fee charged by the exchange for withdrawing a coin of this denomination.
+    fee_withdraw: AmountString;
+
+    // Fee charged by the exchange for depositing a coin of this denomination.
+    fee_deposit: AmountString;
+
+    // Fee charged by the exchange for refreshing a coin of this denomination.
+    fee_refresh: AmountString;
+
+    // Fee charged by the exchange for refunding a coin of this denomination.
+    fee_refund: AmountString;
+  }
+  interface DenomCommon {
+    // Signature of TALER_DenominationKeyValidityPS.
+    master_sig: EddsaSignature;
+
+    // When does the denomination key become valid?
+    stamp_start: Timestamp;
+
+    // When is it no longer possible to withdraw coins
+    // of this denomination?
+    stamp_expire_withdraw: Timestamp;
+
+    // When is it no longer possible to deposit coins
+    // of this denomination?
+    stamp_expire_deposit: Timestamp;
+
+    // Timestamp indicating by when legal disputes relating to these coins must
+    // be settled, as the exchange will afterwards destroy its evidence 
relating to
+    // transactions involving this coin.
+    stamp_expire_legal: Timestamp;
+
+    // Set to 'true' if the exchange somehow "lost"
+    // the private key. The denomination was not
+    // necessarily revoked, but still cannot be used
+    // to withdraw coins at this time (theoretically,
+    // the private key could be recovered in the
+    // future; coins signed with the private key
+    // remain valid).
+    lost?: boolean;
+  }
+  type DenominationKey = RsaDenominationKey | CSDenominationKey;
+  interface RsaDenominationKey {
+    cipher: "RSA";
+
+    // 32-bit age mask.
+    age_mask: Integer;
+
+    // RSA public key
+    rsa_public_key: RsaPublicKey;
+  }
+  interface CSDenominationKey {
+    cipher: "CS";
+
+    // 32-bit age mask.
+    age_mask: Integer;
+
+    // Public key of the denomination.
+    cs_public_key: Cs25519Point;
+  }
 }
 
 export namespace TalerMerchantApi {
diff --git a/packages/taler-util/src/http-impl.node.ts 
b/packages/taler-util/src/http-impl.node.ts
index 8ca2deecd..dec4e3f31 100644
--- a/packages/taler-util/src/http-impl.node.ts
+++ b/packages/taler-util/src/http-impl.node.ts
@@ -21,7 +21,12 @@
  */
 import * as net from "node:net";
 import type { ClientRequest, IncomingMessage } from "node:http";
-import { FollowOptions, RedirectableRequest, http, https } from 
"follow-redirects";
+import {
+  FollowOptions,
+  RedirectableRequest,
+  http,
+  https,
+} from "follow-redirects";
 import { RequestOptions } from "node:http";
 import { TalerError } from "./errors.js";
 import { encodeBody, getDefaultHeaders, HttpLibArgs } from "./http-common.js";
diff --git a/packages/taler-util/src/logging.ts 
b/packages/taler-util/src/logging.ts
index 663bc59c8..17bb184f7 100644
--- a/packages/taler-util/src/logging.ts
+++ b/packages/taler-util/src/logging.ts
@@ -37,7 +37,6 @@ const byTagLogLevel: Record<string, LogLevel> = {};
 
 let nativeLogging: boolean = false;
 
-
 // from 
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/toString
 Error.prototype.toString = function () {
   if (
@@ -51,14 +50,13 @@ Error.prototype.toString = function () {
   let msg = this.message;
   msg = msg === undefined ? "" : `${msg}`;
 
-  let cause = ""
+  let cause = "";
   if ("cause" in this) {
-    cause = `\n Caused by: ${this.cause}`
+    cause = `\n Caused by: ${this.cause}`;
   }
   return `${name}: ${msg}${cause}`;
 };
 
-
 export function getGlobalLogLevel(): string {
   return globalLogLevel;
 }
@@ -148,7 +146,7 @@ function writeNodeLog(
  * and uses the corresponding console.* method to log in the browser.
  */
 export class Logger {
-  constructor(private tag: string) { }
+  constructor(private tag: string) {}
 
   shouldLogTrace(): boolean {
     const level = byTagLogLevel[this.tag] ?? globalLogLevel;
diff --git a/packages/taler-util/src/operation.ts 
b/packages/taler-util/src/operation.ts
index a554e1f31..02cf70196 100644
--- a/packages/taler-util/src/operation.ts
+++ b/packages/taler-util/src/operation.ts
@@ -30,10 +30,14 @@ import {
   TalerErrorDetail,
 } from "./index.js";
 
-export type OperationResult<Body, ErrorEnum> =
+type OperationFailWithBodyOrNever<ErrorEnum, ErrorMap> =
+  ErrorEnum extends keyof ErrorMap ? OperationFailWithBody<ErrorMap> : never;
+
+export type OperationResult<Body, ErrorEnum, K = never> =
   | OperationOk<Body>
-  | OperationAlternative<ErrorEnum, Body>
-  | OperationFail<ErrorEnum>;
+  | OperationAlternative<ErrorEnum, any>
+  | OperationFail<ErrorEnum>
+  | OperationFailWithBodyOrNever<ErrorEnum, K>;
 
 export function isOperationOk<T, E>(
   c: OperationResult<T, E>,
@@ -89,6 +93,15 @@ export interface OperationAlternative<T, B> {
   body: B;
 }
 
+export interface OperationFailWithBody<B> {
+  type: "fail";
+
+  httpResp: HttpResponse;
+
+  case: keyof B;
+  body: B[OperationFailWithBody<B>["case"]];
+}
+
 export async function opSuccess<T>(
   resp: HttpResponse,
   codec: Codec<T>,
@@ -109,6 +122,13 @@ export function opEmptySuccess(resp: HttpResponse): 
OperationOk<void> {
   return { type: "ok" as const, body: void 0, httpResp: resp };
 }
 
+export async function opKnownFailureWithBody<B>(
+  case_: keyof B,
+  body: B[typeof case_],
+): Promise<OperationFailWithBody<B>> {
+  return { type: "fail", case: case_, body, httpResp: {} as any };
+}
+
 export async function opKnownAlternativeFailure<T extends HttpStatusCode, B>(
   resp: HttpResponse,
   s: T,
diff --git a/packages/taler-util/src/transactions-types.ts 
b/packages/taler-util/src/transactions-types.ts
index 3460d2d87..4754603e6 100644
--- a/packages/taler-util/src/transactions-types.ts
+++ b/packages/taler-util/src/transactions-types.ts
@@ -59,9 +59,9 @@ import {
 export interface TransactionsRequest {
   /**
    * return only transactions in the given currency
-   * 
+   *
    * it will be removed in next release
-   * 
+   *
    * @deprecated use scopeInfo
    */
   currency?: string;
@@ -88,7 +88,6 @@ export interface TransactionsRequest {
    */
   includeRefreshes?: boolean;
 
-
   filterByState?: TransactionStateFilter;
 }
 

-- 
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]