github.com/cloudwego/kitex@v0.9.0/pkg/kerrors/bizerrors.go (about)

     1  /*
     2   * Copyright 2022 CloudWeGo Authors
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package kerrors
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  
    23  	"github.com/cloudwego/kitex/pkg/remote/trans/nphttp2/codes"
    24  	"github.com/cloudwego/kitex/pkg/remote/trans/nphttp2/status"
    25  )
    26  
    27  /**
    28   * BizStatusErrorIface is an interface used to define biz errors. Kitex provides the
    29   * default implementation of this interface, and you can also implement it by yourself.
    30   * You can create a biz error through NewBizStatusError or NewBizStatusErrorWithExtra
    31   * and return it in the server handler. You can also convert the err back to
    32   * BizStatusErrorIface through FromBizStatusError on the client side to obtain information
    33   * such as error status codes.
    34   *
    35   * Here is the code example:
    36   *
    37   * Server:
    38   *	func (h *Handler) Serve(ctx, Request) (Response, error) {
    39   *		return nil, kerrors.NewBizStatusError(404, "not found")
    40   *	}
    41   *	// ...
    42   *	svr := myservice.NewServer(&Handler{}, server.WithMetaHandler(transmeta.ServerTTHeaderHandler))
    43   *
    44   * Client:
    45   *	cli := myservice.MustNewClient("client", client.WithTransportProtocol(transport.TTHeader),
    46   * 		client.WithMetaHandler(transmeta.ClientTTHeaderHandler))
    47   *	resp, err := cli.Serve(ctx, req)
    48   *	if err != nil {
    49   *		if bizErr, ok := kerrors.FromBizStatusError(err); ok {
    50   *			println(bizErr.BizStatusCode())
    51   *			println(bizErr.BizMessage())
    52   *			// ....
    53   *		}
    54   *	}
    55   *
    56   * NOTICE: ClientTTHeaderHandler and ServerTTHeaderHandler must be configured in
    57   * the MetaHandler of the corresponding client/server if you're using ttheader
    58   * as transport protocol, otherwise biz error cannot be correctly transmitted to
    59   * the client.
    60   * For custom transport protocol, you need to read and write rpcinfo.TransInfo() in
    61   * your own MetaHandler, and convert it with rpcinfo.Invocation().BizStatusErr().
    62   * For more details, please refer to the implementation of ClientTTHeaderHandler and
    63   * ServerTTHeaderHandler.
    64   */
    65  
    66  type BizStatusErrorIface interface {
    67  	BizStatusCode() int32
    68  	BizMessage() string
    69  	BizExtra() map[string]string
    70  	Error() string
    71  }
    72  
    73  /**
    74   * GRPCStatusIface must be implemented by the BizStatusErrorIface implementation also
    75   * if you need to transmit GRPCStatus.Details in your rpc response.
    76   * Here is the code example:
    77   *
    78   * Server:
    79   *	func (h *Handler) Serve(ctx, Request) (Response, error) {
    80   *		bizErr := kerrors.NewGRPCBizStatusError(404, "not found")
    81   *		grpcStatusErr := bizErr.(kerrors.GRPCStatusIface)
    82   *		st, _ := grpcStatusErr.GRPCStatus().WithDetails(&echo.Echo{Str: "hello world"})
    83   *		grpcStatusErr.SetGRPCStatus(st)
    84   *		return nil, bizErr
    85   *	}
    86   *	// ...
    87   *	svr := myservice.NewServer(&Handler{})
    88   *
    89   * Client:
    90   *	// Replace these two functions to let Kitex pass Details successfully.
    91   *	cli := myservice.MustNewClient("client", client.WithTransportProtocol(transport.GRPC))
    92   *	resp, err := cli.Serve(ctx, req)
    93   *	if err != nil {
    94   *		if bizErr, ok := kerrors.FromBizStatusError(err); ok {
    95   *  		println(bizErr.BizStatusCode())
    96   *			println(bizErr.BizMessage())
    97   *			println(bizErr.(status.Iface).GRPCStatus().Details()[0].(*echo.Echo).Str)
    98   *			// ...
    99   *		}
   100   *	}
   101   */
   102  
   103  type GRPCStatusIface interface {
   104  	GRPCStatus() *status.Status
   105  	SetGRPCStatus(status *status.Status)
   106  }
   107  
   108  type BizStatusError struct {
   109  	code  int32
   110  	msg   string
   111  	extra map[string]string
   112  }
   113  
   114  // FromBizStatusError converts err to BizStatusErrorIface.
   115  func FromBizStatusError(err error) (bizErr BizStatusErrorIface, ok bool) {
   116  	if err == nil {
   117  		return
   118  	}
   119  	ok = errors.As(err, &bizErr)
   120  	return
   121  }
   122  
   123  // NewBizStatusError returns BizStatusErrorIface by passing in code and msg.
   124  func NewBizStatusError(code int32, msg string) BizStatusErrorIface {
   125  	return &BizStatusError{code: code, msg: msg}
   126  }
   127  
   128  // NewBizStatusErrorWithExtra returns BizStatusErrorIface which contains extra info.
   129  func NewBizStatusErrorWithExtra(code int32, msg string, extra map[string]string) BizStatusErrorIface {
   130  	return &BizStatusError{code: code, msg: msg, extra: extra}
   131  }
   132  
   133  func (e *BizStatusError) BizStatusCode() int32 {
   134  	return e.code
   135  }
   136  
   137  func (e *BizStatusError) BizMessage() string {
   138  	return e.msg
   139  }
   140  
   141  func (e *BizStatusError) AppendBizMessage(extraMsg string) {
   142  	if extraMsg != "" {
   143  		e.msg = fmt.Sprintf("%s %s", e.msg, extraMsg)
   144  	}
   145  }
   146  
   147  func (e *BizStatusError) BizExtra() map[string]string {
   148  	return e.extra
   149  }
   150  
   151  func (e *BizStatusError) SetBizExtra(key, value string) {
   152  	if e.extra == nil {
   153  		e.extra = make(map[string]string)
   154  	}
   155  	e.extra[key] = value
   156  }
   157  
   158  func (e *BizStatusError) Error() string {
   159  	return fmt.Sprintf("biz error: code=%d, msg=%s", e.code, e.msg)
   160  }
   161  
   162  // NewGRPCBizStatusError returns *GRPCBizStatusError which implements both
   163  // BizStatusErrorIface and GRPCStatusIface, use this function instead of NewBizStatusError
   164  // to pass status.Details in gRPC scenario.
   165  func NewGRPCBizStatusError(code int32, msg string) BizStatusErrorIface {
   166  	return &GRPCBizStatusError{BizStatusError: BizStatusError{code: code, msg: msg}}
   167  }
   168  
   169  // NewGRPCBizStatusErrorWithExtra returns *GRPCBizStatusError which implements both
   170  // BizStatusErrorIface and GRPCStatusIface, use this function instead of NewBizStatusErrorWithExtra
   171  // to pass status.Details in gRPC scenario.
   172  func NewGRPCBizStatusErrorWithExtra(code int32, msg string, extra map[string]string) BizStatusErrorIface {
   173  	return &GRPCBizStatusError{BizStatusError: BizStatusError{code: code, msg: msg, extra: extra}}
   174  }
   175  
   176  type GRPCBizStatusError struct {
   177  	BizStatusError
   178  
   179  	// for grpc
   180  	status *status.Status
   181  }
   182  
   183  func (e *GRPCBizStatusError) GRPCStatus() *status.Status {
   184  	if e.status == nil {
   185  		e.status = status.New(codes.Internal, e.msg)
   186  	}
   187  	return e.status
   188  }
   189  
   190  func (e *GRPCBizStatusError) SetGRPCStatus(status *status.Status) {
   191  	e.status = status
   192  }