@ws-serenity/api-requests
v2.10.2
Published
Based on axios library to perform API requests
Downloads
268
Keywords
Readme
Библиотека запросов к API
About
Библиотека позволяет выполнять запросы с runtime валидацией, используя описание объекта-валидатора. Основана на runtime-validator`е.
Использование
Настройка
Для начала необходимо настроить мапперы и передать их в apiWithMappers
. Библиотека содержит уже
несколько
стандартных мапперов. Они позволяют не создавать классы для "ручного маппинга" значений.
Самостоятельно проверяя значения на указанные правила и создавая нужный тип:
import { ApiDataHandler } from '@ws-serenity/api-requests';
import axios from 'axios';
import { handleEnum } from '@ws-serenity/api-requests';
const api = axios.create({
baseURL: 'https://some-server.com/api'
// ...continue axios setup
});
const apiHandlers: ApiDataHandler = {
typeHandlers: [
// 'yyyy-MM-dd HH:mm' -> Date instance, но можно указать свой формат, передав его аргументом
handleDateTime("yyyyy-MM-dd'T'HH:mm:ss"),
// 'yyyy-MM-dd' -> Date instance
handleDate(),
// об enum см далее
handleEnum(MeetingStatusEnum),
],
keyHandlers: {}
};
const setup = apiWithMappers(api, apiHandlers);
"Под капотом" мапперы имеют следующую структуру,
используя тип TypeHandler
можно писать свои мапперы:
export const handleDate = (format?: string): TypeHandler<string, Date> => ({
// тип, к которому применяем маппинг - string | number
type: 'string',
// правило, проверяющее, должно ли значение мапиться
doesMatch: value => isValidDate(mapDate(value, format)),
// непосредственно перевод из одного типа в другой
mapper: value => mapDate(value, format),
});
setup
- является функцией, которая настраивается для группы запросов на сервис.
// первым параметром передается baseUrl для контроллера
// вторым AxiosRequestConfig
// от этого объекта выполняются непосредственно запросы к api
const meetingService = setup('meeting-service');
Описание типов
interface Meeting {
id: string;
title: string;
description: string | null;
participantsCount: number;
labels: string[];
}
// тип сущности для валидации.
// Хорошо подходит, если тип не содержит полей, которые должны быть смаплены (enum и Date)
const meetingType: Validator<Meeting> = tObjectStrict({
id: tString(),
title: tString(),
description: tNullable(tString()),
participantsCount: tNumber(),
labels: tArray(tString())
});
если для сущности предполагается маппинг (она содержит поля типа Date или enum) МОЖНО описывать один объект валидации и один объект модели:
export type MeetingStatusType = typeof MeetingStatusEnum;
export const MeetingStatusEnum = {
SCHEDULED: {
value: 'SCHEDULED',
name: 'Запланировано',
},
CANCELED: {
value: 'CANCELED',
name: 'Отменено',
},
DONE: {
value: 'DONE',
name: 'Проведено'
}
// инструкция для TypeScript, что данный объект меняться не будет, чтобы он мог генерировать для него типы
} as const;
// Используется непосредственно в приложении
interface Meeting {
id: string;
dateTime: Date;
status: MeetingStatusType[keyof MeetingStatusType];
}
const apiHandlers: ApiDataHandler = {
typeHandlers: [
handleDateTime(),
handleEnum(MeetingStatusEnum),
],
keyHandlers: {}
};
// Тип не является рантайм-типом. Он просто описывает данные, приходящие НЕПОСРЕДСТВЕННО с API
const meetingType = tObjectStrict({
id: tString(),
// мы знаем, что здесь формат будет yyyy-MM-dd HH:mm, поэтому добавляем маппер
dateTime: tString(),
// мы знаем, что здесь будет одно из значений: SCHEDULED, DONE, CANCELED.
// Оно автоматически смапится в value из MeetingStatusEnum
status: tString(),
});
FEATURE IN PROGRESS. COMING SOON Если возникает пересечение ключей енума различных сущностей, конкретный маппер можно указать с помощью ApiDataHandler.keyHandlers для запроса
Запросы
Get
import { tNumber, tArray, tNullable, tString, tObjectStrict } from '@ws-serenity/api-requests';
// в начале указывается HTTP-метод с возвращаемым типом
const getRequest = meetingService.get<Meeting>()
// для get-метода доступны запросы на получение:
// модели:
getRequest.model(meetingType)
// пейджированного списка
getRequest.collection(meetingListType)
// массива
getRequest.array(meetingListType)
Для валидации на этом этапе необходимо передать тип, описывающий валидацию с помощью библиотеки "runtime-validator".
// для валидации указывается "тип" интерфейса, непосредственно приходящий с api!
// Модель (class) писать не нужно! Мапперы автоматически преобразуют типы! (см выше)
getRequest.model(meetingListType);
Указание параметров
Если для запроса необходимо указать запросы, то это можно сделать с помощью последовательного вызова
метода with
:
modelRequest.with({ headers, body, params })
// замыкет цепочку вызова метод from, в котором указывается последний фрагмент url, куда выполняется запрос
.from(meetingId);
// метод with можно опустить, если параметры не требуются
modelRequest.from(meetingId);
Post
Предоставляет все те же самые методы (model, array, collection), а также дополнительный - void
-
не
возвращающий ничего и не требующий валидации
В отличие от метода get
завершает отправление запроса вызов метода to
, в который аналогично
передается
последний фрагмент url
meetingService.post<Meeting>()
.model(meetingType)
.with({ body: updateMeetingDto })
.to(`${id}/update`)
Примеры
Таким образом полное получение сущности может выглядеть следующим образом
// meetingActions.ts
// заранее настроенный конфиг для запросов под конкретный проект
import { setup } from './config/api'
// конфиг для запросов на API-сервис
const meetingActions = setup('meeting-service/internal');
export const get = (id: string) =>
meetingActions.get<Meeting>()
.model(meetingType)
.from(id);
export const create = (dto: CreateMeetingDto) =>
meetingActions.post<Meeting>()
.model(meetingType)
.with({ body: dto })
.to('create');
export const list = (params: SearchListParams) =>
meetingActions.get<MeetingList>()
.array(meetingListType)
.with({ params })
.from('list');
export const setStatus = (id: string, status: MeetingStatus) =>
meetingActions.post()
.void()
.to(`${id}/${status}`);
)
Put and Patch
meetingActions.put<Meeting>()
.with({ body: updateMeetingDto })
.item(id);
meetingActions.patch<Meeting>()
.with({ body: updateMeetingDto })
.item(id);
ApiHandlers
// простой enum, так тоже сработает. Вернет number
export enum AccountStatus {
ENABLED,
DISABLED,
}
const enumApiDataHandler: ApiDataHandler = {
typeHandlers: [
handleEnum(AccountStatus),
],
keyHandlers: {},
};
// or
// можно передать и сложный объект. Больше не нужно дублировать тип, enum и frontend-сущность-описание
export const AccountStatusDisplay = {
DISABLED: {
value: 'DISABLED',
name: 'Неактивен',
},
ENABLED: {
value: 'ENABLED',
name: 'Активен',
},
} as const;
const complexEnumApiDataHandler: ApiDataHandler = {
typeHandlers: [
handleEnum(AccountStatusDisplay),
],
keyHandlers: {},
};
Бросать ли error при возникновении ошибки с API:
env-переменная REACT_APP_UNSAFE_PROD=true
❌ выключает выбрасывание ошибок при валидации API в
продакшене (NODE_ENV=production
)
env-переменная REACT_APP_UNSAFE_DEV=true
❌ выключает выбрасывание ошибок при валидации API в
разработке (NODE_ENV=development
)
В противном случае ошибки валидации будут выведены в консоль