Open3

gRPCでエラーの詳細を返す方法

ryohma0510ryohma0510

gRPCでエラーの詳細を返却して、ユーザーにエラー内容を表示したい場合の方法を考えてみる。
例えばCSVの形式不正をユーザーに表示したい場合。

ryohma0510ryohma0510

https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto

gRPC公式のerror detailsの定義があるが、エラーコードをprotoで定義できないので自分で同じようなものを定義したほうがよさそう

syntax = "proto3";

package greet.v1;

option go_package = "connect-go-example/gen/greet/v1;greetv1";

// エラーコードの定義
enum ErrorDetailCode {
  UNKNOWN = 0;
  INVALID_CSV = 1;
}

// error detailsの定義
message ErrorDetail {
  ErrorDetailCode code = 1;
  string message = 2;
  string display_message = 3;
}

message GreetRequest {
  string name = 1;
}

message GreetResponse {
  string greeting = 1;
}

service GreetService {
  rpc Greet(GreetRequest) returns (GreetResponse) {}
}


package main

import (
	greetv1 "connect-go-example/gen/greet/v1"
	"connect-go-example/gen/greet/v1/greetv1connect"
	"context"
	"errors"
	"github.com/bufbuild/connect-go"
	"net/http"

	"golang.org/x/net/http2"
	"golang.org/x/net/http2/h2c"
)

type GreetServer struct{}

func (s *GreetServer) Greet(
	_ context.Context,
	_ *connect.Request[greetv1.GreetRequest],
) (*connect.Response[greetv1.GreetResponse], error) {
	return nil, newCustomErrorDetails()
}

func main() {
	greeter := &GreetServer{}
	mux := http.NewServeMux()
	path, handler := greetv1connect.NewGreetServiceHandler(greeter)
	mux.Handle(path, handler)
	http.ListenAndServe(
		"localhost:8080",
		h2c.NewHandler(mux, &http2.Server{}),
	)
}

func newCustomErrorDetails() error {
	err := connect.NewError(
		connect.CodeInvalidArgument,
		errors.New("some error"),
	)

	addCSVErrorDetail(err, "invalid csv header", "CSVのヘッダーが不正です。ヘッダーはxxx,yyyにしてください")
	addCSVErrorDetail(err, "duplicate rows at 2 and 3", "2番目と3番目の列が重複しています")

	return err
}

func addCSVErrorDetail(err *connect.Error, msg, displayMsg string) {
	if errDetail, detailErr := connect.NewErrorDetail(&greetv1.ErrorDetail{
		Code:           greetv1.ErrorDetailCode_INVALID_CSV,
		Message:        msg,
		DisplayMessage: displayMsg,
	}); detailErr == nil {
		err.AddDetail(errDetail)
	}
}

curl \                                                                                                                                                                                    
    --header "Content-Type: application/json" \
    --data '{"name": "Jane"}' \
    http://localhost:8080/greet.v1.GreetService/Greet
{
  "code": "invalid_argument",
  "message": "some error",
  "details": [
    {
      "type": "greet.v1.ErrorDetail",
      "value": "CAESEmludmFsaWQgY3N2IGhlYWRlchpPQ1NW44Gu44OY44OD44OA44O844GM5LiN5q2j44Gn44GZ44CC44OY44OD44OA44O844GveHh4LHl5eeOBq+OBl+OBpuOBj+OBoOOBleOBhA",
      "debug": {
        "@type": "type.googleapis.com/greet.v1.ErrorDetail",
        "code": "INVALID_CSV",
        "message": "invalid csv header",
        "displayMessage": "CSVのヘッダーが不正ですyyyにしてください"
      }
    },
    {
      "type": "greet.v1.ErrorDetail",
      "value": "CAESGWR1cGxpY2F0ZSByb3dzIGF0IDIgYW5kIDMaLzLnlarnm67jgagz55Wq55uu44Gu5YiX44GM6YeN6KSH44GX44Gm44GE44G+44GZ",
      "debug": {
        "@type": "type.googlea/greet.v1.ErrorDetail",
        "code": "INVALID_CSV",
        "message": "duplicate rows at 2 and 3",
        "displayMessage": "2番目と3番目の列が重複しています"
      }
    }
  ]
}