fomenkogregory
@fomenkogregory
Юниор софтварный инженер

Как создать обертку вокруг HttpClient?

Пытаюсь создать обертку над HttpClient, но не получается нормально описать типы.
У HttpClient по 15 оверлоадов на каждый метод (https://angular.io/api/common/http/HttpClient)
Поскольку они очень похожи, я решил вынести одинаковый контент в общий тип и сделал следующее:
type Observe = 'body' | 'events' | 'response';
type ResponseType = 'arraybuffer' | 'blob' | 'text' | 'json';

interface Params<O extends Observe, R extends ResponseType> {
  url: string;
  body: any | null;
  options?: {
    headers?:
      | HttpHeaders
      | {
          [header: string]: string | string[];
        };
    observe?: O;
    params?:
      | HttpParams
      | {
          [param: string]: string | string[];
        };
    reportProgress?: boolean;
    responseType: R;
    withCredentials?: boolean;
  };
}
export class MyHttpClient {
  constructor(private readonly http: HttpClient) {}

  put(params: Params<'body', 'arraybuffer'>): Observable<ArrayBuffer>; // This overload signature is not compatible with its implementation signature.
  put(params: Params<'body', 'blob'>): Observable<Blob>;
  put(params: Params<'body', 'text'>): Observable<string>;
  put(params: Params<'events', 'arraybuffer'>): Observable<HttpEvent<ArrayBuffer>>;
  put(params: Params<'events', 'blob'>): Observable<HttpEvent<Blob>>;
  put(params: Params<'events', 'text'>): Observable<HttpEvent<string>>;
  put(params: Params<'events', 'json'>): Observable<HttpEvent<Object>>;
  put<T>(params: Params<'events', 'json'>): Observable<HttpEvent<T>>;
  put(params: Params<'response', 'arraybuffer'>): Observable<HttpResponse<ArrayBuffer>>;
  put(params: Params<'response', 'blob'>): Observable<HttpResponse<Blob>>;
  put(params: Params<'response', 'text'>): Observable<HttpResponse<string>>;
  put(params: Params<'response', 'json'>): Observable<HttpResponse<Object>>;
  put<T>(params: Params<'response', 'json'>): Observable<HttpResponse<T>>;
  put(params: Params<'body', 'json'>): Observable<Object>;
  put<T>(params: Params<'body', 'json'>): Observable<T> {
    return this.http.put<T>(params.url, params.body, params.options);
  }
}

Все оверлоады в той же последовательности как в оригинальном клиенте
Но тайпскрипт считает типы несопоставимыми (This overload signature is not compatible with its implementation signature.)

Есть ли у кого-то опыт создания такой или похожей обертки? Возможно ли это сделать сохранив все типы? Может быть еще какие-то советы относительно того, что я пытаюсь сделать... Хелп
  • Вопрос задан
  • 65 просмотров
Решения вопроса 1
bingo347
@bingo347
Ткнуть в доку лучше готового к копипасте ответа
У реализации должна быть наиболее общая сигнатура от всех перегрузок. Притом типы в реализации работают только внутрь, а наружу торчат только перегрузки:
export class MyHttpClient {
  constructor(private readonly http: HttpClient) {}

  put(params: Params<'body', 'arraybuffer'>): Observable<ArrayBuffer>;
  put(params: Params<'body', 'blob'>): Observable<Blob>;
  put(params: Params<'body', 'text'>): Observable<string>;
  put(params: Params<'events', 'arraybuffer'>): Observable<HttpEvent<ArrayBuffer>>;
  put(params: Params<'events', 'blob'>): Observable<HttpEvent<Blob>>;
  put(params: Params<'events', 'text'>): Observable<HttpEvent<string>>;
  put(params: Params<'events', 'json'>): Observable<HttpEvent<Object>>;
  put<T>(params: Params<'events', 'json'>): Observable<HttpEvent<T>>;
  put(params: Params<'response', 'arraybuffer'>): Observable<HttpResponse<ArrayBuffer>>;
  put(params: Params<'response', 'blob'>): Observable<HttpResponse<Blob>>;
  put(params: Params<'response', 'text'>): Observable<HttpResponse<string>>;
  put(params: Params<'response', 'json'>): Observable<HttpResponse<Object>>;
  put<T>(params: Params<'response', 'json'>): Observable<HttpResponse<T>>;
  put(params: Params<'body', 'json'>): Observable<Object>;
  put(params: Params<Observe, ResponseType>): Observable<unknown> {
    return this.http.put(params.url, params.body, params.options as Params<'body', 'json'>['options']);
  }
}


А вообще, мне почему-то кажется, что так будет гораздо проще:
type ResponseTypeMap = {
  arraybuffer: ArrayBuffer;
  blob: Blob;
  text: string;
  json: Object;
};
type ResponseTypes = keyof ResponseTypeMap;
type ObserveWrapperMap<T extends ResponseTypeMap[ResponseTypes]> = {
  body: Observable<T>;
  events: Observable<HttpEvent<T>>;
  response: Observable<HttpResponse<T>>;
};
type ObserveWrappers = keyof ObserveWrapperMap<ResponseTypeMap[ResponseTypes]>;
type Result<O extends ObserveWrappers, R extends ResponseTypes> = ObserveWrapperMap<ResponseTypeMap[R]>[O];
type Params<O extends ObserveWrappers, R extends ResponseTypes> = {
  url: string;
  body: any;
  options?: {
    headers?: HttpHeaders | Record<string, string | string[]>;
    observe?: O;
    params?: HttpParams | Record<string, string | string[]>;
    reportProgress?: boolean;
    responseType: R;
    withCredentials?: boolean;
  };
}

export class MyHttpClient {
  constructor(private readonly http: HttpClient) {}

  put<O extends ObserveWrappers = 'body', R extends ResponseTypes = 'json'>(params: Params<O, R>): Result<O, R> {
    const {url, body, options} = params as Params<'body', 'json'>;
    return this.http.put(url, body, options) as Result<O, R>;
  }
}
особенно если помимо put будут и другие методы
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы