1/* 2Copyright 2017 Google LLC 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package spanner 18 19import ( 20 "context" 21 "fmt" 22 23 "google.golang.org/genproto/googleapis/rpc/errdetails" 24 "google.golang.org/grpc/codes" 25 "google.golang.org/grpc/status" 26) 27 28// Error is the structured error returned by Cloud Spanner client. 29type Error struct { 30 // Code is the canonical error code for describing the nature of a 31 // particular error. 32 // 33 // Deprecated: The error code should be extracted from the wrapped error by 34 // calling ErrCode(err error). This field will be removed in a future 35 // release. 36 Code codes.Code 37 // err is the wrapped error that caused this Spanner error. The wrapped 38 // error can be read with the Unwrap method. 39 err error 40 // Desc explains more details of the error. 41 Desc string 42 // additionalInformation optionally contains any additional information 43 // about the error. 44 additionalInformation string 45} 46 47// TransactionOutcomeUnknownError is wrapped in a Spanner error when the error 48// occurred during a transaction, and the outcome of the transaction is 49// unknown as a result of the error. This could be the case if a timeout or 50// canceled error occurs after a Commit request has been sent, but before the 51// client has received a response from the server. 52type TransactionOutcomeUnknownError struct { 53 // err is the wrapped error that caused this TransactionOutcomeUnknownError 54 // error. The wrapped error can be read with the Unwrap method. 55 err error 56} 57 58const transactionOutcomeUnknownMsg = "transaction outcome unknown" 59 60// Error implements error.Error. 61func (*TransactionOutcomeUnknownError) Error() string { return transactionOutcomeUnknownMsg } 62 63// Unwrap returns the wrapped error (if any). 64func (e *TransactionOutcomeUnknownError) Unwrap() error { return e.err } 65 66// Error implements error.Error. 67func (e *Error) Error() string { 68 if e == nil { 69 return fmt.Sprintf("spanner: OK") 70 } 71 code := ErrCode(e) 72 if e.additionalInformation == "" { 73 return fmt.Sprintf("spanner: code = %q, desc = %q", code, e.Desc) 74 } 75 return fmt.Sprintf("spanner: code = %q, desc = %q, additional information = %s", code, e.Desc, e.additionalInformation) 76} 77 78// Unwrap returns the wrapped error (if any). 79func (e *Error) Unwrap() error { 80 return e.err 81} 82 83// GRPCStatus returns the corresponding gRPC Status of this Spanner error. 84// This allows the error to be converted to a gRPC status using 85// `status.Convert(error)`. 86func (e *Error) GRPCStatus() *status.Status { 87 err := unwrap(e) 88 for { 89 // If the base error is nil, return status created from e.Code and e.Desc. 90 if err == nil { 91 return status.New(e.Code, e.Desc) 92 } 93 code := status.Code(err) 94 if code != codes.Unknown { 95 return status.New(code, e.Desc) 96 } 97 err = unwrap(err) 98 } 99} 100 101// decorate decorates an existing spanner.Error with more information. 102func (e *Error) decorate(info string) { 103 e.Desc = fmt.Sprintf("%v, %v", info, e.Desc) 104} 105 106// spannerErrorf generates a *spanner.Error with the given description and a 107// status error with the given error code as its wrapped error. 108func spannerErrorf(code codes.Code, format string, args ...interface{}) error { 109 msg := fmt.Sprintf(format, args...) 110 wrapped := status.Error(code, msg) 111 return &Error{ 112 Code: code, 113 err: wrapped, 114 Desc: msg, 115 } 116} 117 118// toSpannerError converts general Go error to *spanner.Error. 119func toSpannerError(err error) error { 120 return toSpannerErrorWithCommitInfo(err, false) 121} 122 123// toSpannerErrorWithCommitInfo converts general Go error to *spanner.Error 124// with additional information if the error occurred during a Commit request. 125// 126// If err is already a *spanner.Error, err is returned unmodified. 127func toSpannerErrorWithCommitInfo(err error, errorDuringCommit bool) error { 128 if err == nil { 129 return nil 130 } 131 var se *Error 132 if errorAs(err, &se) { 133 return se 134 } 135 switch { 136 case err == context.DeadlineExceeded || err == context.Canceled: 137 desc := err.Error() 138 wrapped := status.FromContextError(err).Err() 139 if errorDuringCommit { 140 desc = fmt.Sprintf("%s, %s", desc, transactionOutcomeUnknownMsg) 141 wrapped = &TransactionOutcomeUnknownError{err: wrapped} 142 } 143 return &Error{status.FromContextError(err).Code(), wrapped, desc, ""} 144 case status.Code(err) == codes.Unknown: 145 return &Error{codes.Unknown, err, err.Error(), ""} 146 default: 147 statusErr := status.Convert(err) 148 code, desc := statusErr.Code(), statusErr.Message() 149 wrapped := err 150 if errorDuringCommit && (code == codes.DeadlineExceeded || code == codes.Canceled) { 151 desc = fmt.Sprintf("%s, %s", desc, transactionOutcomeUnknownMsg) 152 wrapped = &TransactionOutcomeUnknownError{err: wrapped} 153 } 154 return &Error{code, wrapped, desc, ""} 155 } 156} 157 158// ErrCode extracts the canonical error code from a Go error. 159func ErrCode(err error) codes.Code { 160 s, ok := status.FromError(err) 161 if !ok { 162 return codes.Unknown 163 } 164 return s.Code() 165} 166 167// ErrDesc extracts the Cloud Spanner error description from a Go error. 168func ErrDesc(err error) string { 169 var se *Error 170 if !errorAs(err, &se) { 171 return err.Error() 172 } 173 return se.Desc 174} 175 176// extractResourceType extracts the resource type from any ResourceInfo detail 177// included in the error. 178func extractResourceType(err error) (string, bool) { 179 var s *status.Status 180 var se *Error 181 if errorAs(err, &se) { 182 // Unwrap statusError. 183 s = status.Convert(se.Unwrap()) 184 } else { 185 s = status.Convert(err) 186 } 187 if s == nil { 188 return "", false 189 } 190 for _, detail := range s.Details() { 191 if resourceInfo, ok := detail.(*errdetails.ResourceInfo); ok { 192 return resourceInfo.ResourceType, true 193 } 194 } 195 return "", false 196} 197