chore(wiki): concept openapi-generator 3.1 gotchas (F&F sched 2026-08-18)

This commit is contained in:
2026-08-18 22:28:14 +03:00
parent 8ebe00ad16
commit 114db2956f
3 changed files with 68 additions and 0 deletions

View File

@@ -0,0 +1,66 @@
---
title: openapi-generator + OpenAPI 3.1 — gotchas генерации клиентов
type: concept
tags: [openapi, openapi-generator, codegen, python, sched, ff, api-contract]
related: [[sched-fnf-r8-verification-stand]]
updated: 2026-08-18
---
# openapi-generator + OpenAPI 3.1 — gotchas
Проверено вживую на F&F-раунде sched «клиент на любом ЯП одной командой»
(2026-08-18): Python-клиент из `@sched/admin-api@0.1.2` спеки (3.1.0, 15 путей)
через `openapi-generator` **7.24.0** (`-g python`, pydantic v2). Спеку НЕ чинили
(их импл, TDD) — обходили. Находки F-1..F-5 полные — в
`sched/.agents/inbox/2026-08-18T19-15-43Z-admin-fnf-openapi-client-report.md`;
здесь — переиспользуемая суть.
## Гоча 1 — ответы `{}` в спеке ломают десериализацию (главная)
Если операция объявляет ответ `{}` (пустая схема), а сервер реально отдаёт
объект (`{task: …}`, `{run: …}`, `{schedule: …}`, `{logChunk, logTotalLength}`),
сгенерированный клиент валится на **pydantic ValidationError** — он пытается
десериализовать wrapped-тело в объявленную модель, а требуемых полей на верхнем
уровне нет (все None).
- Симптом: `ValidationError` на 2xx с полями `input_value=None`.
- Фикс (у имплементера спеки): объявлять реальные схемы ответов
(`{task: TaskRecord}` и т.п.). Без этого ни один генератор не даёт рабочий клиент.
- Обход (у потребителя): raw-транспорт + ручная валидация:
```python
resp = client.call_api("POST", client.configuration.host + "/tasks", body=body)
data = json.loads(resp.read()) # resp.data может быть None до read()
rec = TaskRecord.model_validate(data["task"])
```
Важно: в raw-вызове `call_api` **НЕ делает snake→camel конвертацию** запроса —
сериализуй через `model_dump(exclude_none=True, by_alias=True)`.
## Гоча 2 — `const: true` компилится в строковый enum
`"ok": { "type": "boolean", "const": true }` → openapi-generator генерит
`enum ('true')` со СТРОКОВЫМ значением → настоящий `true` (boolean) не проходит
валидацию. Убирать `const` из схемы или не использовать его на булевых полях.
## Гоча 3 — oneOf + discriminator не десериализуется
`Schedule` (kind: cron|interval|once, oneOf + discriminator) в сгенерированной
модели приходит с `actual_instance=None` — правило (`cron`) недостижимо через
модель, только через raw JSON: `raw["schedule"]["schedule"]["cron"]`.
Касается любого oneOf+discriminator на этой версии генератора (3.1-ветка).
## Окружение (Windows)
- `openapi-generator` 7.x требует **Java 11+**: системная Java 8 падает на
class-load; ставь `JAVA_HOME` на новый JDK (Temurin 25 работает).
Запуск: `npx --yes @openapitools/openapi-generator-cli generate -i spec.json -g python -o out --additional-properties=packageName=...`
- Windows excluded port ranges (Hyper-V/WinNAT) резервируют порты (напр. 8090/8091):
`EACCES` на listen. Проверка: `netsh interface ipv4 show excludedportrange protocol=tcp`.
- Консоль cp1251: не печатай ``/`«»` в скриптах — `'charmap' codec can't encode`; ASCII-only.
## Паттерн обхода (потребитель, клиент не трогаем)
Тонкий raw-слой рядом с сгенерированным пакетом (в примере — `raw_ops.py`):
только операции, где спека `{}`, через `call_api`; остальное — через типизированный
клиент. Сгенерированный пакет остаётся перегенерируемым.
Пример целиком: `sched/examples/admin-client-python/` (24/24 сьют, 19/19 smoke).