エラーをトランスポートステータスコードにマッピング

GoaのエラーをHTTPおよびgRPCステータスコードに適切にマッピングする方法を学び、異なるトランスポートプロトコル間で一貫したエラーレスポンスを確保します。

Goa DSLでエラーを定義した後、次のステップはこれらのエラーを適切な トランスポート固有のステータスコードにマッピングすることです。これにより、 クライアントがエラーの性質に基づいて意味のある標準化されたレスポンスを 受け取ることが保証されます。GoaではDSL内のResponse関数を使用して、 HTTPやgRPCなどの異なるトランスポートプロトコルに対してこれらの マッピングを定義できます。

HTTPトランスポートマッピング

HTTPトランスポートの場合、サービスまたはメソッド定義内でHTTP関数を使用して、 エラーを特定のHTTPステータスコードにマッピングします。このマッピングにより、 エラーが発生した場合、クライアントは正しいステータスコードとエラー情報を 含むHTTPレスポンスを受け取ることが保証されます。

var _ = Service("divider", func() { Error("DivByZero", func() { Description("DivByZeroは、除数がゼロの場合に返されるエラーです。") }) HTTP(func() { // "DivByZero"エラーをHTTP 400 Bad Requestにマッピング Response("DivByZero", StatusBadRequest) }) Method("integral_divide", func() { Error("HasRemainder", func() { Description("HasRemainderは、整数除算に余りがある場合に返されます。") }) HTTP(func() { // "HasRemainder"エラーをHTTP 417 Expectation Failedにマッピング Response("HasRemainder", StatusExpectationFailed) }) // 追加のメソッド定義... }) Method("divide", func() { // メソッド固有の定義... }) })

この例では:

  • DivByZero: HTTPステータスコード400 Bad Requestにマッピング。
  • HasRemainder: HTTPステータスコード417 Expectation Failedにマッピング。

レスポンスの定義

HTTP関数内で、Response関数を使用して各エラーをHTTPステータスコードに 関連付けます。構文は以下の通りです:

Response("<エラー名>", <HTTPステータスコード>, func() { Description("<オプションの説明>") })
  • <エラー名>: DSLで定義されたエラーの名前。
  • <HTTPステータスコード>: エラーをマッピングするHTTPステータスコード。
  • Description: (オプション)ドキュメント用のレスポンスの説明。

完全なHTTPマッピングの例

var _ = Service("divider", func() { // サービスレベルのエラー Error("DivByZero", func() { Description("DivByZeroは、除数がゼロの場合に返されるエラーです。") }) HTTP(func() { // サービス全体のエラーマッピング Response("DivByZero", StatusBadRequest) // 400 }) Method("integral_divide", func() { Description("整数除算を実行し、余りをチェックします") Payload(func() { Field(1, "dividend", Int, "被除数") Field(2, "divisor", Int, "除数") Required("dividend", "divisor") }) Result(Int) Error("HasRemainder", func() { Description("HasRemainderは、整数除算に余りがある場合に返されます。") }) HTTP(func() { POST("/divide/integral") // メソッド固有のエラーマッピング Response("HasRemainder", StatusExpectationFailed, func() { // 417 Description("除算に余りがある場合に返されます") }) }) }) Method("divide", func() { Description("浮動小数点除算を実行します") Payload(func() { Field(1, "dividend", Float64, "被除数") Field(2, "divisor", Float64, "除数") Required("dividend", "divisor") }) Result(Float64) Error("Overflow", func() { Description("Overflowは、結果が最大値を超える場合に返されます。") }) HTTP(func() { POST("/divide") // メソッド固有のエラーマッピング Response("Overflow", StatusUnprocessableEntity, func() { // 422 Description("除算結果が最大値を超える場合に返されます") }) }) }) })

この例は以下を示しています:

  1. サービスレベルのエラー: メソッド間で共通のエラー:

    • DivByZero: ゼロによる除算を試みた場合
  2. メソッド固有のエラー: 各メソッドが独自の特定のエラーを定義:

    • integral_divide: 余りのケースを処理
    • divide: 浮動小数点オーバーフローを処理
  3. HTTPステータスコードマッピング:

    • 400 Bad Request: ゼロによる除算の場合
    • 417 Expectation Failed: 余りのある整数除算の場合
    • 422 Unprocessable Entity: 浮動小数点オーバーフローの場合
  4. 異なるエンドポイント: 2つの異なる除算操作のエラーマッピングを示す:

    • /divide/integral 整数除算用
    • /divide 浮動小数点除算用

これらのマッピングにより、各エラー条件がエラーの性質を正確に反映した 適切なHTTPステータスコードを返すことが保証されます。

gRPCトランスポートマッピング

gRPCトランスポートの場合、サービスまたはメソッド定義内でGRPC関数を使用して、 エラーを特定のgRPCステータスコードにマッピングします。このマッピングにより、 エラーが発生した場合、クライアントは正しいステータスコードとエラー情報を 含むgRPCレスポンスを受け取ることが保証されます。

var _ = Service("divider", func() { Error("DivByZero", func() { Description("DivByZeroは、除数がゼロの場合に返されるエラーです。") }) GRPC(func() { // "DivByZero"エラーをgRPCステータスコードInvalidArgument (3)にマッピング Response("DivByZero", CodeInvalidArgument) }) Method("integral_divide", func() { Error("HasRemainder", func() { Description("HasRemainderは、整数除算に余りがある場合に返されます。") }) GRPC(func() { // "HasRemainder"エラーをgRPCステータスコードUnknown (2)にマッピング Response("HasRemainder", CodeUnknown) }) // 追加のメソッド定義... }) Method("divide", func() { // メソッド固有の定義... }) })

この例では:

  • DivByZero: gRPCステータスコードInvalidArgument(コード3)にマッピング。
  • HasRemainder: gRPCステータスコードUnknown(コード2)にマッピング。

レスポンスの定義

GRPC関数内で、Response関数を使用して各エラーをgRPCステータスコードに 関連付けます。構文は以下の通りです:

Response("<エラー名>", Code<ステータスコード>, func() { Description("<オプションの説明>") })
  • <エラー名>: DSLで定義されたエラーの名前。
  • Code<ステータスコード>: エラーをマッピングするgRPCステータスコード(Codeプレフィックス付き)。
  • Description: (オプション)ドキュメント用のレスポンスの説明。

HTTPとgRPCマッピングの組み合わせ

Goaでは、同じサービスまたはメソッド内でHTTPとgRPCの両方のマッピングを 定義できます。これは、サービスが複数のトランスポートをサポートする場合に 特に有用で、クライアントが使用するトランスポートプロトコルに関係なく、 エラーが適切にマッピングされることを保証します。

var _ = Service("divider", func() { Error("DivByZero", func() { Description("DivByZeroは、ゼロによる除算を試みた場合に返されます。") }) Method("divide", func() { Payload(func() { Field(1, "dividend", Float64, "被除数") Field(2, "divisor", Float64, "除数") Required("dividend", "divisor") }) Result(Float64) HTTP(func() { POST("/divide") // ゼロによる除算をHTTP 422 Unprocessable Entityにマッピング Response("DivByZero", StatusUnprocessableEntity) }) GRPC(func() { // ゼロによる除算をINVALID_ARGUMENTにマッピング Response("DivByZero", CodeInvalidArgument) }) }) })