github.com/cloudwego/kitex@v0.9.0/pkg/remote/trans/nphttp2/status/status.go (about) 1 /* 2 * 3 * Copyright 2020 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 * This file may have been modified by CloudWeGo authors. All CloudWeGo 18 * Modifications are Copyright 2021 CloudWeGo Authors. 19 */ 20 21 // Package status implements errors returned by gRPC. These errors are 22 // serialized and transmitted on the wire between server and client, and allow 23 // for additional data to be transmitted via the Details field in the status 24 // proto. gRPC service handlers should return an error created by this 25 // package, and gRPC clients should expect a corresponding error to be 26 // returned from the RPC call. 27 // 28 // This package upholds the invariants that a non-nil error may not 29 // contain an OK code, and an OK code must result in a nil error. 30 package status 31 32 import ( 33 "context" 34 "errors" 35 "fmt" 36 37 spb "google.golang.org/genproto/googleapis/rpc/status" 38 "google.golang.org/protobuf/proto" 39 "google.golang.org/protobuf/types/known/anypb" 40 41 "github.com/cloudwego/kitex/pkg/remote/trans/nphttp2/codes" 42 ) 43 44 type Iface interface { 45 GRPCStatus() *Status 46 } 47 48 // Status represents an RPC status code, message, and details. It is immutable 49 // and should be created with New, Newf, or FromProto. 50 type Status struct { 51 s *spb.Status 52 } 53 54 // New returns a Status representing c and msg. 55 func New(c codes.Code, msg string) *Status { 56 return &Status{s: &spb.Status{Code: int32(c), Message: msg}} 57 } 58 59 // Newf returns New(c, fmt.Sprintf(format, a...)). 60 func Newf(c codes.Code, format string, a ...interface{}) *Status { 61 return New(c, fmt.Sprintf(format, a...)) 62 } 63 64 // ErrorProto returns an error representing s. If s.Code is OK, returns nil. 65 func ErrorProto(s *spb.Status) error { 66 return FromProto(s).Err() 67 } 68 69 // FromProto returns a Status representing s. 70 func FromProto(s *spb.Status) *Status { 71 return &Status{s: proto.Clone(s).(*spb.Status)} 72 } 73 74 // Err returns an error representing c and msg. If c is OK, returns nil. 75 func Err(c codes.Code, msg string) error { 76 return New(c, msg).Err() 77 } 78 79 // Errorf returns Error(c, fmt.Sprintf(format, a...)). 80 func Errorf(c codes.Code, format string, a ...interface{}) error { 81 return Err(c, fmt.Sprintf(format, a...)) 82 } 83 84 // Code returns the status code contained in s. 85 func (s *Status) Code() codes.Code { 86 if s == nil || s.s == nil { 87 return codes.OK 88 } 89 return codes.Code(s.s.Code) 90 } 91 92 // Message returns the message contained in s. 93 func (s *Status) Message() string { 94 if s == nil || s.s == nil { 95 return "" 96 } 97 return s.s.Message 98 } 99 100 // AppendMessage append extra msg for Status 101 func (s *Status) AppendMessage(extraMsg string) *Status { 102 if s == nil || s.s == nil || extraMsg == "" { 103 return s 104 } 105 s.s.Message = fmt.Sprintf("%s %s", s.s.Message, extraMsg) 106 return s 107 } 108 109 // Proto returns s's status as an spb.Status proto message. 110 func (s *Status) Proto() *spb.Status { 111 if s == nil { 112 return nil 113 } 114 return proto.Clone(s.s).(*spb.Status) 115 } 116 117 // Err returns an immutable error representing s; returns nil if s.Code() is OK. 118 func (s *Status) Err() error { 119 if s.Code() == codes.OK { 120 return nil 121 } 122 return &Error{e: s.Proto()} 123 } 124 125 // WithDetails returns a new status with the provided details messages appended to the status. 126 // If any errors are encountered, it returns nil and the first error encountered. 127 func (s *Status) WithDetails(details ...proto.Message) (*Status, error) { 128 if s.Code() == codes.OK { 129 return nil, errors.New("no error details for status with code OK") 130 } 131 // s.Code() != OK implies that s.Proto() != nil. 132 p := s.Proto() 133 for _, detail := range details { 134 any, err := anypb.New(detail) 135 if err != nil { 136 return nil, err 137 } 138 p.Details = append(p.Details, any) 139 } 140 return &Status{s: p}, nil 141 } 142 143 // Details returns a slice of details messages attached to the status. 144 // If a detail cannot be decoded, the error is returned in place of the detail. 145 func (s *Status) Details() []interface{} { 146 if s == nil || s.s == nil { 147 return nil 148 } 149 details := make([]interface{}, 0, len(s.s.Details)) 150 for _, any := range s.s.Details { 151 detail, err := any.UnmarshalNew() 152 if err != nil { 153 details = append(details, err) 154 continue 155 } 156 details = append(details, detail) 157 } 158 return details 159 } 160 161 // Error wraps a pointer of a status proto. It implements error and Status, 162 // and a nil *Error should never be returned by this package. 163 type Error struct { 164 e *spb.Status 165 } 166 167 func (e *Error) Error() string { 168 return fmt.Sprintf("rpc error: code = %d desc = %s", codes.Code(e.e.GetCode()), e.e.GetMessage()) 169 } 170 171 // GRPCStatus returns the Status represented by se. 172 func (e *Error) GRPCStatus() *Status { 173 return FromProto(e.e) 174 } 175 176 // Is implements future error.Is functionality. 177 // A Error is equivalent if the code and message are identical. 178 func (e *Error) Is(target error) bool { 179 tse, ok := target.(*Error) 180 if !ok { 181 return false 182 } 183 return proto.Equal(e.e, tse.e) 184 } 185 186 // FromError returns a Status representing err if it was produced from this 187 // package or has a method `GRPCStatus() *Status`. Otherwise, ok is false and a 188 // Status is returned with codes.Unknown and the original error message. 189 func FromError(err error) (s *Status, ok bool) { 190 if err == nil { 191 return nil, true 192 } 193 var se Iface 194 if errors.As(err, &se) { 195 return se.GRPCStatus(), true 196 } 197 return New(codes.Unknown, err.Error()), false 198 } 199 200 // Convert is a convenience function which removes the need to handle the 201 // boolean return value from FromError. 202 func Convert(err error) *Status { 203 s, _ := FromError(err) 204 return s 205 } 206 207 // Code returns the Code of the error if it is a Status error, codes.OK if err 208 // is nil, or codes.Unknown otherwise. 209 func Code(err error) codes.Code { 210 // Don't use FromError to avoid allocation of OK status. 211 if err == nil { 212 return codes.OK 213 } 214 var se Iface 215 if errors.As(err, &se) { 216 return se.GRPCStatus().Code() 217 } 218 return codes.Unknown 219 } 220 221 // FromContextError converts a context error into a Status. It returns a 222 // Status with codes.OK if err is nil, or a Status with codes.Unknown if err is 223 // non-nil and not a context error. 224 func FromContextError(err error) *Status { 225 switch err { 226 case nil: 227 return nil 228 case context.DeadlineExceeded: 229 return New(codes.DeadlineExceeded, err.Error()) 230 case context.Canceled: 231 return New(codes.Canceled, err.Error()) 232 default: 233 return New(codes.Unknown, err.Error()) 234 } 235 }