前回は簡単な Web API の Swagger definition を書いただけで終わってしまったけど、
ここからが本題。
やりたいのは、Swagger definition からクライアントとサーバーのソースコード生成すること。
そのためのツールとして swagger-codegen が提供されている。
http://swagger.io/swagger-codegen/swagger.io
お試しなので、Homebrew や mvn を使わずに直接 jar をダウンロードしてしまおう。
http://repo1.maven.org/maven2/io/swagger/swagger-codegen-cli/2.1.5/swagger-codegen-cli-2.1.5.jar
前回の Swagger definition を sample_api.yml として保存し、
まずは API クライアントを生成してみる。
java -jar swagger-codegen-cli-2.1.5.jar generate \
-i sample_api.yml \
-l csharp \
-o SampleApiClient
を実行すると、RestSharp を使った API クライアントのソースコードが生成された。
一部抜粋すると、こんな感じ。
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using RestSharp;
using IO.Swagger.Client;
using IO.Swagger.Model;
namespace IO.Swagger.Api
{
<summary>
</summary>
public class DefaultApi : IDefaultApi
{
<summary>
<see cref="DefaultApi"/>
</summary>
<returns></returns>
public DefaultApi(String basePath)
{
this.Configuration = new Configuration(new ApiClient(basePath));
}
<summary>
<see cref="DefaultApi"/>
</summary>
<param name="configuration"></param>
<returns></returns>
public DefaultApi(Configuration configuration = null)
{
if (configuration == null)
this.Configuration = Configuration.Default;
else
this.Configuration = configuration;
}
<summary>
</summary>
<value></value>
public String GetBasePath()
{
return this.Configuration.ApiClient.RestClient.BaseUrl.ToString();
}
<summary>
</summary>
<value></value>
[Obsolete("SetBasePath is deprecated, please do 'Configuraiton.ApiClient = new ApiClient(\"http://new-path\")' instead.")]
public void SetBasePath(String basePath)
{
}
<summary>
</summary>
<value></value>
public Configuration Configuration {get; set;}
<summary>
</summary>
<returns></returns>
[Obsolete("DefaultHeader is deprecated, please use Configuration.DefaultHeader instead.")]
public Dictionary<String, String> DefaultHeader()
{
return this.Configuration.DefaultHeader;
}
<summary>
</summary>
<param name="key"></param>
<param name="value"></param>
<returns></returns>
[Obsolete("AddDefaultHeader is deprecated, please use Configuration.AddDefaultHeader instead.")]
public void AddDefaultHeader(string key, string value)
{
this.Configuration.AddDefaultHeader(key, value);
}
<summary>
</summary>
<param name="page"></param>
<returns><></returns>
public List<User> UsersGet (int? page = null)
{
ApiResponse<List<User>> response = UsersGetWithHttpInfo(page);
return response.Data;
}
<summary>
</summary>
<param name="page"></param>
<returns><></returns>
public ApiResponse< List<User> > UsersGetWithHttpInfo (int? page = null)
{
var path_ = "/users";
var pathParams = new Dictionary<String, String>();
var queryParams = new Dictionary<String, String>();
var headerParams = new Dictionary<String, String>(Configuration.DefaultHeader);
var formParams = new Dictionary<String, String>();
var fileParams = new Dictionary<String, FileParameter>();
String postBody = null;
String[] http_header_accepts = new String[] {
"application/json"
};
String http_header_accept = Configuration.ApiClient.SelectHeaderAccept(http_header_accepts);
if (http_header_accept != null)
headerParams.Add("Accept", Configuration.ApiClient.SelectHeaderAccept(http_header_accepts));
pathParams.Add("format", "json");
if (page != null) queryParams.Add("page", Configuration.ApiClient.ParameterToString(page));
IRestResponse response = (IRestResponse) Configuration.ApiClient.CallApi(path_, Method.GET, queryParams, postBody, headerParams, formParams, fileParams, pathParams);
int statusCode = (int) response.StatusCode;
if (statusCode >= 400)
throw new ApiException (statusCode, "Error calling UsersGet: " + response.Content, response.Content);
else if (statusCode == 0)
throw new ApiException (statusCode, "Error calling UsersGet: " + response.ErrorMessage, response.ErrorMessage);
return new ApiResponse<List<User>>(statusCode,
response.Headers.ToDictionary(x => x.Name, x => x.Value.ToString()),
(List<User>) Configuration.ApiClient.Deserialize(response, typeof(List<User>)));
}
}
}
次はサーバー側。
README によると、ASP.NET Core のプロジェクトを生成できるようだ(名前は ASP.NET 5 のままになっているけど)。
java -jar swagger-codegen-cli-2.1.5.jar generate \
-i sample_api.yml \
-l aspnet5 \
-o SampleApiServer
を実行したら、aspnet5 には対応してないっていうエラーになった。
…ASP.NET Core 対応が入ったの 1 ヶ月前だけど、2.1.5 がリリースされたのは 1 月 7 日だった。
含まれてなかったのか。
mvn でビルドするしかないかと思っていたら、
Swagger Editor でも ASP.NET Core のソースコードを生成できるではないですか。
メニューをクリックするとソースコードのアーカイブをダウンロードできた。
一部抜粋すると、こんな感じ。
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc;
using Newtonsoft.Json;
using Swashbuckle.SwaggerGen.Annotations;
using IO.Swagger.Models;
namespace IO.Swagger.Controllers
{
<summary>
</summary>
public class DefaultApiController : Controller
{
<summary>
</summary>
<remarks></remarks>
<param name="page"></param>
<response code="200"></response>
[HttpGet]
[Route("/users")]
[SwaggerOperation("UsersGet")]
[SwaggerResponse(200, type: typeof(List<User>))]
public IActionResult UsersGet([FromQuery]int? page)
{
string exampleJson = null;
var example = exampleJson != null
? JsonConvert.DeserializeObject<List<User>>(exampleJson)
: default(List<User>);
return new ObjectResult(example);
}
<summary>
</summary>
<remarks></remarks>
<param name="name"></param>
<response code="200"></response>
[HttpPost]
[Route("/users")]
[SwaggerOperation("UsersPost")]
[SwaggerResponse(200, type: typeof(User))]
public IActionResult UsersPost([FromForm]string name)
{
string exampleJson = null;
var example = exampleJson != null
? JsonConvert.DeserializeObject<User>(exampleJson)
: default(User);
return new ObjectResult(example);
}
<summary>
</summary>
<remarks></remarks>
<param name="userId"></param>
<response code="200"></response>
[HttpGet]
[Route("/users/{userId}")]
[SwaggerOperation("UsersUserIdGet")]
[SwaggerResponse(200, type: typeof(User))]
public IActionResult UsersUserIdGet([FromRoute]string userId)
{
string exampleJson = null;
var example = exampleJson != null
? JsonConvert.DeserializeObject<User>(exampleJson)
: default(User);
return new ObjectResult(example);
}
<summary>
</summary>
<remarks></remarks>
<param name="userId"></param>
<param name="name"></param>
<response code="200"></response>
[HttpPut]
[Route("/users/{userId}")]
[SwaggerOperation("UsersUserIdPut")]
public void UsersUserIdPut([FromRoute]string userId, [FromForm]string name)
{
throw new NotImplementedException();
}
<summary>
</summary>
<remarks></remarks>
<param name="userId"></param>
<response code="200"></response>
[HttpDelete]
[Route("/users/{userId}")]
[SwaggerOperation("UsersUserIdDelete")]
public void UsersUserIdDelete([FromRoute]string userId)
{
throw new NotImplementedException();
}
}
}
サーバー側は Swashbuckle に依存していた。
アクションの定義だけはあるけど、中身は実装してないに等しい。
せめて partial メソッドとか使っていればいいのに。
デモ版 Swagger Editor で生成できるソースコードに不満が無い場合は、
わざわざ swagger-codegen をインストールしなくてもよさそうだ。
ソースコードを生成するテンプレートをいじりたいときや、生成ロジックをカスタマイズしたいときに、
swagger-codegen を使うことになりそう。
とりあえず ASP.NET Core のテンプレートはカスタマイズが必要だな。