Files
admin/.wiki/concepts/openapi-generator-3-1-spec-gotchas.md

4.4 KiB
Raw Blame History

title, type, tags, related, updated
title type tags related updated
openapi-generator + OpenAPI 3.1 — gotchas генерации клиентов concept
openapi
openapi-generator
codegen
python
sched
ff
api-contract
sched-fnf-r8-verification-stand
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-транспорт + ручная валидация:
    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).