SwaggerCodegenとは
This is the Swagger Codegen project, which allows generation of API client libraries (SDK generation), server stubs and documentation automatically given an OpenAPI Spec. Currently, the following languages/frameworks are supported:
SwaggerCodegenはOpenAPIの仕様に沿ったファイルを変換し,APIのクライアントやスタブサーバー,ドキュメントを自動生成するプロダクトです.
今回は,このSwaggerCodegenの使い方や拡張の仕方,実用への課題などを書きたいと思います.
SwaggerCodegenの難しさ
圧倒的に情報量が少ない
これにつきます.
日本語の資料であれば,
こんなに簡単! Swagger Codegenのカスタマイズ
ぐらいしかありません.
では,公式のドキュメントは?というと
20行から30行ぐらいしか記述がなく,使いこなすのは結構厳しいです.
したがって本当に使いこなそうと思うとSwaggerCodegenのソースコードを読むことになります.さすがにそれは辛いです.
というわけで,このドキュメントでは
SwaggerCodegenでコード生成するために必要なドキュメントのリンクを集め,サンプルや備考を交えながら,できるだけ簡単にコード生成ができるようになることを目的とします.
環境導入
swagger-codegenの導入
wget "http://central.maven.org/maven2/io/swagger/swagger-codegen-cli/2.4.6/swagger-codegen-cli-2.4.6.jar -O swagger-codegen-cli.jar"
今回は,先人の通った道の通り,Groovyで作っていきたいと思います.
Groovyのインストール
$ apt-get install groovy
基本的な使い方
SwaggerCodegenの使い方は大きく3つあります.
- SwaggerCodegen組み込みのコード生成機能を使う
- SwaggerCodegen組み込みのコード生成機能をテンプレートで拡張する
- SwaggerCodegenのコード生成機能を拡張する
今回はSwagger Editorにあるサンプルのymlを使って説明していきます.
SwaggerCodegen組み込みのコード生成機能を使う
1.の「SwaggerCodegen組み込みのコード生成機能を使う」の例です.
$ java -jar swagger-codegen-cli.jar generate \
-i swagger.yml \
-l python \
-o python_test
-i
にswaggerのymlファイルを指定し,-l
で利用する言語を指定します.-o
で出力するディレクトリを指定します.-l
で指定できる言語は,langs
をオプションにしていすることで確認できます.
$ java -jar swagger-codegen-cli.jar langs
> Available languages: [ada, ada-server, akka-scala, android, apache2, apex, aspnetcore, bash, csharp, clojure, cwiki, cpprest, csharp-dotnet2, dart, dart-jaguar, elixir, elm, eiffel, erlang-client, erlang-server, finch, flash, python-flask, go, go-server, groovy, haskell-http-client, haskell, jmeter, jaxrs-cxf-client, jaxrs-cxf, java, inflector, jaxrs-cxf-cdi, jaxrs-spec, jaxrs, msf4j, java-pkmst, java-play-framework, jaxrs-resteasy-eap, jaxrs-resteasy, javascript, javascript-closure-angular, java-vertx, kotlin, lua, lumen, nancyfx, nodejs-server, objc, perl, php, powershell, pistache-server, python, qt5cpp, r, rails5, restbed, ruby, rust, rust-server, scala, scala-gatling, scala-lagom-server, scalatra, scalaz, php-silex, sinatra, slim, spring, dynamic-html,
html2, html, swagger, swagger-yaml, swift4, swift3, swift, php-symfony, tizen, typescript-aurelia, typescript-angular, typescript-inversify, typescript-angularjs, typescript-fetch, typescript-jquery, typescript-node, undertow, ze-ph, kotlin-server]
SwaggerCodegen組み込みのコード生成機能をテンプレートで拡張する
次は2.の「SwaggerCodegen組み込みのコード生成機能をテンプレートで拡張する」です.
SwaggerCodegenのコード生成機能は2つの機能により構成されます.1つは「コード生成(Generator)」と「テンプレート機能」です.簡単に拡張する分には,「テンプレート機能」が有用です.
先ほどの,Pythonで出力した例を取り上げます.組み込みのコード生成機能のテンプレートは以下のディレクトリの下にあります.この中のpythonをダウンロードしてきます.
ここでは一旦swagger-codegenのリポジトリをクローンして,テンプレートをコピーしてきます.
$ git clone https://github.com/swagger-api/swagger-codegen.git
$ cp -r swagger-codegen/modules/swagger-codegen/src/main/resources/python/ python_extend_template
SwaggerCodegenのテンプレートはmustacheというテンプレート言語で書かれています.ここでは簡単にrequirements.mustacheというテンプレートを書き換えます.
certifi >= 14.05.14
six >= 1.10
python_dateutil >= 2.5.3
setuptools >= 21.0.0
urllib3 >= 1.15.1
# テストで以下の行を追加
tqdm
このように自作でテンプレートを拡張した場合は,以下のコマンドで出力できます.
$ java -jar swagger-codegen-cli.jar generate \
-i swagger.yml \
-l python \
-t python_extend_template/ \
-o python_extend_template_test
注目すべきは-t
オプションで,この引数に自作のテンプレートのフォルダを指定します.
$ cat python_extend_template_test/requirements.txt
certifi >= 14.05.14
six >= 1.10
python_dateutil >= 2.5.3
setuptools >= 21.0.0
urllib3 >= 1.15.1
# テストで以下の行を追加
tqdm
このように出力されたファイルが変更されていることを確認できます.
SwaggerCodegenのコード生成機能を拡張する
この内容はほぼこんなに簡単! Swagger Codegenのカスタマイズ
と同じになります.この節ではジェネレーターを自分で作成します.今回は言語はTypeScriptでのサンプルです.
@Grab('io.swagger:swagger-codegen-cli:2.1.4')
import io.swagger.codegen.*;
import io.swagger.codegen.languages.*;
class SampleTypeScriptClientGen extends AbstractTypeScriptClientCodegen {
String name = "sample-typescript"
String help = "Sample Codegen"
public SampleTypeScriptClientGen() {
super()
}
@Override
public void processOpts() {
super.processOpts();
apiTemplateFiles.put("sample.mustache",".ts");
}
}
SwaggerCodegen.main(args)
最小の例としては,name
とhelp
の宣言と,コンストラクターとprocessOptsを実装しておく必要があります.
その次にテンプレートを作成します.
export class {{classname}}Sample {
{{#operations}}{{#operation}}
public {{operationId}}(
{{#allParams}}{{paramName}}:{{{dataType}}}{{#hasMore}},{{/hasMore}} {{/allParams}}
):
{{^returnType}}void{{/returnType}}{{#returnType}} {{{returnType}}} {{/returnType}}
{ }
{{/operation}}{{/operations}}
}
以下のコマンドで実際の出力を行います.
$ groovy sample-generator.groovy generate \
-i swagger.yml \
-l SampleTypeScriptClientGen \
-t sample/ \
-o sample_test
ここで注目すべきポイントは2つです.-l
オプションで渡された出力する言語です.これは先ほどのGroovyファイルで宣言したクラス名になります.もう1つは-t
で自作のテンプレートの入ったフォルダを指定します.
実際の出力結果は以下のようになります.
export class PetApiSample {
public updatePet(
body:Pet
):
void
{ }
public addPet(
body:Pet
):
void
{ }
(略)
これだけの例だと何をしているか分からないと思います.そのため,次節以降では,SwaggerCodeGenではどのような言語が使えるのか?テンプレート内で使える変数はどのようなものがあるのか?変数の意味は?といったことを紹介していきたいと思います.
自作ジェネレーター制作
プログラミング言語を選ぶ
先ほどlangs
のオプションでも表示していましたが,Swaggerではかなりのプログラミング言語の出力に対応しています.
有名どころだとJava,Php,TypeScript,Goなんかがありますが,変わり種だとAdaとかもあります.
この辺の抽象クラスを継承してつくるのが簡単です.例えば,TypeScriptでは,AbstractTypeScriptClientCodegenを継承して自作のジェネレーターを作ります.そのように作られたジェネレーターが同じフォルダにあるTypeScriptAngularClientCodegenなどになります.
テンプレートの変数
先の例でも紹介しましたが,Swaggerでは主にテンプレート機能でコード生成を行います.そこに使える変数は何か?というと2つのドキュメントが参考になります.
OpenAPIの仕様書
これはSwaggerが準拠している仕様の部分にあたります.ここで宣言されている値は大体使えます.このドキュメントはswagger.ymlを書く時にも役に立ちますが,まぁまぁ読みにくいです.
SwaggerAPIのMustacheTemplateVariables
例えばclassname
でSwaggerCodegenにより生成されたクラス名にアクセスできます.ほかに有用な例であれば,上でも使った{hasMore}
などがあります.
{{#allParams}}{{paramName}}:{{{dataType}}}{{#hasMore}},{{/hasMore}} {{/allParams}}
関数の引数を定義する場合,引数の間に","を追加したい場合があります.その時に,{{#hasMore}}
を用いて,最後のオブジェクトかそうでないかを判定し,","を付与するかしないかを判定します.
3つのテンプレート
SwaggerCodegenのコード生成では3つの重要なテンプレートが存在します.
それぞれの用途と使い方を説明します.
supportingFiles
実際の仕様の仕方としては,groovyのファイル内に,
supportingFiles.put("sample.mustache",".ts")
といった使い方をします.これが一番シンプルなテンプレートの使い方になり,1回のコード生成につき,1度だけ呼ばれます.
実際の使用されている例としては,typescript-nodeがあります.
基本的な使い方は,"ソースコードではない"部分に使われることが多いです.上の例であれば,gitignoreやpackage.jsonを生成するような用途です.typescript-nodeは珍しい例で,ソースコードもsupportingFilesのみでコード生成されています.そのため,ソースコードを読むとっかかりとしては読みやすいものになっています.一方で,1つのコードにまとめたために,mustacheのテンプレートが凶悪になっています.
apiTemplateFiles
使い方は以下です.
apiTemplateFiles.put("api.mustache",".ts")
主にapiのコントローラー部分を作るのに利用します.
しかし,これだけでは,**どのソースコードに,どのエントリポイントがコード生成されるのか?何個ソースコードが生成されるのか?**全く分からないと思います.
そのため,サンプルのswagger.ymlの一部を参考に説明します.
paths:
/pet:
post:
tags:
- "pet"
summary: "Add a new pet to the store"
description: ""
operationId: "addPet"
swaggerでエントリポイントを指定するときにpath
という属性を利用し,urlを宣言すると思います.この時にtags
という属性を付与します.このtags
の属性ごとにソースコードが生成されます.
サンプルのymlでは,
- Pet
- User
- Store
の3つのtagが存在します.そして,生成されるファイル名は,タグ名+第一引数のCamelCaseの.mustache抜き+apiTemplate.putの第2引数
という形になります.したがって,
- PetApi.ts
- UserApi.ts
- StorApi.ts
という形になります.
このあたりの公式実装は,typescript-angularが参考になります.
apiTemplateFiles.putされているファイル:
https://github.com/swagger-api/swagger-codegen/blob/prepare-release-2.4.6/modules/swagger-codegen/src/main/resources/typescript-angular/api.service.mustache
紹介していて申し訳ないのですが,このコードは結構難しいです.そのため,あぁこういう感じでやってるのだな.という雰囲気だけ汲み取る感じにしてください.
modelTemplateFiles
基本的にはapiTemplateFilesと使い方は同じです.
modelTemplateFiles.put("model.mustache",".ts")
ただ処理の対象が違い,definitions
が対象となります.
これもsample.ymlを参考に書くと,
definitions:
Order:
# (略)
Category:
# (略)
Tag:
# (略)
Pet:
# (略)
ApiResponse:
# (略)
という記述があります.ymlの下部にdefinitions
という節を設け,固有の型を定義することがあります.これがmodelsの対象になります.この例だと,
- Order
- Category
- Tag
- Pet
- ApiResponse
という単位でファイルが生成されます.
これもapiTemplateの時と同じで,typescript-angularが参考になります.
ただ結構読むところが多いので,一度出力してみて,自分に必要な変数のみ拾うのがいいと思います.そして,その中で足りないものをドキュメントを参考に追加しておくとよいと思います.
参考になるコード
基本的な使い方は以上になりますが,いろいろな細かい挙動を追いたいときもあります.そのために参考になるソースコードを列挙します.
DefaultGenerator.java
https://github.com/swagger-api/swagger-codegen/blob/prepare-release-2.4.6/modules/swagger-codegen/src/main/java/io/swagger/codegen/DefaultCodegen.java
コード生成のメインになる部分です.基本的にはこの部分の挙動とにらめっこする必要があります.generateSupportingFilesやgenerateApis,generateModelsが先ほどの3つのテンプレートの処理部分になるので,このあたりを詳しく読むことで動作を把握することができます.
CodegenConfig.java
https://github.com/swagger-api/swagger-codegen/blob/prepare-release-2.4.6/modules/swagger-codegen/src/main/java/io/swagger/codegen/CodegenConfig.java
これがCodegenのinterfaceになります.Codegenではこのinterfaceを実装することが目的になります.そのため,ロジックはありません.
DefaultCodegen.java
https://github.com/swagger-api/swagger-codegen/blob/prepare-release-2.4.6/modules/swagger-codegen/src/main/java/io/swagger/codegen/DefaultCodegen.java
DefaultCodegenが基本的なコード生成のロジックになります.
例えば,AbstractTypeScriptClientCodegenの定義を見ると
public abstract class AbstractTypeScriptClientCodegen extends DefaultCodegen implements CodegenConfig
となっており,先ほどのCodegenConfigを実装し,DefaultCodegenを継承しています.このような形で,基本的なDefaultCodegenの機能を利用しながら,CodegenConfigを実装していくのがセオリーのようです.ただDefaultCodegenのコードが割と凶悪で,1クラスで3,900行ぐらいあるので,結構読むのは辛いです.
DefaultGeneratorとDefaultCodegenの使い分けは?
これがだんだん意味が分からなくなってくるので,図解します.
答えから書くとこれだけです.DefaultGeneratorはSwaggerCodegenのコード生成の処理の本体となっています.「まず初めに,apiTemplateのテンプレート情報からコード生成を行い,その次にmodelTemplateを・・・」といったコード生成の全体のフローを定義しています.DefaultCodegenは何をしているかというと,コード生成の設定をしているにすぎません.DefaultGeneratorから,apiのファイル名は何?(apiFilename)とCodeConfigに対し呼び出しをします.本来であれば,CodeConfigに実装された関数が値を返します.そこをDefaultCodegenが継承されていることで,DefaultCodegenのapiFilenameを返しています.その名前の通り,DefaultCodegenはCodeConfigのDefaultの値を返すクラスになっています.
感想
必要にかられてSwaggerCodegenを読んでみましたが,かなり大変でした.私もまだ全体を把握しきれてはいません.
コード生成するプログラム自体が難しい部類のものになるので,かなり厳しいものがあります.やっぱりなんやかんやで手を動かさないと分からない部分が多かったです.
それと自分がこう組みたい!みたいなビジョンがあっても,SwaggerCodegenの仕様上無理なことがあります.しかし,SwaggerCodegenの仕様上無理なことを知るためには,SwaggerCodegenの仕様を知らねばならない.というデッドロックに引っ掛かります.
また,APIのコードの構成をよく知っていないと厳しい面があります.例えば,Java+SpringBootでAPIを組むことを考えると,mustacheのテンプレートにはimport文とかも1つ1つ書く必要があります.しかし,こういうものに対してコード補完が効かなかったりするので,いちいちめんどくさかったりもします.なので,割とコードをメタ的に認知する必要があって,また別の脳みそを使う感じがあったりします.
この辺,SwaggerCodegen使うのも難しかったりもするのですが,この記事がSwaggerCodegenを使ってみたい人の,なんらかの役に立てば幸いです.
あれ?OpenAPIGeneratorじゃないの・・・?しかもSwaggerにしてもバイナリ2系なの?とかいろいろと突っ込みどころはあるんですがね・・・