Some checks are pending
Auto-tag / Create date-tag (push) Waiting to run
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
Docker API / Build + push API (push) Waiting to run
Docker API / Deploy API on stage (push) Blocked by required conditions
1. Docs cross-check — обновил performance-baseline.md (Sprint 18/20/23 фиксы), secrets.md (16 новых env-vars из Sprint 20+ — Authentication Google/Microsoft, Monitoring, Cleanup, Hangfire:Cron, Telegram, Maintenance, App, Storage, PUBLIC_GA_ID/YM_ID). 2. Auto-gen api-reference — ApiReferenceDocsJob (Hangfire weekly вс 05:30 UTC) + Python-эквивалент `/tmp/gen-api-ref.py` для commit actual snapshot. docs/api-reference.md = 195 endpoints, 57 controllers. 3. Coverage gap-fill — Sprint18To23FeaturesTests.cs (16 Facts): - bulk-update + cross-tenant isolation - UserPresets CRUD - inline-edit price PATCH - CSV import 2 строки транзакцией - OrgExport create + list isolation - 1C-CSV import с русскими заголовками - audit-log export CSV streaming + BOM check - MoySklad sync-status stub - SSO providers + 503 unconfigured + 400 unknown provider - bug-001 NUL byte → 400 - bug-004 tiny price → 400 - export CSV BOM Покрывает все новые контроллеры Sprint 18-23 + regression-protect для критичных багов. 4. Contract tests — deploy/swagger-diff.sh: pull /swagger/v1/swagger.json с двух URL, diff endpoints+schemas через python3. Exit 0/1/2 для blue-green safety gate. Multi-path auto-detect. 5. docs/error-codes.md — каталог HTTP-кодов API (200-503) + humanizeError pattern для фронта + retry-policy таблица. 6. docs/glossary.md — 50+ доменных терминов (Tenant/Organization/Stock/ StockMovement/RetailSale/Counterparty/Owner/Employee/Role/Permission/ advisory lock/Serializable/…) с ссылками на code-сущности. 7. docs/ONBOARDING.md — first 3 days для нового разработчика (install → запуск → структура → первый PR + FAQ). 8. README.md — обновил под текущее состояние: React 19, Sprint-history 1-24, ссылки на ключевые docs, корректный 5-min quick start. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
160 lines
8.7 KiB
Markdown
160 lines
8.7 KiB
Markdown
# API error catalog
|
||
|
||
Каталог HTTP-кодов и тел ответов, которые возвращает `food-market.api`.
|
||
Используется фронтом для `humanizeError(response)` и QA для regression
|
||
проверки. Если поле `error` есть — это user-facing сообщение; `errors`
|
||
(множественное) — структурированные ошибки валидации (ASP.NET
|
||
ValidationProblemDetails).
|
||
|
||
## Формат
|
||
|
||
```jsonc
|
||
// Универсальный шаблон single-error:
|
||
{ "error": "Понятный текст для пользователя.", "field": "Optional" }
|
||
|
||
// ValidationProblemDetails (FluentValidation / DataAnnotations):
|
||
{ "type": "...", "title": "One or more validation errors occurred.",
|
||
"status": 400, "errors": { "Name": ["..."], "Prices[0].Amount": ["..."] } }
|
||
|
||
// retryable flag (Sprint 23):
|
||
{ "error": "...", "retryable": true }
|
||
```
|
||
|
||
## Коды
|
||
|
||
### 200/201/204 — OK / Created / NoContent
|
||
Корректно. Тело — DTO или пусто.
|
||
|
||
### 400 — Bad Request
|
||
|
||
| Когда | Тело | Что показать |
|
||
|---|---|---|
|
||
| Validation от FluentValidation | `ValidationProblemDetails` с `errors.{field}: [msg]` | Подсветить поле + показать сообщение |
|
||
| Business-rule (например, draft пустой) | `{error: "Нельзя провести пустой чек."}` | toast + не закрывать форму |
|
||
| Сумма оплаты < total | `{error: "Сумма оплаты X меньше итога Y. Доплатите...", field: "PaidCash"}` | подсветить поле PaidCash |
|
||
| Required price = 0 после rounding (Sprint 23 bug-004) | `{error: "Цена «X» обязательна и должна быть больше 0."}` | подсветить prices section |
|
||
| NUL-byte в строке (Sprint 23 bug-001) | `errors.Name: ["Поле Name не должно содержать управляющих символов..."]` | подсветить поле |
|
||
| Дубликат barcode при создании | `{error: "Штрихкод X уже используется товаром «Y»."}` | toast |
|
||
| Дубликат артикула | `{error: "Артикул «X» уже занят в этой организации."}` | toast |
|
||
| Невалидный CSV / 1С-import | `errors: [{row, error}]` | таблица с подсветкой строк |
|
||
|
||
### 401 — Unauthorized
|
||
|
||
| Когда | Тело | Что показать |
|
||
|---|---|---|
|
||
| Нет токена / устаревший токен | пусто или OpenIddict-`{error: "missing_token"}` | редирект на `/login`, refresh с RT |
|
||
| Garbage / tampered JWT | `{error: "missing_token"}` | logout + login |
|
||
| Refresh-token недействителен | `{error: "invalid_grant", error_description: "..."}` | logout |
|
||
|
||
### 403 — Forbidden
|
||
|
||
| Когда | Тело | Что показать |
|
||
|---|---|---|
|
||
| Нет permission на mutating action | пусто или ProblemDetails | toast: «Нет прав на это действие» |
|
||
| Регулярный Admin лезет в `/hangfire` | пусто | redirect → 404 на фронте |
|
||
| Cashier пытается удалить заявку | пусто | скрыть кнопку delete для Cashier |
|
||
|
||
### 404 — Not Found
|
||
|
||
| Когда | Что показать |
|
||
|---|---|
|
||
| Document не найден (включая cross-tenant — нельзя раскрыть существование!) | «Запись не найдена. Возможно, удалена.» |
|
||
| Endpoint не существует (типо в URL) | (фронту не должно встречаться) |
|
||
|
||
### 409 — Conflict
|
||
|
||
| Когда | Тело | Что показать |
|
||
|---|---|---|
|
||
| DbUpdateConcurrencyException (xmin) | `{error: "Документ изменён в другом окне..."}` | toast + reload |
|
||
| Чек уже проведён, повторный post | `{error: "Чек уже проведён."}` | toast |
|
||
| Serialization failure 40001 (Sprint 23 bug-003) | `{error: "Конфликт параллельных операций. Попробуйте ещё раз.", retryable: true}` | **auto-retry один раз**, при повторе — toast |
|
||
| Дубликат preset name | `{error: "Пресет с таким именем уже существует..."}` | подсветить input name |
|
||
| In-flight org-export ≥3 | `{error: "Уже в очереди 3+ экспорта. Подождите..."}` | toast |
|
||
| Удаление непустой группы товаров | `{error: "Нельзя удалить группу, содержащую товары/подгруппы."}` | toast |
|
||
|
||
### 413 — Payload Too Large
|
||
|
||
| Когда | Что показать |
|
||
|---|---|
|
||
| Body > nginx limit (10 MB по default) | «Файл слишком большой. Лимит: 10 МБ.» |
|
||
|
||
### 429 — Too Many Requests
|
||
|
||
| Когда | Тело | Что показать |
|
||
|---|---|---|
|
||
| Rate-limit на signup (3/h IP) | пусто или `Retry-After` header | «Слишком много попыток. Попробуйте через час.» |
|
||
| Rate-limit на forgot-password (3/h email + 10/h IP) | то же | то же |
|
||
| Rate-limit на feedback (5/час) | то же | то же |
|
||
| IP-limit (60/мин общий) | то же | «Слишком много запросов с вашего IP.» |
|
||
|
||
### 431 — Request Header Fields Too Large
|
||
|
||
| Когда | Что показать |
|
||
|---|---|
|
||
| Слишком большие/много HTTP-headers | (нечем фиксить с UI; нечасто) |
|
||
|
||
### 500 — Internal Server Error
|
||
|
||
После Sprint 23 — **очень редко**. Если встречается:
|
||
- Все NUL-byte 500 → теперь 400 (bug-001).
|
||
- Все serialization 40001 → теперь 409 (bug-003).
|
||
- Все остальные uncaught exceptions → Serilog лог + `correlation-id` header.
|
||
|
||
Что показать пользователю: «Произошла ошибка. Попробуйте ещё раз
|
||
или сообщите администратору. Код: {x-correlation-id}». Этот correlation
|
||
id находится в `x-correlation-id` response-header — записываем в audit.
|
||
|
||
### 501 — Not Implemented
|
||
|
||
| Когда | Тело | Что показать |
|
||
|---|---|---|
|
||
| SSO callback flow (Sprint 20 scaffold) | `{status: "scaffolded", message, email, next}` | «SSO ещё не настроено полностью» |
|
||
|
||
### 503 — Service Unavailable
|
||
|
||
| Когда | Тело | Что показать |
|
||
|---|---|---|
|
||
| SSO провайдер не сконфигурирован | `{error: "SSO для X не настроено.", hint: "..."}` | скрыть кнопку SSO |
|
||
| (резерв на maintenance window) | пусто | «Сервис недоступен» |
|
||
|
||
## humanizeError на фронте
|
||
|
||
`src/lib/api.ts → humanizeError(err)`:
|
||
|
||
```typescript
|
||
export function humanizeError(err: AxiosError): string {
|
||
const data = err.response?.data as any
|
||
// 1. Single-error (наш стандарт)
|
||
if (data?.error) return data.error
|
||
// 2. ValidationProblemDetails
|
||
if (data?.errors) {
|
||
const first = Object.values(data.errors).flat()[0]
|
||
return first ?? 'Ошибка валидации'
|
||
}
|
||
// 3. По статусу
|
||
switch (err.response?.status) {
|
||
case 401: return 'Сессия истекла. Войдите снова.'
|
||
case 403: return 'Нет прав на это действие.'
|
||
case 404: return 'Запись не найдена.'
|
||
case 409: return 'Конфликт версий. Перезагрузите страницу.'
|
||
case 413: return 'Файл слишком большой.'
|
||
case 429: return 'Слишком много запросов. Подождите немного.'
|
||
case 500: return `Ошибка сервера. Код: ${err.response.headers['x-correlation-id'] ?? 'unknown'}`
|
||
case 503: return 'Сервис временно недоступен.'
|
||
}
|
||
return err.message ?? 'Неизвестная ошибка'
|
||
}
|
||
```
|
||
|
||
## Retry-policy
|
||
|
||
| Код | Retry? | Условие |
|
||
|---|---|---|
|
||
| 401 | Один раз — после refresh-token | Если refresh тоже 401 → logout |
|
||
| 409 c `retryable: true` | Один авто-retry с задержкой 500ms | Sprint 23 фикс — серверная сторона уже retry'ит до 5 раз, клиентский — дополнительный safety net |
|
||
| 429 | Через `Retry-After` секунд (если есть) | Не более 3 попыток |
|
||
| 500 | НЕТ авто-retry | Пользователь сам решает |
|
||
| 503 | Через 5 секунд | До 2 попыток |
|
||
|
||
Без auto-retry: 400, 403, 404, 413, 501.
|