goa が返すエラーレスポンスの Content-Type を変更する

Web API がエラーレスポンスを返す際にはレスポンスボディでエラーの詳細を示すのが一般的です。 goa には goa.ErrorResponse という型が組み込まれており、 controller に到達する前の処理でエラーが発生した際のレスポンスに使われています。また controller の中で goa.ErrorResponse を使うこともできます。

controller 到達前のエラーレスポンス

例えば /foo というエンドポイントが存在しないのに GET /foo というリクエストが来たときには以下の形のレスポンスが返ります。

{
  "id": "XXXXXXXX",
  "code": "not_found",
  "status" :404,
  "detail": "/foo"
}

この際、レスポンスヘッダの Content-Typeapplication/vnd.goa.error となります。この値は goa.ErrorMediaIdentifier として定義されているため変更することが可能です。例えば Content-Typeapplication/vnd.myservice.error に変える場合は以下のコードを main パッケージに追加します。

package main

import (
    "github.com/goadesign/goa"
)

func init() {
    // Change the media type identifier used for error responses.
    goa.ErrorMediaIdentifier = "application/vnd.myservice.error"
}

controller が返すエラーレスポンス

goa.ErrorResponse には、対応する design 定義として design.ErrorMedia が用意されています。これを design の中で使うことで、あるレスポンスが goa.ErrorResponse のフォーマットであることを明示できます。以下の例では BadRequestErrorMedia を指定することで、不正なリクエストが来たときのエラーレスポンスが goa.ErrorResponse のフォーマットであることを表しています。

Response(BadRequest, ErrorMedia)

controller の実装では ctx.BadRequest() を呼び出すことで Bad Request を返却することができます。その際、引数に *goa.ErrorResponse を渡すとそれがレスポンスボディに設定されます。

ctx.BadRequest(&goa.ErrorResponse{
    Detail: "this is a error message",
})

こちらの Content-Typeapplication/vnd.goa.error ですが、controller 到達前のエラーレスポンスとは違い design.ErrorMedia.Identifier が参照されています。このフィールドには design.ErrorMediaIdentifier が設定されているため、まず design.ErrorMediaIdentifier を変更した上で design.ErrorMedia.Identifier に再設定しておくことで実際の Content-Type を変更することができます。

package design

import (
    "github.com/goadesign/goa/design"
)

func init() {
    // Change the media type identifier used for error responses.
    design.ErrorMediaIdentifier = "application/vnd.myservice.error"
    design.ErrorMedia.Identifier = design.ErrorMediaIdentifier
}

Swagger UI

goa は Swagger 定義を出力できるので Swagger UI と組み合わせて使われることも多いと思います。しかし application/vnd.goa.error のようにサフィックスが付いていない形式だと Try it out ボタンでリクエストを試すときにエラーレスポンスが Unknown response type となり表示されません。

f:id:tchssk:20180922113030p:plain

これを表示させるには、プロジェクトに以下のコードを追加して Content-Typeサフィックス付きの application/vnd.goa.error+json に変更します。

package main

import (
    "github.com/goadesign/goa"
)

func init() {
    // Change the media type identifier used for error responses.
    goa.ErrorMediaIdentifier = goa.ErrorMediaIdentifier + "+json"
}
package design

import (
    "github.com/goadesign/goa/design"
)

func init() {
    // Change the media type identifier used for error responses.
    design.ErrorMediaIdentifier = design.ErrorMediaIdentifier + "+json"
    design.ErrorMedia.Identifier = design.ErrorMediaIdentifier
}

これで Swagger UI でもレスポンスが表示されるようになります。

f:id:tchssk:20180922113527p:plain