OpenAPI Generator — klienty i serwery API generowane ze specyfikacji
173 generatory zamieniające plik OpenAPI w gotowy klient API, stuby serwera albo dokumentację — w tym dziewięć dla PHP i dwanaście dla TypeScriptu. Pokazujemy pełny przebieg dla naszego stacku: klient PHP do cudzego API wstrzykiwany przez kontener Laravela, typowany klient TypeScript dla frontu w Reakcie, generowanie selektywne, nadpisywanie szablonów mustache i pinowanie wersji w CI. Plus dwie opcje domyślne, które cicho psują wygenerowany kod, i rzecz najważniejsza licencyjnie: wygenerowany kod świadomie nie jest objęty licencją projektu.
Klient API napisany ręcznie zaczyna się od trzech metod i jednego pliku. Po roku ma czterdzieści metod, własną obsługę ponowień, dwa różne sposoby serializacji daty, bo pierwszy okazał się niewystarczający, i komentarz „nie ruszać, działa" nad metodą, której nikt już nie rozumie. Najgorsze jest jednak nie to. Najgorsze jest, że gdy druga strona doda pole do odpowiedzi albo zmieni typ z liczby na łańcuch, nasz klient dowie się o tym w produkcji — od użytkownika.
Specyfikacja OpenAPI istnieje właśnie po to, żeby ten kontrakt był zapisany w jednym miejscu, w formie, którą czyta maszyna. OpenAPI Generator (github.com/OpenAPITools/openapi-generator) robi z tego zapisu kod: klienta w PHP, klienta w TypeScripcie, stuby serwera, dokumentację, kolekcję Postmana, schemat MySQL-a. Zmiana kontraktu przestaje być niespodzianką, a staje się diffem w pull requeście.
Poniżej: jak to uruchomić bez instalowania Javy, jakich flag używa się realnie, jak podłączyć wygenerowany klient do aplikacji w Laravelu i do frontu w Reakcie, oraz gdzie są pułapki — bo są, w tym dwie opcje domyślne, które psują wygenerowany kod w sposób trudny do zauważenia.
Stan projektu
Dane z API GitHuba na 12 września 2026:
- 26 734 gwiazdki i 7670 forków — jeden z najpopularniejszych projektów narzędziowych w ekosystemie API,
- repozytorium założone 12 maja 2018 jako fork Swagger Codegen, po tym jak zespół utrzymujący tamten projekt odszedł i założył własną organizację. Kod w Javie, licencja Apache-2.0,
- najnowsze stabilne wydanie
7.25.0z 24 sierpnia 2026, a7.26.0jest już zaplanowane na 24 września. Kadencja to wydanie minor raz na miesiąc lub dwa i, co istotne, opisywane w dokumentacji jako „minor release with breaking changes (with fallback)", - ponad sto commitów w ostatnim miesiącu, ostatni z 9 września 2026,
- 173 generatory — tyle plików liczy katalog dokumentacji generatorów, w tym dziewięć dla PHP (
php,php-nextgen,php-dt,php-laravel,php-lumen,php-symfony,php-slim4,php-flight,php-mezzio-ph) i dwanaście dla TypeScriptu, - 5754 otwarte zgłoszenia. Tej liczby nie da się zignorować i wrócimy do niej w rozdziale o pułapkach.
Zgodność ze specyfikacją: OpenAPI 2.0 i 3.0 w pełni, 3.1 w wersji beta. Obsługiwane są też historyczne wersje Swaggera od 1.0. Przy specyfikacji 3.1 warto to sprawdzić na własnym pliku przed podjęciem decyzji — beta oznacza tu dokładnie to, co powinna.
Projekt ma stronę z dokumentacją pod openapi-generator.tech, komitet techniczny, listę firm, które go używają, i sponsorów opłacających domeny oraz serwer usługi online. To nie jest narzędzie utrzymywane przez jedną osobę po godzinach.
Uruchomienie bez instalowania Javy
Narzędzie jest napisane w Javie i to jest jego jedyna twarda zależność. Są cztery drogi instalacji i tylko jedna z nich nie wymaga JVM na hoście.
Docker — wariant domyślny i rekomendowany. Nic nie instalujemy, wersję przypinamy tagiem obrazu:
docker run --rm -v "${PWD}:/local" openapitools/openapi-generator-cli:v7.25.0 generate \
-i /local/openapi.yaml \
-g php \
-o /local/build/api-clientKatalog roboczy trzeba zamontować jako wolumen — generator zapisuje wynik do systemu plików kontenera, więc bez montowania efekt zniknie razem z kontenerem. Ścieżki w flagach -i i -o odnoszą się do wnętrza kontenera, stąd prefiks /local.
npm — pakiet @openapitools/openapi-generator-cli jest opakowaniem, które samo pobiera odpowiedni JAR. JVM nadal jest wymagana, ale zarządzanie wersją jest wygodniejsze i mieści się w package.json:
npm install @openapitools/openapi-generator-cli -D
npx openapi-generator-cli version-manager set 7.25.0
npx openapi-generator-cli versionpip — analogiczne opakowanie w ekosystemie Pythona (pip install openapi-generator-cli==7.25.0), z wariantem [jdk4py], który dociąga własną Javę, gdy na maszynie jej nie ma. Homebrew i goły JAR to dwie pozostałe opcje; JAR jest przydatny wtedy, gdy pipeline i tak ma Javę.
Istnieje też obraz openapi-generator-online — samodzielna usługa webowa z API do generowania kodu, którą można postawić u siebie i wołać po HTTP. Publiczne instancje pod api.openapi-generator.tech są oznaczone jako beta i bez gwarancji poziomu usług, więc do niczego poważnego nie należy się na nich opierać.
Flagi, których używa się realnie
help generate wypisuje kilkadziesiąt opcji. W codziennej pracy liczy się kilka:
-i/--input-spec— ścieżka albo adres URL specyfikacji. Przy zdalnej specyfikacji za autoryzacją jest jeszcze-a, przyjmujące zakodowany łańcuchnazwa:nagłówek,-g/--generator-name— nazwa generatora, na przykładphp,typescript-axios,php-laravel,-o/--output— katalog wynikowy,-c/--config— plik konfiguracyjny; o nim niżej, bo to jest właściwy sposób pracy,-p/--additional-properties— opcje specyficzne dla generatora, jako listaklucz=wartośćpo przecinkach,--global-property— sterowanie tym, co się generuje (modele, API, pliki wspierające); osobny rozdział niżej,-t/--template-dir— katalog z własnymi szablonami nadpisującymi wbudowane,--library— wariant biblioteki w obrębie generatora (dla Javy na przykładokhttp-gson,resttemplate,feign),--dry-run— pokazuje, co zostałoby zapisane, bez zapisywania. Pierwsza rzecz, którą warto odpalić na cudzej specyfikacji,--minimal-update— zapisuje wyłącznie pliki, których treść faktycznie się zmieniła. Bez tego każde uruchomienie dotyka wszystkich plików, co zamienia diff w pull requeście w bezużyteczną ścianę,--skip-validate-spec— pomija walidację specyfikacji. Kusi przy cudzym, niedomkniętym pliku i jest to zwykle zła decyzja: generator, który nie zwalidował wejścia, wypluje kod, który się kompiluje i nie działa,--model-name-prefixi--model-name-suffix— ratują sytuację, gdy nazwy schematów kolidują z klasami, które już mamy.
Plik konfiguracyjny zamiast komendy na pół ekranu
Komenda z dziesięcioma flagami i dwudziestoma dodatkowymi właściwościami jest nie do utrzymania. Konfigurację trzyma się w pliku YAML w repozytorium, obok specyfikacji:
# openapi/php-client.yaml
generatorName: php
inputSpec: openapi/openapi.yaml
outputDir: packages/billing-client
templateDir: openapi/templates/php
additionalProperties:
invokerPackage: HypeDev\Billing
composerPackageName: hypedev/billing-client
artifactVersion: 1.4.0
variableNamingConvention: camelCase
hideGenerationTimestamp: truedocker run --rm -v "${PWD}:/local" openapitools/openapi-generator-cli:v7.25.0 generate \
-c /local/openapi/php-client.yamlZysk jest podwójny: komenda w pipeline'ie jest jednolinijkowa, a konfiguracja generowania podlega review razem z resztą kodu. hideGenerationTimestamp: true jest tu warte osobnego zdania — bez tego każdy wygenerowany plik dostaje w komentarzu znacznik czasu, więc każde uruchomienie generuje diff we wszystkich plikach, niezależnie od tego, czy cokolwiek się zmieniło. To pierwsza rzecz, którą trzeba wyłączyć, jeśli wygenerowany kod ma trafiać do repozytorium.
Generowanie selektywne
Domyślnie generator produkuje wszystko, co dany generator obsługuje: modele, klasy API, pliki wspierające, testy i dokumentację. Rzadko jest to pożądane. Zakres zawęża --global-property:
# tylko modele
--global-property models
# tylko klasy API, bez testów
--global-property apis,apiTests=false
# modele i pliki wspierające
--global-property models,supportingFiles
# wybrane modele, bez testów i bez dokumentacji
--global-property models="User:Pet",modelTests=false,modelDocs=falseWersja z listą po dwukropkach przydaje się, gdy z dużej specyfikacji potrzebujemy trzech typów, a nie stu czterdziestu. Ważny szczegół działania: przy generowaniu selektywnym używane są wyłącznie szablony potrzebne dla wybranego zakresu, więc pliki wspierające, które normalnie by powstały, po prostu się nie pojawią — i to jest zamierzone, nie błąd.
Jest jeszcze skipFormModel (domyślnie true od wersji 5.0.0), pomijające generowanie modeli powstających z parametrów formularza w requestBody. Zostawienie domyślnej wartości jest tu praktycznie zawsze właściwe.
Plik ignorowania
Wygenerowany kod niemal nigdy nie jest potrzebny w całości. Do wykluczania służy .openapi-generator-ignore, składniowo bliźniaczo podobny do .gitignore i umieszczany w katalogu wynikowym:
# .openapi-generator-ignore
# nie chcemy wygenerowanego composer.json — mamy własny
composer.json
# cała dokumentacja poza jednym plikiem
docs/**
!docs/UserApi.md
# testy generatora — mamy własne
test/**
# pojedynczy plik
src/Configuration.phpMa to jedną przewagę nad flagą --skip-overwrite: kontrolę na poziomie pojedynczych plików, a nie „wszystko albo nic". Reguły negacji (!) działają, ale z ograniczeniem, które trzeba znać: nie da się odwrócić wykluczenia pliku leżącego w wykluczonym katalogu. Jeśli reguła wyklucza src/**/Api/, to !src/**/Api/PetApi.php nie zadziała.
Do pierwszego uruchomienia, gdy pliku jeszcze nie ma, jest flaga --ignore-file-override=/ścieżka/do/pliku. Uwaga: to całkowite nadpisanie, więc plik leżący już w katalogu wynikowym zostanie zignorowany. Alternatywnie opcja openapiGeneratorIgnoreList pozwala wygenerować .openapi-generator-ignore z zadaną listą wpisów w trakcie pierwszego przebiegu.
Własne szablony
Generatory używają szablonów Mustache i każdy z nich da się nadpisać. Wystarczy skopiować szablon z repozytorium projektu do własnego katalogu, zmienić i wskazać ten katalog flagą -t. Nazwa i położenie pliku muszą się zgadzać — przy generatorach z opcją --library szablon musi leżeć w tej samej ścieżce względnej, na przykład my_templates/libraries/jvm-okhttp/api.mustache.
Od wersji 5.0.0 można też dodawać własne pliki przez sekcję files w pliku konfiguracyjnym, bez pisania własnego generatora:
templateDir: my_custom_templates
additionalProperties:
artifactId: kotlin-petstore-client
files:
AUTHORS.md: {}
api_interfaces.mustache:
templateType: API
destinationFilename: Interface.kt
other/check.mustache:
folder: scripts
destinationFilename: check.sh
templateType: SupportingFilesPierwszy wpis kopiuje plik bez przetwarzania (rozszerzenie nie jest szablonowe). Drugi kompiluje szablon raz na każde API — a API powstają z tagów w specyfikacji, więc tag Equipment da plik EquipmentInterface.kt. Trzeci generuje jednorazowy plik wspierający we wskazanym podkatalogu.
Rada praktyczna: własne szablony to potężne narzędzie i realny dług. Każda aktualizacja generatora może zmienić szablon wbudowany, na którym oparliśmy własny, i nikt nas o tym nie powiadomi. Zanim skopiujesz api.mustache, sprawdź, czy tego samego efektu nie da additionalProperties — lista opcji na generator jest długa i pokrywa większość typowych potrzeb.
Mapowanie typów i reużycie własnych klas
Domyślne odwzorowanie typów ze specyfikacji na typy języka rzadko jest dokładnie tym, czego chcemy. Służą do tego dwie flagi, których zwykle używa się razem:
--type-mappingsokreśla typ docelowy w generowanym kodzie,--import-mappingsmówi szablonowi, jaki import dopisać dla tego typu.
Kanoniczny przykład z dokumentacji zamienia domyślne OffsetDateTime na LocalDateTime w generatorze serwera Kotlina:
--import-mappings=DateTime=java.time.LocalDateTime \
--type-mappings=DateTime=java.time.LocalDateTimeZwróć uwagę, że mapowanie stosuje się do klucza DateTime, bo to jest wewnętrzna reprezentacja typu prymitywnego w generatorze — nie do nazwy z naszej specyfikacji. Nie wszystkie mapowania da się przy tym nadpisać; część generatorów definiuje odwzorowania ściśle powiązane z wbudowanymi szablonami.
Osobny, bardzo praktyczny mechanizm: reużycie klasy, którą już mamy w projekcie. Jeśli w kodzie istnieje własny Pet i nie chcemy, żeby generator tworzył swój, oznaczamy ten typ jako prymityw i dopisujemy import:
--language-specific-primitives=Pet \
--import-mappings=Pet=App\\Models\\PetPierwsza flaga każe generatorowi uznać Pet za typ wbudowany i nie generować dla niego modelu; druga podaje, skąd go zaimportować. Przy integracji, w której kilka schematów odpowiada encjom istniejącym już w naszej domenie, oszczędza to warstwy tłumaczącej jedne obiekty na drugie.
Formatowanie wygenerowanego kodu
Wygenerowany kod nie trzyma się stylu naszego projektu i przy commitowaniu go do repozytorium natychmiast staje się to widoczne. Rozwiązaniem jest post-processing: generator może wywołać zewnętrzne narzędzie dla każdego wygenerowanego pliku osobno, przekazując mu ścieżkę.
Mechanizm jest świadomie dwuczłonowy z powodów bezpieczeństwa — trzeba jednocześnie ustawić zmienną środowiskową i włączyć flagę:
export PHP_POST_PROCESS_FILE="vendor/bin/pint"
docker run --rm -v "${PWD}:/local" -e PHP_POST_PROCESS_FILE \
openapitools/openapi-generator-cli:v7.25.0 generate \
--enable-post-process-file \
-c /local/openapi/php-client.yamlZmienna zależy od języka: PHP_POST_PROCESS_FILE, TS_POST_PROCESS_FILE, PYTHON_POST_PROCESS_FILE, GO_POST_PROCESS_FILE i kilkanaście dalszych. Wartością może być dowolne polecenie, także własny skrypt uruchamiający kilka narzędzi po kolei — więc nic nie stoi na przeszkodzie, żeby przepuścić wygenerowanego klienta PHP przez Pinta, a klienta TypeScript przez Prettiera, i mieć w repozytorium kod nie do odróżnienia stylem od pisanego ręcznie. Przy uruchamianiu w kontenerze pamiętaj, że narzędzie musi być dostępne wewnątrz kontenera — przy formatowaniu Pintem prościej jest odpalić go osobnym krokiem po generowaniu.
W naszym stacku: trzy scenariusze
1. Klient PHP do cudzego API
Najczęstszy przypadek: integrujemy się z API, które ma specyfikację OpenAPI. Zamiast pisać klienta ręcznie, generujemy go i traktujemy jak zależność.
Do wyboru są dwa generatory klienta PHP i różnica jest istotna: php ma status STABLE, php-nextgen ma status BETA (nowsza implementacja, docelowo następca). Do produkcji dziś wybiera się php, chyba że coś konkretnego wymaga nowej wersji.
# openapi/php-client.yaml
generatorName: php
inputSpec: openapi/billing.yaml
outputDir: packages/billing-client
additionalProperties:
invokerPackage: HypeDev\Billing
composerPackageName: hypedev/billing-client
variableNamingConvention: camelCase
hideGenerationTimestamp: true
enumUnknownDefaultCase: trueWygenerowany pakiet podłącza się do aplikacji jako repozytorium ścieżkowe, bez publikowania czegokolwiek:
{
"repositories": [
{ "type": "path", "url": "packages/billing-client" }
],
"require": {
"hypedev/billing-client": "*"
}
}A potem konfigurację klienta rejestruje się w kontenerze, żeby reszta aplikacji nie wiedziała nic o tokenach ani adresach:
<?php
namespace App\Providers;
use HypeDev\Billing\Api\InvoicesApi;
use HypeDev\Billing\Configuration;
use Illuminate\Support\ServiceProvider;
class BillingServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->scoped(InvoicesApi::class, function (): InvoicesApi {
$config = Configuration::getDefaultConfiguration()
->setHost(config('services.billing.host'))
->setAccessToken(config('services.billing.token'));
return new InvoicesApi(config: $config);
});
}
}Świadomie scoped(), a nie singleton() — aplikacja chodzi na Octane, więc obiekt trzymający konfigurację i klienta HTTP nie powinien przeżywać żądania. Dalej wstrzykuje się InvoicesApi normalnie przez konstruktor i mamy typowane metody z podpowiadaniem w IDE, zamiast Http::post() z tablicą.
Dwie rzeczy warto ustawić od razu. variableNamingConvention domyślnie ma wartość snake_case, co w PHP wygląda obco — camelCase jest tu zwykle właściwe. I enumUnknownDefaultCase: true: bez tego dodanie przez dostawcę nowej wartości enum wywali parsowanie odpowiedzi w naszym kliencie; z tym każdy enum dostaje przypadek unknown_default_open_api, do którego nieznana wartość bezpiecznie wpada. Domyślnie jest to false, czyli klient jest kruchy — a to kruchość, o której dowiemy się w produkcji.
2. Typowany klient TypeScript dla frontu
Nasze aplikacje mają front w Reakcie na Inertii, więc drugi naturalny przypadek to klient do naszego własnego API — generowany z tej samej specyfikacji, którą wystawia backend. Generator typescript-axios ma status STABLE i sensowne opcje:
# openapi/ts-client.yaml
generatorName: typescript-axios
inputSpec: openapi/openapi.yaml
outputDir: resources/js/api
additionalProperties:
withSeparateModelsAndApi: true
apiPackage: apis
modelPackage: models
stringEnums: true
useSingleRequestParameter: true
supportsES6: true
enumPropertyNaming: PascalCaseTrzy z tych opcji zmieniają jakość życia w kodzie frontu:
withSeparateModelsAndApirozdziela modele i klasy API do osobnych katalogów i klas. Wymaga podaniaapiPackageimodelPackage. Bez tego wszystko ląduje w jednym wielkim pliku, co przy stu typach jest nie do czytania,stringEnumsgeneruje enumy łańcuchowe zamiast obiektów — dużo naturalniejsze w TypeScripcie i lepiej współpracujące z porównaniami,useSingleRequestParameterzmienia sygnatury na jeden obiekt z wszystkimi parametrami zamiast listy argumentów pozycyjnych. Przy endpointach z sześcioma opcjonalnymi parametrami to różnica między czytelnym wywołaniem a zgadywaniem kolejności.
Generowanie wpina się w skrypty npm, żeby nikt nie musiał pamiętać komendy:
{
"scripts": {
"api:generate": "docker run --rm -v \"${PWD}:/local\" openapitools/openapi-generator-cli:v7.25.0 generate -c /local/openapi/ts-client.yaml"
}
}Warto dodać, że przy Inertii duża część danych i tak przychodzi propsami z kontrolera, więc generowany klient nie zastępuje tam niczego. Ma sens dla tego, co front wywołuje samodzielnie: pobierania danych po interakcji, panelu z tabelami, integracji z API zewnętrznym. Innymi słowy — nie generuj klienta do endpointów, których front nigdy nie woła.
3. Stuby serwera
Trzecia kategoria to generatory typu SERVER. Dla PHP jest ich kilka, w tym php-laravel ze statusem STABLE oraz php-slim4, php-symfony, php-lumen i php-flight. Generator Laravela ma nawet własną opcję autowire.
I tu potrzebna jest szczerość, której dokumentacja nie dostarczy: generowanie serwera ma sens jednorazowo, jako scaffolding, a nie jako proces ciągły. Wygenerowane trasy, kontrolery i modele są punktem startowym, ale w chwili, gdy dopiszemy do nich logikę biznesową, ponowne uruchomienie generatora przestaje być bezpieczne. Można to opanować przez .openapi-generator-ignore i rozdzielenie warstwy wygenerowanej od implementacji, tylko że wtedy okazuje się, że utrzymujemy dwa systemy naraz.
Dla aplikacji w Laravelu bardziej opłacalny jest podział: specyfikacja jest kontraktem i źródłem klientów, a implementacja jest pisana ręcznie, z testem sprawdzającym, że odpowiedź endpointu zgadza się ze schematem. Generatory serwera zostawiamy do prototypów i do sytuacji, w której API dopiero powstaje i chcemy szybko postawić działającą atrapę.
Skąd wziąć specyfikację
Generator jest tylko tak dobry, jak plik na wejściu, a to pytanie rozstrzyga się na poziomie procesu, nie narzędzia. Są dwie drogi.
Spec-first: specyfikacja jest pisana ręcznie i jest źródłem prawdy. Z niej generujemy klientów dla wszystkich konsumentów, ona idzie do review przy zmianie kontraktu i ona jest tym, co pokazujemy zewnętrznemu zespołowi. Implementacja backendu dostosowuje się do niej, a nie odwrotnie. Kosztuje dyscyplinę i daje dokładnie to, po co się to robi.
Code-first: specyfikacja jest generowana z kodu — z atrybutów, adnotacji albo analizy statycznej. W ekosystemie Laravela używa się do tego osobnych pakietów. Wygodniejsze na start i podatne na jeden problem: specyfikacja opisuje wtedy to, co kod robi, także wtedy, gdy kod robi coś niezamierzonego.
Dla OpenAPI Generatora różnica nie ma znaczenia — dostaje plik i produkuje kod. Ale przy code-first warto ten wygenerowany plik commitować do repozytorium, żeby zmiana kontraktu była widoczna w diffie. Bez tego traci się największą korzyść z całego układu: to, że nikt nie zmieni API po cichu.
Jeden szczegół, który psuje wygenerowany kod niezależnie od drogi: brak operationId w specyfikacji. Bez niego generator wymyśla nazwy metod z metody HTTP i ścieżki, więc zamiast listInvoices() dostajemy invoicesGet(), a przy zagnieżdżonych zasobach nazwy stają się nieczytelne. Uzupełnienie operationId to najtańsza możliwa poprawa jakości generowanego kodu.
W CI/CD
Projekt dostarcza wtyczki do Mavena, Gradle'a, sbt i Bazela, ale w naszym świecie prościej jest wywołać obraz Dockera. Trzy decyzje, które warto podjąć świadomie.
Pinuj wersję generatora. Tag latest w obrazie oznacza, że wygenerowany kod może się zmienić bez żadnej zmiany po naszej stronie — a wydania minor są w dokumentacji jawnie opisane jako zawierające zmiany łamiące zgodność. Wersja w tagu obrazu i w pliku konfiguracyjnym to jedna linia zabezpieczenia.
Commituj wygenerowany kod. Kusi, żeby generować w pipeline'ie i nie brudzić repozytorium. Argumenty za commitowaniem są mocniejsze: kod jest przeglądalny w pull requeście, zmiana kontraktu jest widoczna jako diff, środowisko deweloperskie nie wymaga Dockera ani Javy do uruchomienia projektu, a przy awarii rejestru obrazów nic się nie zatrzymuje. Warunek to hideGenerationTimestamp: true i --minimal-update, inaczej diff będzie bezużyteczny.
Sprawdzaj w CI, czy wygenerowany kod jest aktualny. Najprostszy wariant to zadanie, które generuje kod do katalogu tymczasowego i porównuje z tym w repozytorium:
# w pipeline: wykryj rozjazd specyfikacji i wygenerowanego klienta
docker run --rm -v "${PWD}:/local" openapitools/openapi-generator-cli:v7.25.0 generate \
-c /local/openapi/php-client.yaml -o /local/build/check
diff -r packages/billing-client build/check || {
echo "Wygenerowany klient jest nieaktualny — odpal generator i zacommituj wynik."
exit 1
}Zadanie zapala się wtedy, gdy ktoś zmienił specyfikację i zapomniał odświeżyć klienta. To dokładnie ten błąd, którego szukamy, i najtańszy moment na jego wyłapanie.
Nie tylko klienty
Nazwa sugeruje generowanie kodu, ale ze specyfikacji da się wyprodukować także rzeczy, które kodem nie są — i część z nich jest niedoceniana:
- Dokumentacja — generatory
html2,markdown,asciidoc,confluence-wikiiplantuml. Wariantmarkdownjest wart uwagi przy dokumentacji trzymanej w repozytorium: opis endpointów przestaje się rozjeżdżać z API, bo powstaje z tego samego pliku, - Kolekcja Postmana — generator
postman-collection. Zdejmuje z zespołu pracę ręcznego utrzymywania kolekcji, która i tak zawsze jest o dwa endpointy z tyłu, - Testy wydajnościowe — generatory
k6ijmeterprodukują scenariusze obciążeniowe wprost ze specyfikacji. Punkt startowy, nie gotowy test, ale wygenerowanie szkieletu dla czterdziestu endpointów jest kwestią jednej komendy, - Protocol Buffer, schemat MySQL-a, WSDL, GraphQL — wyjścia, które przydają się przy migracjach i integracjach z systemami mówiącymi innym protokołem,
- Konfiguracja Apache2 — jedyny generator w kategorii plików konfiguracyjnych.
Jest też droga do opublikowania wygenerowanego klienta jako osobnego repozytorium. Generator tworzy skrypt git_push.sh, a flagi --git-user-id, --git-repo-id i --release-note wypełniają metadane:
-g php --git-user-id "hypedev-group" --git-repo-id "billing-client" \
--release-note "Zgodne ze specyfikacją 1.4.0"Przy kliencie używanym przez kilka naszych projektów jest to sensowniejsze niż kopiowanie katalogu: jedno repozytorium, wersjonowanie przez tagi, instalacja Composerem z prywatnego rejestru pakietów.
Poza CLI projekt dostarcza też wtyczki do Gradle'a, Mavena, sbt i Bazela oraz obsługę Cake'a, a społeczność utrzymuje wtyczki do IntelliJ IDEA, Eclipse'a, Visual Studio i VS Code'a. Część generatorów wypluwa dodatkowo pliki konfiguracji CI (na przykład .travis.yml) dla wygenerowanego pakietu — zwykle jedna z pierwszych rzeczy, które trafiają do .openapi-generator-ignore.
Pułapki
- 173 generatory o bardzo różnej jakości. Każdy ma w dokumentacji pole generator stability z wartością STABLE, BETA albo EXPERIMENTAL i to jest pierwsza rzecz do sprawdzenia przed podjęciem decyzji.
phpjest STABLE,php-nextgenjest BETA,php-laraveljest STABLE. Różnica w praktyce dotyczy nie tylko liczby błędów, ale też tego, czy generator będzie utrzymywany — projekt ma etykietę „Decommission" dla wycofanych generatorów i korzysta z niej, disallowAdditionalPropertiesIfNotPresentdomyślnietrue. To ustawienie zachowuje stare, niezgodne ze specyfikacją zachowanie, w którymadditionalPropertiesjest domyślniefalse. Dokumentacja mówi to wprost, nazywając zachowanie domyślne „incorrect". Ustawieniefalsedaje zgodność z OAS i JSON Schema. Jeśli wygenerowany klient odrzuca odpowiedzi zawierające pola, których nie ma w schemacie — to jest to,enumUnknownDefaultCasedomyślniefalse. Nowa wartość enum po stronie serwera wywala parsowanie odpowiedzi. Włączenie tej opcji dodaje przypadek awaryjny. Przy kliencie do cudzego API to nie jest opcja, to jest wymóg,- Java jest wymagana. Opakowania npm i pip niczego tu nie zmieniają — pobierają JAR i uruchamiają go na lokalnej JVM. Docker jest jedyną drogą, która nie dotyka hosta,
- Nie edytuj wygenerowanego kodu. Brzmi banalnie i jest najczęściej łamaną zasadą w tym narzędziu. Poprawka wprowadzona ręcznie zniknie przy następnym uruchomieniu, po cichu. Jeśli plik wymaga zmian, są dwie właściwe drogi:
.openapi-generator-ignorei pisanie go samodzielnie, albo własny szablon, - 5754 otwarte zgłoszenia. Przy 173 generatorach to około trzydziestu na generator i taka liczba oznacza jedno: przy niszowym generatorze i nietypowej specyfikacji trafienie na znany, niezałatany błąd jest realne. Zanim narzędzie wejdzie w krytyczną ścieżkę, warto poszukać w zgłoszeniach nazwy swojego generatora razem z konstrukcją, której używa nasza specyfikacja —
oneOf,allOfi dyskryminatory są w tym zestawie klasykami, - Wsparcie dla OpenAPI 3.1 jest w wersji beta. Przy specyfikacji 3.1 trzeba to sprawdzić na własnym pliku, a nie założyć,
- Jakość wyniku zależy od jakości specyfikacji. Brakujące
operationId, brak schematów odpowiedzi błędów, typy opisane jako gołeobject— wszystko to przechodzi do kodu. Generator nie naprawia niedbałej specyfikacji, tylko ją wiernie odtwarza.
Podsumowanie
OpenAPI Generator rozwiązuje problem, który w zespołach pracujących z cudzymi API pojawia się zawsze: klient napisany ręcznie rozjeżdża się z kontraktem i nikt tego nie widzi do momentu awarii. Gdy kod powstaje ze specyfikacji, rozjazd staje się diffem, a diff da się przejrzeć. Co z tego wynika przy planowaniu wdrożenia:
- Projekt jest duży i utrzymywany — 26 734 gwiazdki, 7670 forków, wydanie
7.25.0z 24 sierpnia 2026, ponad sto commitów w miesiącu, komitet techniczny i sponsorzy. Powstał w 2018 jako fork Swagger Codegen i od tego czasu nie zwolnił, - Uruchamiaj przez Dockera z przypiętym tagiem. To jedyny wariant, który nie wymaga Javy na hoście, i najtańszy sposób na powtarzalność między maszyną deweloperską a pipeline'em,
- Konfigurację trzymaj w pliku YAML w repozytorium, nie w komendzie. Komenda staje się jednolinijkowa, a zmiana sposobu generowania podlega review,
- Ustaw
hideGenerationTimestamp: truei używaj--minimal-update, jeśli wygenerowany kod ma trafiać do repozytorium — bez tego każde uruchomienie produkuje diff we wszystkich plikach, - Zawężaj zakres przez
--global-propertyi wykluczaj pliki przez.openapi-generator-ignore. Domyślnie generator produkuje też testy i dokumentację, których zwykle nie chcemy, - Sprawdź status generatora (STABLE / BETA / EXPERIMENTAL) przed decyzją. Dla klienta PHP to dziś
php, niephp-nextgen, - Włącz
enumUnknownDefaultCasei rozważdisallowAdditionalPropertiesIfNotPresent: false— dwie opcje, których wartości domyślne czynią klienta kruchym na zmiany po stronie serwera, - Klient tak, serwer ostrożnie. Generowanie klientów opłaca się jako proces ciągły; generowanie serwera raczej jako jednorazowy scaffolding, bo po dopisaniu logiki biznesowej ponowne uruchomienie przestaje być bezpieczne,
- Przepuść wygenerowany kod przez własny formatter —
--enable-post-process-fileplusPHP_POST_PROCESS_FILEalboTS_POST_PROCESS_FILEsprawiają, że wygenerowane pliki nie odstają stylem od reszty repozytorium, - Reużywaj własnych klas przez
--language-specific-primitivesi--import-mappings, zamiast pisać warstwę tłumaczącą modele generatora na modele domeny, - Uzupełnij
operationIdw specyfikacji. Najtańsza pojedyncza poprawa jakości generowanego kodu, jaka istnieje, - Dodaj do CI kontrolę aktualności — wygenerowanie klienta do katalogu tymczasowego i
diffwzględem repozytorium łapie dokładnie ten błąd, o który w tym wszystkim chodzi.
Licencja: narzędzie jest rozpowszechniane na Apache License 2.0 — permisywnej licencji open source, dopuszczającej komercyjne użycie bez zastrzeżeń, z wymogiem zachowania noty o prawach autorskich i wskazania istotnych zmian w plikach objętych licencją. Apache-2.0 zawiera też jawne udzielenie licencji patentowej, co przy narzędziu wchodzącym do procesu budowania jest zaletą, nie formalnością. Najważniejsze jest natomiast to, co projekt deklaruje osobno w sprawie wyniku działania: szablony są objęte licencją projektu, ale kod wygenerowany świadomie nie jest. Dokumentacja mówi wprost, że wygenerowany kod należy traktować jako „AS IS", jest własnością użytkownika narzędzia, nie ma do niego żadnych gwarancji i podlega tym warunkom licencyjnym, jakie użytkownik uzna za właściwe. W praktyce oznacza to, że wygenerowany klient można bez żadnych rozważań licencyjnych wkleić do zamkniętego produktu, opublikować pod własną licencją albo sprzedać klientowi — i jest to jeden z powodów, dla których to narzędzie tak dobrze przyjęło się w firmach.