@Leonardo-lavanda

Как описать обобщённый ответ с restful api сервера?

Я начал изучать typescript, для практики решил написать класс для создания api сущностей. вот так выглядит базовый класс.
export type ApiResponse =
  | { errors: Array<{ [key: string]: string }> }
  | { data: any }
  | { text: string };

type RequestData = {
  url?: string;
  method?: string;
  data?: any;
};

abstract class Api {
  protected readonly baseUrl: string;
  protected readonly endPoint: string;

  protected get url(): string {
    return new URL(this.endPoint, this.baseUrl).toString();
  }

  protected constructor(baseUrl = '/', endPoint: string) {
    this.baseUrl = baseUrl;
    this.endPoint = endPoint;
  }

  protected async http({
    url = '',
    method = 'GET',
    data = undefined,
  }: RequestData): Promise<ApiResponse> {
    try {
      const response: Response = await fetch(
        (new URL(url, this.url).toString()),
        { method, body: data }
      );
      return await response.json();
    } catch (e) {
      console.error(e.message);
      return { text: e.message };
    } finally {
      // finnally
    }
  }
}


Как в целом все должно работать: я создаю апи сущность, наследуюясь от класса Api, например создаю сущность Post от Api.
В чем состоит мой ступор: у меня есть метод для http запроса, который должен в зависимости от разных ситуаций возвращать либо массив с ошибками, либо объект с данными, либо ошибку запроса. Естественно, при проектировании базового класса предугадать нельзя, что может прийти в запросе в поле data, имею ли я право в этом случае в поле data ставить тип any?
export type ApiResponse =
  | { errors: Array<{ [key: string]: string }> }
  | { data: Array<any> }
  | { text: string };


В дальнейшем этот самый ответ я уточню создав сущность:

import Api, { ApiResponse } from "@/api/Api";


export type Post {
  userId: number;
  id: number;
  title: string;
  body: string;
}

class ApiPost extends Api {
  constructor(baseUrl: string, endPoint: string) {
    super(baseUrl, endPoint);
  }

  async list(count?: number): Promise<boolean | Array<Post>> {
    const response: ApiResponse = await this.http({
      url: `${count ? `?_limit=${count}` : ''}`,
    });

    if (Array.isArray(response))
      return response as Array<IPost>;

    return false;
  }
}

export default ApiPost;
  • Вопрос задан
  • 80 просмотров
Решения вопроса 1
bingo347
@bingo347
Ткнуть в доку лучше готового к копипасте ответа
Используйте дженерик параметры
export type ApiResponse<T> =
  | { errors: Array<{ [key: string]: string }> }
  | { data: Array<T> }
  | { text: string };

abstract class Api<T> {
  protected readonly baseUrl: string;
  protected readonly endPoint: string;

  protected get url(): string {
    return new URL(this.endPoint, this.baseUrl).toString();
  }

  protected constructor(baseUrl = '/', endPoint: string) {
    this.baseUrl = baseUrl;
    this.endPoint = endPoint;
  }

  protected async http({
    url = '',
    method = 'GET',
    data = undefined,
  }: RequestData): Promise<ApiResponse<T>> {
    try {
      const response: Response = await fetch(
        (new URL(url, this.url).toString()),
        { method, body: data }
      );
      return await response.json();
    } catch (e) {
      console.error(e.message);
      return { text: e.message };
    } finally {
      // finnally
    }
  }
}

class ApiPost extends Api<Post> {
  constructor(baseUrl: string, endPoint: string) {
    super(baseUrl, endPoint);
  }

  async list(count?: number): Promise<false | Array<Post>> {
    const response: ApiResponse = await this.http({
      url: `${count ? `?_limit=${count}` : ''}`,
    });

    if (Array.isArray(response))
      return response;

    return false;
  }
}
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

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

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