Swagger YAMLのバリデーションをCIで回す
Swagger YAMLを書いていると間違いに気づかないことがよくあります。リポジトリにpushした時にJenkinsやCircle CIなどで構文チェックできるとミスを防げるます。そこで、GradleのプラグインにSwagger YAMLのバリデーションを追加してみました。
使い方
build.gradleに下記を書きます。
plugins { id 'org.hidetake.swagger.generator' version '1.6.0' } validateSwagger { inputFile = file('petstore.yaml') }
下記のようにvalidateSwagger
タスクを実行すると、YAMLのバリデーションが実行されます。
% ./gradlew validateSwagger :validateSwagger
YAMLに間違いがあると、下記のようなエラーを表示してくれます。
:validateSwagger FAILED FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':validateSwagger'. > Invalid Swagger YAML: /Users/hidetake/repo/gradle-swagger-generator-plugin/acceptance-test/validator/petstore.yaml --- - level: "error" schema: loadingURI: "http://swagger.io/v2/schema.json#" pointer: "/definitions/parametersList/items" instance: pointer: "/paths/~1pets~1{petId}/get/parameters/0" domain: "validation" keyword: "oneOf" message: "instance failed to match exactly one schema (matched 0 out of 2)" matched: 0 nrSchemas: 2 (以下略)
仕組み
Swaggerで公式に用意されているJSONスキーマでYAMLを検証しています。
https://github.com/OAI/OpenAPI-Specification/blob/master/schemas/v2.0/schema.json
Gradleプラグインについて
Gradle Swagger Generator Pluginはバリデーションだけでなくコード生成やドキュメント生成の機能も提供しています。詳しい使い方はREADMEを参照してください。
Spring Bootで例外発生時にJSONを返す
Spring BootのREST APIサーバで例外発生時のエラー情報をJSONで返す方法を調べたのでメモです。
やりたいこと
- 例外が発生した場合は常にエラー情報をJSONで返したい。
- 例外の種類によってステータスコードを分けたい。例えば、バリデーションエラーが発生した場合は400、その他は500を返す。
- Spring MVCの本来の振る舞いは変更しない。例えば、認可エラーで401を返す振る舞いは維持する。
実現方法
ErrorController
インタフェースを実装すると、例外時のレスポンスをカスタマイズできる。- キャッチした例外によってステータスコードを変更する。
ステータスコードを指定できる例外クラスを作成します。実際のプロダクトでは、エラーコードやエラーメッセージを含む業務例外クラスを作成するとよいでしょう。
class AppException extends RuntimeException { final int status def AppException(int status, String message) { super(message) this.status = status } def AppException(int status, String message, Throwable cause) { super(message, cause) this.status = status } }
ErrorController
インタフェースの実装クラスを作成します。 ErrorAttributes#getErrorAttributes()
メソッドでエラー情報のMapを取得できます。
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Value import org.springframework.boot.autoconfigure.web.ErrorAttributes import org.springframework.boot.autoconfigure.web.ErrorController import org.springframework.http.HttpStatus import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController import org.springframework.web.context.request.ServletRequestAttributes import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletResponse @RestController @RequestMapping(produces = 'application/json') class AppErrorController implements ErrorController { @Autowired ErrorAttributes errorAttributes @Value('${debug:false}') boolean debug @RequestMapping('/error') Map<String, Object> error(HttpServletRequest request, HttpServletResponse response) { // エラー情報を取得する def servletRequestAttributes = new ServletRequestAttributes(request) def attributes = errorAttributes.getErrorAttributes(servletRequestAttributes, debug) def cause = errorAttributes.getError(servletRequestAttributes) if (cause instanceof AppException) { // 例外に含まれるステータスコードとメッセージを返す response.status = cause.status attributes.status = cause.status attributes.error = HttpStatus.valueOf(cause.status).reasonPhrase attributes.message = cause.message } attributes } @Override String getErrorPath() { '/error' } }
例えば、ステータスコード400を返す場合は下記のような例外を投げます。
@GetMapping('/hello') void hello() { throw new AppException(400, 'Custom Validation Error') }
レスポンスの例
ステータスコードを指定した例外(上記のAppException
)を投げた場合は下記になります。この場合はログにスタックトレースが出力されます。
{ "timestamp":1480254775918, "status":400, "error":"Bad Request", "exception":"example.server.AppException", "message":"Intentionally 400 raised", "trace":"example.server.AppException: ...(debugが有効の場合のみスタックトレースが付く)", "path":"/hello" }
401の場合:
{ "timestamp":1480254600193, "status":401, "error":"Unauthorized", "message":"Bad credentials", "path":"/hello" }
404の場合:
{ "timestamp":1480255037435, "status":404, "error":"Not Found", "message":"No message available", "path":"/hello" }
REST APIクライアントの考慮
以下のように例外処理を設計するとよいでしょう。
- ステータス200でJSONが返ってきた場合、正常
- ステータス200だがJSONを解釈できない場合、共通エラー
- ステータス200以外でJSONが返ってきた場合、エラーコードに応じた例外処理
- ステータス200以外でJSONを解釈できない場合、共通エラー
- 接続失敗の場合、共通エラー
参考文献です。
はじめてのSpring Boot―スプリング・フレームワークで簡単Javaアプリ開発 (I・O BOOKS)
- 作者: 槙俊明
- 出版社/メーカー: 工学社
- 発売日: 2016/09
- メディア: 単行本
- この商品を含むブログ (1件) を見る
Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発
- 作者: 株式会社NTTデータ
- 出版社/メーカー: 翔泳社
- 発売日: 2016/07/21
- メディア: 大型本
- この商品を含むブログ (1件) を見る
Swagger Codegenにおけるspring-cloudテンプレートのカスタマイズ
Swagger Codegenではコード生成のテンプレートをカスタマイズできますが、ライブラリに spring-cloud
を選んだ場合はカスタマイズの仕方に注意が必要です。
springのテンプレート構造は以下になります。
- template/
spring-cloud直下のapiClient.mustacheを変更しても、なぜかコードに反映されません。そこで、libraries直下にapiClient.mustacheをコピーして変更すると、コードに反映されるようになりました。
- template/
罠にはまったので参考になれば幸いです。