Files

10 KiB
Raw Permalink Blame History

title, description, keywords
title description keywords
Автогенерация REST-клиента Генерация REST-клиента из OpenAPI-спецификации.
rest
openapi
api-codegen
автогенерация
generated
npx

Автогенерация REST-клиента

Генерация REST-клиента из OpenAPI-спецификации.

Когда использовать

Автогенерация используется, когда у API есть актуальная OpenAPI-спецификация. Генератор создаёт TypeScript-клиент, типы и методы API, а разработчик вручную добавляет настройку клиента и GET-хуки.

Пример API

В примерах используется Swagger Petstore:

https://petstore3.swagger.io/api/v3/openapi.json

Имена модуля:

src/infra/pet-store-api/
petStoreApi
pet-store-api.generated.ts

Скрипт генерации

@gromlab/api-codegen не устанавливается в devDependencies. Используем npx @gromlab/api-codegen@latest, чтобы запускать свежую версию.

{
  "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-метод, без бизнес-логики и композиции.

Генерация

npm run codegen:pet-store-api

Ожидаемый результат:

src/infra/pet-store-api/generated/
└── pet-store-api.generated.ts

Сгенерированный файл не правится руками и коммитится в репозиторий.

Проверка методов

После генерации откройте generated/pet-store-api.generated.ts и проверьте фактические имена методов.

Для Petstore нужны GET-операции вида:

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

Сгенерированный код не должен напрямую использоваться из приложения. Сначала создаётся настроенный инстанс клиента.

// 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 }):

// 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-клиента.

Расширение сгенерированных типов

Сгенерированный файл не правится руками. Если OpenAPI-спецификация неполная или генератор дал слишком общий тип (object, unknown, отсутствующее поле), расширения живут в types/.

src/infra/biocad-less-api/
├── generated/
│   └── biocad-less-api.generated.ts
├── types/
│   ├── term.ts
│   └── index.ts
├── client.ts
└── index.ts

Пример расширения generated-типа:

// 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>
}
// src/infra/biocad-less-api/types/index.ts
export type { TermRecordItemExtended } from './term'

declare module используется для добавления отсутствующих полей в generated-интерфейс. Extended-тип используется, когда нужно переопределить неточные поля, не трогая generated-файл.

Публичный API

// 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:

// src/infra/biocad-less-api/index.ts
export type { TermRecordItemExtended } from './types'

Регенерация

При изменении OpenAPI-схемы:

npm run codegen:pet-store-api

Что меняется:

  • generated/pet-store-api.generated.ts — перезаписывается генератором.
  • client.ts, hooks/, types/, index.ts — не трогаются автоматически.

Если после регенерации поменялись сигнатуры методов или типы, это исправляется в ручном коде модуля.

Следующий шаг

После генерации и настройки client.ts проверьте использование REST-клиента или добавьте GET-хук REST-клиента для Client Components.