Как я могу написать пользовательский класс ошибок, для которого потребуется свойство, если существует определенный аргумент?

#javascript #node.js #typescript #types #typescript-generics

#javascript #node.js #typescript #типы #typescript-generics

Вопрос:

Я создаю пользовательский класс ошибок, подобный этому:

 import { OperationalError } from './baseErrors';

type ErrorProps = {
  message: string;
  paymentProvider: 'stripe' | 'braintree';
  failedChargeData: {
    errorMessage: string;
    stripePaymentIntentId?: string;
    braintreeChargeId?: string;
    eventType: string;
  };
};

export class FailedPaymentError extends OperationalError {
  constructor({ message, paymentProvider, failedChargeData }: ErrorProps) {
    const properties = { failedChargeData, paymentProvider };
    super(message, properties, 400);
  }
}
  

Однако я хочу улучшить это.

Я хочу написать какой — нибудь условный тип , чтобы if paymentProvider === 'stripe' , then ErrorProps требовал a stripePaymentIntentId . Принимая во внимание, что если paymentProvider === 'braintree' , то braintreeChargeId требуется a.

Я думаю, мне нужно сделать что-то подобное?

 import { OperationalError } from './baseErrors';

type PaymentProvider = 'stripe' | 'braintree';
type ErrorProps<T> = {
  message: string;
  paymentProvider: T;
  failedChargeData: T extends 'stripe'
    ? {
        errorMessage: string;
        stripePaymentIntentId: string;
        eventType: string;
      }
    : {
        errorMessage: string;
        braintreeChargeId: string;
        eventType: string;
      };
};

export class FailedPaymentError extends OperationalError {
  constructor({
    message,
    paymentProvider,
    failedChargeData,
  }: ErrorProps<PaymentProvider>) {
    const properties = { failedChargeData, paymentProvider };
    super(message, properties, 400);
  }
}
  

Таким образом, я могу затем выдать ошибку следующим образом:

 const failedChargeData = {
  errorMessage: message,
  stripePaymentIntentId: e.type amp;amp; e.requestId,
  eventType: e.type || 'unexpectedFailedPayment',
};

throw new FailedPaymentError({
  message,
  paymentProvider: 'stripe',
  failedChargeData,
});
  

Но это не совсем работает. TS не жалуется, если я прохожу через неправильного поставщика платежей. Кроме того, он не особенно СУХОЙ.

Это ДЕЙСТВИТЕЛЬНО работает, но несколько строк кажутся излишне дублированными:

 type ErrorProps =
  | {
      message: string;
      paymentProvider: 'stripe';
      failedChargeData: {
        errorMessage: string;
        stripePaymentIntentId: string;
        eventType: string;
      };
    }
  | {
      message: string;
      paymentProvider: 'braintree';
      failedChargeData: {
        errorMessage: string;
        braintreeChargeId: string;
        eventType: string;
      };
    };
  

Как я могу это сделать? Спасибо

Ответ №1:

Различаемое объединение в вашем последнем примере абсолютно правильное.

Вы можете удалить его, извлекая различаемые типы из базового интерфейса.

 type MapErrorProps<T, K> = {
  message: string;
  paymentProvider: T;
  failedChargeData: {
    errorMessage: string;
    eventType: string;
  } amp; K;
};

// Map each type to a required data interface
type ErrorProps = MapErrorProps<"stripe", { stripePaymentIntentId: string }>
  | MapErrorProps<"braintree", { braintreeChargeId: string }>;
  | MapErrorProps<"iceCream", { flavor: string }>;