273 lines
10 KiB
Markdown
273 lines
10 KiB
Markdown
|
|
---
|
|||
|
|
title: Автогенерация REST-клиента
|
|||
|
|
description: Генерация REST-клиента из OpenAPI-спецификации.
|
|||
|
|
keywords: [rest, openapi, api-codegen, автогенерация, generated, npx]
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# Автогенерация REST-клиента
|
|||
|
|
|
|||
|
|
Генерация REST-клиента из OpenAPI-спецификации.
|
|||
|
|
|
|||
|
|
## Когда использовать
|
|||
|
|
|
|||
|
|
Автогенерация используется, когда у API есть актуальная OpenAPI-спецификация. Генератор создаёт TypeScript-клиент, типы и методы API, а разработчик вручную добавляет настройку клиента и GET-хуки.
|
|||
|
|
|
|||
|
|
## Пример API
|
|||
|
|
|
|||
|
|
В примерах используется Swagger Petstore:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
https://petstore3.swagger.io/api/v3/openapi.json
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Имена модуля:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
src/infra/pet-store-api/
|
|||
|
|
petStoreApi
|
|||
|
|
pet-store-api.generated.ts
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Скрипт генерации
|
|||
|
|
|
|||
|
|
`@gromlab/api-codegen` не устанавливается в `devDependencies`. Используем `npx @gromlab/api-codegen@latest`, чтобы запускать свежую версию.
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"scripts": {
|
|||
|
|
"codegen:pet-store-api": "npx @gromlab/api-codegen@latest -i https://petstore3.swagger.io/api/v3/openapi.json -o src/infra/pet-store-api/generated -n pet-store-api.generated"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Параметры:
|
|||
|
|
|
|||
|
|
- `-i` — путь к OpenAPI-спецификации: URL или локальный файл.
|
|||
|
|
- `-o` — директория для сгенерированного файла.
|
|||
|
|
- `-n` — имя сгенерированного файла без `.ts`.
|
|||
|
|
|
|||
|
|
Ключ `--swr` не используется. GET-хуки REST-клиента пишутся вручную, чтобы сохранить проектный контракт: один GET-хук = один GET-метод, без бизнес-логики и композиции.
|
|||
|
|
|
|||
|
|
## Генерация
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
npm run codegen:pet-store-api
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Ожидаемый результат:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
src/infra/pet-store-api/generated/
|
|||
|
|
└── pet-store-api.generated.ts
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Сгенерированный файл не правится руками и коммитится в репозиторий.
|
|||
|
|
|
|||
|
|
## Проверка методов
|
|||
|
|
|
|||
|
|
После генерации откройте `generated/pet-store-api.generated.ts` и проверьте фактические имена методов.
|
|||
|
|
|
|||
|
|
Для Petstore нужны GET-операции вида:
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
petStoreApi.pet.findPetsByStatus({ status: StatusEnum.Available })
|
|||
|
|
petStoreApi.pet.getPetById({ petId: 10 })
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Точные сигнатуры зависят от OpenAPI-схемы и версии генератора. В рабочих задачах всегда сверяйтесь с generated-файлом.
|
|||
|
|
|
|||
|
|
## Алгоритм для агента
|
|||
|
|
|
|||
|
|
После генерации агент должен действовать по шагам:
|
|||
|
|
|
|||
|
|
1. Открыть `generated/{service-name}.generated.ts`.
|
|||
|
|
2. Найти фактические имена GET-методов клиента.
|
|||
|
|
3. Для каждого нужного GET-метода найти generated-тип параметров и тип ответа.
|
|||
|
|
4. Создать или обновить `client.ts` только для настройки транспорта и экспорта инстанса клиента.
|
|||
|
|
5. Создать GET-хуки только для реально нужных GET-методов, не для всех методов API на всякий случай.
|
|||
|
|
6. Для каждого GET-хука создать key-функцию формата `[serviceName, endpoint]`.
|
|||
|
|
7. В key-функции вернуть `null`, если обязательные параметры не готовы.
|
|||
|
|
8. В хуке принять `params?: GeneratedParams | null` и `config?: SWRConfiguration<Data>`.
|
|||
|
|
9. В fetcher вызвать generated-метод клиента с `params as GeneratedParams`.
|
|||
|
|
10. Экспортировать хук и key-функцию из `hooks/index.ts`.
|
|||
|
|
11. Экспортировать наружу только нужные generated-типы, generated enum, DTO и `hooks` через корневой `index.ts`.
|
|||
|
|
|
|||
|
|
Что агент не должен делать:
|
|||
|
|
|
|||
|
|
- Не использовать ключ `--swr` генератора.
|
|||
|
|
- Не править `generated/*.generated.ts` руками.
|
|||
|
|
- Не добавлять GET-хуки для POST, PUT, PATCH, DELETE.
|
|||
|
|
- Не добавлять бизнес-флаги, тосты, редиректы и UI-состояние в GET-хук.
|
|||
|
|
- Не создавать словари enum-маппинга внутри GET-хука.
|
|||
|
|
- Не объявлять DTO и response-типы в файле хука.
|
|||
|
|
- Не вызывать `useSWR` условно.
|
|||
|
|
- Не добавлять `throw` в fetcher для неготовых params.
|
|||
|
|
|
|||
|
|
## `client.ts`
|
|||
|
|
|
|||
|
|
Сгенерированный код не должен напрямую использоваться из приложения. Сначала создаётся настроенный инстанс клиента.
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
// src/infra/pet-store-api/client.ts
|
|||
|
|
import { Api, HttpClient } from './generated/pet-store-api.generated'
|
|||
|
|
|
|||
|
|
const baseUrl = process.env.NEXT_PUBLIC_PET_STORE_API_BASE_URL
|
|||
|
|
|
|||
|
|
if (!baseUrl) {
|
|||
|
|
throw new Error('NEXT_PUBLIC_PET_STORE_API_BASE_URL is required')
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const httpClient = new HttpClient({
|
|||
|
|
baseUrl,
|
|||
|
|
baseApiParams: {
|
|||
|
|
secure: false,
|
|||
|
|
headers: {
|
|||
|
|
'Content-Type': 'application/json',
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
export const petStoreApi = new Api(httpClient)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Локальное значение `NEXT_PUBLIC_PET_STORE_API_BASE_URL` задаётся в `.env.local`. Не добавляйте fallback вроде `?? 'http://localhost:8080/api/v3'` или `?? ''`: если env-переменная не задана, клиент должен падать с явной ошибкой конфигурации.
|
|||
|
|
|
|||
|
|
`client.ts` не содержит расширения типов, `declare module` и `Extended`-типы. Он только настраивает транспорт и экспортирует инстанс клиента.
|
|||
|
|
|
|||
|
|
## GET-хуки
|
|||
|
|
|
|||
|
|
GET-хуки пишутся вручную после проверки generated-методов.
|
|||
|
|
|
|||
|
|
Пример для generated-метода `petStoreApi.pet.getPetById({ petId })`:
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
// src/infra/pet-store-api/hooks/use-get-pet-detail.hook.ts
|
|||
|
|
import type { SWRConfiguration } from 'swr'
|
|||
|
|
import useSWR from 'swr'
|
|||
|
|
import { petStoreApi } from '../client'
|
|||
|
|
import type { GetPetByIdParams, Pet } from '../generated/pet-store-api.generated'
|
|||
|
|
|
|||
|
|
export const getPetDetailKey = (params?: GetPetByIdParams | null) => {
|
|||
|
|
if (!params?.petId) {
|
|||
|
|
return null
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return ['pet-store-api', `/pet/${params.petId}`] as const
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Получает детальную карточку питомца с кешированием результата.
|
|||
|
|
*/
|
|||
|
|
export const useGetPetDetail = (
|
|||
|
|
params?: GetPetByIdParams | null,
|
|||
|
|
config?: SWRConfiguration<Pet>,
|
|||
|
|
) => {
|
|||
|
|
const key = getPetDetailKey(params)
|
|||
|
|
const fetcher = () => petStoreApi.pet.getPetById(params as GetPetByIdParams)
|
|||
|
|
|
|||
|
|
return useSWR<Pet>(key, fetcher, config)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Подробный контракт key-функций, `params`, `config` и запретов описан в разделе [GET-хуки REST-клиента](/docs/applied/rest-client/setup/hooks).
|
|||
|
|
|
|||
|
|
## Расширение сгенерированных типов
|
|||
|
|
|
|||
|
|
Сгенерированный файл не правится руками. Если OpenAPI-спецификация неполная или генератор дал слишком общий тип (`object`, `unknown`, отсутствующее поле), расширения живут в `types/`.
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
src/infra/biocad-less-api/
|
|||
|
|
├── generated/
|
|||
|
|
│ └── biocad-less-api.generated.ts
|
|||
|
|
├── types/
|
|||
|
|
│ ├── term.ts
|
|||
|
|
│ └── index.ts
|
|||
|
|
├── client.ts
|
|||
|
|
└── index.ts
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Пример расширения generated-типа:
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
// src/infra/biocad-less-api/types/term.ts
|
|||
|
|
import type { TermRecordItem } from '../generated/biocad-less-api.generated'
|
|||
|
|
|
|||
|
|
declare module '../generated/biocad-less-api.generated' {
|
|||
|
|
interface TermRecordItem {
|
|||
|
|
media?: {
|
|||
|
|
file?: string
|
|||
|
|
title?: string
|
|||
|
|
url?: string
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export type TermRecordItemExtended = Omit<
|
|||
|
|
TermRecordItem,
|
|||
|
|
'categories' | 'tags' | 'fields'
|
|||
|
|
> & {
|
|||
|
|
categories?: Array<{
|
|||
|
|
_id?: string
|
|||
|
|
id?: string
|
|||
|
|
slug?: string
|
|||
|
|
name?: string
|
|||
|
|
}>
|
|||
|
|
tags?: Array<{
|
|||
|
|
_id?: string
|
|||
|
|
id?: string
|
|||
|
|
slug?: string
|
|||
|
|
name?: string
|
|||
|
|
}>
|
|||
|
|
fields?: Record<string, unknown>
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
// src/infra/biocad-less-api/types/index.ts
|
|||
|
|
export type { TermRecordItemExtended } from './term'
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`declare module` используется для добавления отсутствующих полей в generated-интерфейс. `Extended`-тип используется, когда нужно переопределить неточные поля, не трогая generated-файл.
|
|||
|
|
|
|||
|
|
## Публичный API
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
// src/infra/pet-store-api/index.ts
|
|||
|
|
export { petStoreApi } from './client'
|
|||
|
|
export type {
|
|||
|
|
FindPetsByStatusParams,
|
|||
|
|
GetPetByIdParams,
|
|||
|
|
Pet,
|
|||
|
|
} from './generated/pet-store-api.generated'
|
|||
|
|
export { PetStatusEnum, StatusEnum } from './generated/pet-store-api.generated'
|
|||
|
|
export * from './hooks'
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Наружу импортируют только из `infra/pet-store-api`, не из `generated/`.
|
|||
|
|
|
|||
|
|
Если у модуля есть расширенные типы, они тоже реэкспортируются через `index.ts`:
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
// src/infra/biocad-less-api/index.ts
|
|||
|
|
export type { TermRecordItemExtended } from './types'
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Регенерация
|
|||
|
|
|
|||
|
|
При изменении OpenAPI-схемы:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
npm run codegen:pet-store-api
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Что меняется:
|
|||
|
|
|
|||
|
|
- `generated/pet-store-api.generated.ts` — перезаписывается генератором.
|
|||
|
|
- `client.ts`, `hooks/`, `types/`, `index.ts` — не трогаются автоматически.
|
|||
|
|
|
|||
|
|
Если после регенерации поменялись сигнатуры методов или типы, это исправляется в ручном коде модуля.
|
|||
|
|
|
|||
|
|
## Следующий шаг
|
|||
|
|
|
|||
|
|
После генерации и настройки `client.ts` проверьте [использование REST-клиента](/docs/applied/rest-client/usage) или добавьте [GET-хук REST-клиента](/docs/applied/rest-client/setup/hooks) для Client Components.
|