🆙

Goa 更新情報 v3.9.0

2022/10/01に公開

概要

Goa の更新情報メモです。今回のバージョンアップは細かな修正ですが BREAKING CHANGE を含みます。

Goa v3.9.0

主な変更内容

  • エラーフォーマッターが context を取るようになりました(BREAKING CHANGE#3137
  • Goa のエラー構造体のメソッドに ErrorName() がありますが、こちらは非推奨となり、GoaErrorName() が新しく導入されました #3105
  • OpenAPI ドキュメントに要素を強制的に反映させる Meta(type:generate:force)v3.8.5 で導入されましたが、指定しても出力されないケースがありこれが修正されました

詳細

エラーフォーマッターが context を取るようになった件

これは破壊的変更になります。具体的には HTTP トランスポートの話になります。

エラーフォーマッターが必要になるのは、エラーのうちでも特にサービスメソッドに到達する前の処理で HTTP トランスポートで発生してしまったエラー(バリデーションなど Goa が生成したコード内で生まれるエラー)をカスタマイズするケースです。

たとえば、入力時のバリデーションで Bad Request が発生した場合や、なんらかのエラーが発生して起こる Internal Server Error に対してエラーの形式を制御します。

この機能が導入された議論の詳細は issue を参照ください。

で、これが、これまでは

// NewErrorResponse creates a HTTP response from the given error.
func NewErrorResponse(err error) Statuser {
    if gerr, ok := err.(*goa.ServiceError); ok {
        return &ErrorResponse{
            Name:      gerr.Name,
            ID:        gerr.ID,
            Message:   gerr.Message,
            Timeout:   gerr.Timeout,
            Temporary: gerr.Temporary,
            Fault:     gerr.Fault,
        }
    }
    return NewErrorResponse(goa.Fault(err.Error()))
}

という形式だったのが、この変更で以下のように第一引数に context を取るようになりました。

// NewErrorResponse creates a HTTP response from the given error.
func NewErrorResponse(ctx context.Context, err error) Statuser {
    if gerr, ok := err.(*goa.ServiceError); ok {
        return &ErrorResponse{
            Name:      gerr.Name,
            ID:        gerr.ID,
            Message:   gerr.Message,
            Timeout:   gerr.Timeout,
            Temporary: gerr.Temporary,
            Fault:     gerr.Fault,
        }
    }
    return NewErrorResponse(ctx, goa.Fault(err.Error()))
}

context、そりゃ合った方がいいけど、なくてもいいんじゃないかな・・・という気もしますが Clue の context logger とか使っていたらやはりここに context 欲しいよな、という気もします。

GoaErrorName() が新しく導入された件

Goa のエラー構造体が持つ、ErrorName() が非推奨になった理由は、他のモジュールとかで ErrorName() を実装した ErrorNamer なエラーが存在すると、Goa のエラーかどうかをこれを目印に判定できないから、という理由です。

Goa では ErrorNamer というインターフェースが用意されています(今回非推奨になりましたが継続して利用は可能です)。

type ErrorNamer interface {
  ErrorName() string
}

具体的には次のようなコードが問題になります。

if _, ok := err.(goa.ErrorNamer); !ok {
    report(error)
}

なぜなら、ErrorName() を実装してしまうエラー構造体があるかも知れないからです。そこで今回、名前がバッティングしないように GoaErrorNamer が用意されました。

type GoaErrorNamer interface {
	GoaErrorName() string
}

今後はこちらをご利用してください。ということでした。

破壊的な変更が入りましたが、機能として大きく変わるものはありませんでしたね。

Happy hacking!

Discussion