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 }