しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

Swagger / OpenAPI 定義から C# クライアントを自動生成するツールの比較

Swagger / OpenAPI の定義からクライアントを作成するツールがいくつかあって、それぞれ特徴がありそうだったので実際に生成して試してみました。試したのは以下の 3 つです。

  • Swagger Codegen
  • OpenAPI Generator
  • AutoRest

使った Swagger 定義は公式の Petstore サンプル*1です。生成したコードは GitHub に上げました。

C# に関してのみ試しているので、他の言語の場合はまた変わってくると思います。何となく C# のクライアントは Java のコードを移植した感がありました。

Swagger Codegen

Swagger が開発しているジェネレータです。対応する言語は多く、サーバー側のコードも生成してくれます。

実行環境を用意するのが面倒だったので、Swagger Editor を使ってクライアントを生成しました。

csharp と csharp-dotnet2 の 2 つ用意されていますが、csharp-dotnet2 は非常に古いフレームワークをターゲットにしているようなので、使う場合は csharp の方を選んだ方が良いです。

生成されたクライアントは以下のような構造になっていました。Swagger で定義されている tag 毎に API を提供するクラスが分かれているので扱いやすそうです。

f:id:shiba-yan:20190610144733p:plain

テストコードも生成されていましたが、特に中身のないものだったので省略します。

使い方も特に説明が要らないぐらいにシンプルです。

static async Task Main(string[] args)
{
    var petApi = new PetApi();

    var pet = await petApi.GetPetByIdAsync(10);

    await petApi.AddPetAsync(new Pet());
}

Bearer Token での認証が必要な場合は Configuration を渡せば良さそうです。

static async Task Main(string[] args)
{
    var petApi = new PetApi(new Configuration { AccessToken = "ACCESS_TOKEN" });

    var pet = await petApi.GetPetByIdAsync(10);

    await petApi.AddPetAsync(new Pet());
}

非同期メソッドも提供されているので、必要な機能は揃っています。内部の実装はちょっとイマイチな感じがしましたが、利用する上ではそこまで問題は無いと思います。

OpenAPI Generator

Swagger Codegen をフォークして OpenAPI spec v3 に対応させたもののようです。フォークまでの経緯はいろいろあったようですが、こちらの方が開発が活発のようです。

軽く調べた感じではオンラインで生成するツールが見当たらなかったので、提供されている Docker Image を使うことにしました。LCOW だと動いてくれなかったので Linux Container に切り替えました。

OpenAPI Generator には csharp-netcore が用意されていたので、それを使って生成してみました。

デフォルトのままだとビルド出来ないプロジェクトファイルが生成されてしまったので、オプションを指定して SDK ベースのプロジェクトファイルを出力するようにした方が良さそうでした。

docker run -v **:/spec --rm openapitools/openapi-generator-cli generate -i /spec/swagger.json \
     -g csharp-netcore --additional-properties=netCoreProjectFile=true,targetFramework=netstandard2.0 \
     -o /spec/out

ジェネレータ毎のオプションは --additional-properties を使って指定します。ついでに .NET Standard 2.0 をターゲットフレームワークにするように指定しました。

他にもオプションはいろいろと用意されています。コレクションクラスを変更するオプションは使った方が良いかもしれません。デフォルトは常に List<T> となっています。

生成されたクライアントの構造は以下の通りです。基本は Swagger Codegen と同じです。多少の違いがありますが、内部実装の違いだけなので公開されている API は同じです。

f:id:shiba-yan:20190610144724p:plain

従って、使い方も Swagger Codegen が生成したクライアントと同じです。

static async Task Main(string[] args)
{
    var petApi = new PetApi();

    var pet = await petApi.GetPetByIdAsync(10);

    await petApi.AddPetAsync(new Pet());
}

Bearer Token も同じように Configuration クラスで指定するだけです。単純に .NET Core / .NET Standard に対応させたバージョンという認識で問題ないでしょう。

AutoRest

AutoRest は Microsoft が開発しているツールで主に Azure SDK の生成に使われていますが、Azure REST API 自体が Swagger で公開されているのもあり、汎用的に使えるように作られています。

OpenAPI spec v3 への対応は GA されておらず、ベータ版として公開中の v3 系を使う必要があります。

インストールする場合は beta を明示的に指定すれば OK です。

npm install -g autorest@beta

Swagger 定義から C# のクライアントを生成する場合には --csharp を指定します。

autorest --input-file=swagger.json --csharp

これで Generated というディレクトリにクライアントが作られます。

生成されるのは C# のコードだけなので、ビルドするにはプロジェクトファイルを別途用意する必要があります。.NET Standard 2.0 にしたい場合は、以下のような csproj を作成すれば良いです。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Rest.ClientRuntime" Version="2.3.20" />
  </ItemGroup>

</Project>

生成されたクライアントの構造は以下の通りです。REST API を実行する部分は別ライブラリになっているので、他のジェネレータと比べてコード量は少ないです。

f:id:shiba-yan:20190610145907p:plain

使い方は以下のようになります。クラス名はオプションでオーバーライド可能ですが、デフォルトだと $.into.title の名前が使われるようになっています。

static async Task Main(string[] args)
{
    var client = new SwaggerPetstore();

    var pet = await client.GetPetByIdAsync(10);

    await client.AddPetAsync(new Pet());
}

AutoRest は tag 毎に API クライアントを作成してくれないのが少し残念です。

Bearer Token などでの認証が必要な場合は、生成時に add-credentials オプションを追加します。

autorest --input-file=swagger.json --csharp --add-credentials

これで TokenCredentials などを受け取ってくれるようになります。この辺りは Azure SDK と同じです。

static async Task Main(string[] args)
{
    var client = new SwaggerPetstore(new TokenCredentials("ACCESS_TOKEN"));

    var pet = await client.GetPetByIdAsync(10);

    await client.AddPetAsync(new Pet());
}

AutoRest は tag を見てくれないですが、代わりに operationId をベースに作るようになっているので、少し定義を工夫すると Swagger Codegen などのように API 毎に分離することが出来ます。

実際に分離して生成すると以下のような構造となります。とても Azure SDK っぽいですね。

f:id:shiba-yan:20190610150355p:plain

API 毎に分離されても、使い方は他のジェネレータとは違います。大元のクライアントに、プロパティで API クライアントが生えるような構造になります。

static async Task Main(string[] args)
{
    var client = new SwaggerPetstore();

    var pet = await client.Pet.GetAsync(10);

    await client.Pet.AddAsync(new Pet());
}

AutoRest を使うと、良くも悪くも Azure SDK と同じになります。この辺りは好みの問題になりそうです。

ちなみに AutoRest は Swagger 定義の DOM に対して任意の変換を行う機能も持っているので、それを利用するとかなりのカスタマイズが出来ますが結構書くのが難しいです。