mirror of
https://github.com/gromlab-ru/slm-design.git
synced 2026-08-22 07:30:16 +03:00
feat: add example
This commit is contained in:
168
.opencode/skills/rest-client/reference/canons/http-client.md
Normal file
168
.opencode/skills/rest-client/reference/canons/http-client.md
Normal file
@@ -0,0 +1,168 @@
|
||||
---
|
||||
title: Кастомизация HTTP-клиента
|
||||
description: Настройка транспорта REST-клиента через опции и хуки HttpClient.
|
||||
keywords: [rest, http, транспорт, авторизация, jwt, refresh token, onRequest, onError]
|
||||
---
|
||||
|
||||
# Кастомизация HTTP-клиента
|
||||
|
||||
Настройка транспорта REST-клиента через опции и хуки `HttpClient`.
|
||||
|
||||
## Где живёт кастомизация
|
||||
|
||||
Вся настройка транспорта — `baseUrl`, заголовки, авторизация, retry — задаётся в `client.ts` REST-модуля при создании `HttpClient`.
|
||||
|
||||
Не размещайте авторизацию, обработку 401 и логирование в компонентах, GET-хуках или обёртках над операциями: у транспорта одна точка настройки.
|
||||
|
||||
В generated/SDK-сценарии `HttpClient` импортируется из generated-кода или SDK-пакета. В ручном сценарии без OpenAPI `HttpClient` импортируется из runtime-зависимости `@gromlab/api-codegen`.
|
||||
|
||||
```ts
|
||||
import { HttpClient } from '@gromlab/api-codegen'
|
||||
```
|
||||
|
||||
## Опции HttpClient
|
||||
|
||||
`HttpClient` принимает плоский конфиг: стандартные `fetch`-опции задаются вместе с хуками клиента.
|
||||
|
||||
| Опция | Назначение |
|
||||
| --- | --- |
|
||||
| `baseUrl` | Базовый URL API. |
|
||||
| `headers` | Заголовки по умолчанию для всех запросов. |
|
||||
| `credentials` | Политика отправки cookies: `omit`, `same-origin`, `include`. |
|
||||
| `timeout` | Таймаут запроса в миллисекундах, работает через `AbortSignal`. |
|
||||
| `customFetch` | Замена стандартного `fetch`: тесты, SSR, custom transport. |
|
||||
| `paramsSerializer` | Кастомная сериализация query params в URL. |
|
||||
| `responseParser` | Кастомный парсинг response body. |
|
||||
| `onRequest` | Request-хук перед вызовом `fetch`. |
|
||||
| `onResponse` | Response-хук после успешного HTTP-ответа. |
|
||||
| `onError` | Error-хук для HTTP-ошибок, network errors и ошибок парсинга. |
|
||||
|
||||
Полный список опций — в README `@gromlab/api-codegen`.
|
||||
|
||||
## Контракт хуков
|
||||
|
||||
- `onRequest(params, context)` вызывается перед `fetch` и возвращает изменённые `params`.
|
||||
- `onResponse(response, context)` вызывается после успешного ответа и возвращает `response`.
|
||||
- `onError(error, context)` вызывается для HTTP-ошибок, network errors и ошибок парсинга.
|
||||
- `context` содержит `url`, `request`, `retryCount` и `retry()` — повтор текущего запроса.
|
||||
- `onError` должен либо бросить ошибку, либо вернуть fallback-значение, либо вернуть результат `context.retry()`. Если вернуть `undefined`, ошибка будет считаться обработанной, а вызывающий код получит `undefined` вместо исключения.
|
||||
- Для защищённых endpoints generated operation передаёт `secure: true`, поэтому авторизацию можно добавлять только там, где она нужна.
|
||||
|
||||
## JWT-авторизация
|
||||
|
||||
Токен добавляется в `onRequest` только для защищённых запросов и не перезаписывает явно переданный `Authorization`.
|
||||
|
||||
```ts
|
||||
// src/infra/pet-store-rest-api/client.ts
|
||||
export const petStoreHttpClient = new HttpClient({
|
||||
baseUrl: 'https://example.com/api',
|
||||
onRequest: (params) => {
|
||||
const token = localStorage.getItem('access_token')
|
||||
|
||||
if (!params.secure || !token) {
|
||||
return params
|
||||
}
|
||||
|
||||
const headers = new Headers(params.headers)
|
||||
|
||||
if (!headers.has('Authorization')) {
|
||||
headers.set('Authorization', `Bearer ${token}`)
|
||||
}
|
||||
|
||||
return {
|
||||
...params,
|
||||
headers,
|
||||
}
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Refresh token
|
||||
|
||||
Обновление токена и повтор запроса выполняются в `onError` через `context.retry()`. `context.retryCount` защищает от бесконечного цикла повторов.
|
||||
|
||||
```ts
|
||||
// src/infra/pet-store-rest-api/client.ts
|
||||
import { ApiError, HttpClient } from './generated'
|
||||
|
||||
export const petStoreHttpClient = new HttpClient({
|
||||
baseUrl: 'https://example.com/api',
|
||||
onError: async (error, context) => {
|
||||
if (error instanceof ApiError && error.status === 401 && context.retryCount === 0) {
|
||||
await refreshToken()
|
||||
return context.retry()
|
||||
}
|
||||
|
||||
throw error
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
`ApiError` экспортируется из `generated/` и содержит `status`, `statusText`, `response`, `data` и исходный `request`.
|
||||
|
||||
## Логирование
|
||||
|
||||
```ts
|
||||
const petStoreHttpClient = new HttpClient({
|
||||
baseUrl: 'https://example.com/api',
|
||||
onResponse: (response, context) => {
|
||||
console.log(context.request.method, context.url, response.status)
|
||||
return response
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Сериализация query params
|
||||
|
||||
```ts
|
||||
const petStoreHttpClient = new HttpClient({
|
||||
baseUrl: 'https://example.com/api',
|
||||
paramsSerializer: (query) => {
|
||||
const params = new URLSearchParams()
|
||||
|
||||
Object.entries(query).forEach(([key, value]) => {
|
||||
if (Array.isArray(value)) {
|
||||
params.set(key, value.join(','))
|
||||
return
|
||||
}
|
||||
|
||||
if (value !== undefined) {
|
||||
params.set(key, String(value))
|
||||
}
|
||||
})
|
||||
|
||||
return params.toString()
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Параметры одного вызова
|
||||
|
||||
Разовые настройки запроса не относятся к `HttpClient`. Они передаются последним аргументом operation:
|
||||
|
||||
```ts
|
||||
import { getPetById } from './generated/operations/get-pet-by-id'
|
||||
import { petStoreHttpClient } from './client'
|
||||
|
||||
await getPetById(
|
||||
petStoreHttpClient,
|
||||
{ petId },
|
||||
{
|
||||
headers: {
|
||||
'X-Request-Id': requestId,
|
||||
},
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
## Правила
|
||||
|
||||
- Кастомизация транспорта живёт только в `client.ts` REST-модуля.
|
||||
- Авторизация добавляется в `onRequest` с учётом `params.secure` и без перезаписи явного `Authorization`.
|
||||
- `onError` либо бросает ошибку, либо возвращает fallback или `context.retry()`; молчаливый `return` запрещён.
|
||||
- Повторы запроса ограничиваются проверкой `context.retryCount`.
|
||||
- Бизнес-реакции на ошибки — тосты, редиректы, UI-состояние — не размещаются в хуках `HttpClient`.
|
||||
|
||||
## Следующий шаг
|
||||
|
||||
После настройки транспорта проверьте [использование REST-клиента](../../SKILL.md#использование-rest-клиента) или добавьте [GET-хуки REST-клиента](../../SKILL.md#get-хуки-rest-клиента).
|
||||
Reference in New Issue
Block a user