197 lines
5.9 KiB
Go
197 lines
5.9 KiB
Go
/*
|
|
Copyright 2017 Google LLC
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package spanner
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"google.golang.org/genproto/googleapis/rpc/errdetails"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
)
|
|
|
|
// Error is the structured error returned by Cloud Spanner client.
|
|
type Error struct {
|
|
// Code is the canonical error code for describing the nature of a
|
|
// particular error.
|
|
//
|
|
// Deprecated: The error code should be extracted from the wrapped error by
|
|
// calling ErrCode(err error). This field will be removed in a future
|
|
// release.
|
|
Code codes.Code
|
|
// err is the wrapped error that caused this Spanner error. The wrapped
|
|
// error can be read with the Unwrap method.
|
|
err error
|
|
// Desc explains more details of the error.
|
|
Desc string
|
|
// additionalInformation optionally contains any additional information
|
|
// about the error.
|
|
additionalInformation string
|
|
}
|
|
|
|
// TransactionOutcomeUnknownError is wrapped in a Spanner error when the error
|
|
// occurred during a transaction, and the outcome of the transaction is
|
|
// unknown as a result of the error. This could be the case if a timeout or
|
|
// canceled error occurs after a Commit request has been sent, but before the
|
|
// client has received a response from the server.
|
|
type TransactionOutcomeUnknownError struct {
|
|
// err is the wrapped error that caused this TransactionOutcomeUnknownError
|
|
// error. The wrapped error can be read with the Unwrap method.
|
|
err error
|
|
}
|
|
|
|
const transactionOutcomeUnknownMsg = "transaction outcome unknown"
|
|
|
|
// Error implements error.Error.
|
|
func (*TransactionOutcomeUnknownError) Error() string { return transactionOutcomeUnknownMsg }
|
|
|
|
// Unwrap returns the wrapped error (if any).
|
|
func (e *TransactionOutcomeUnknownError) Unwrap() error { return e.err }
|
|
|
|
// Error implements error.Error.
|
|
func (e *Error) Error() string {
|
|
if e == nil {
|
|
return fmt.Sprintf("spanner: OK")
|
|
}
|
|
code := ErrCode(e)
|
|
if e.additionalInformation == "" {
|
|
return fmt.Sprintf("spanner: code = %q, desc = %q", code, e.Desc)
|
|
}
|
|
return fmt.Sprintf("spanner: code = %q, desc = %q, additional information = %s", code, e.Desc, e.additionalInformation)
|
|
}
|
|
|
|
// Unwrap returns the wrapped error (if any).
|
|
func (e *Error) Unwrap() error {
|
|
return e.err
|
|
}
|
|
|
|
// GRPCStatus returns the corresponding gRPC Status of this Spanner error.
|
|
// This allows the error to be converted to a gRPC status using
|
|
// `status.Convert(error)`.
|
|
func (e *Error) GRPCStatus() *status.Status {
|
|
err := unwrap(e)
|
|
for {
|
|
// If the base error is nil, return status created from e.Code and e.Desc.
|
|
if err == nil {
|
|
return status.New(e.Code, e.Desc)
|
|
}
|
|
code := status.Code(err)
|
|
if code != codes.Unknown {
|
|
return status.New(code, e.Desc)
|
|
}
|
|
err = unwrap(err)
|
|
}
|
|
}
|
|
|
|
// decorate decorates an existing spanner.Error with more information.
|
|
func (e *Error) decorate(info string) {
|
|
e.Desc = fmt.Sprintf("%v, %v", info, e.Desc)
|
|
}
|
|
|
|
// spannerErrorf generates a *spanner.Error with the given description and a
|
|
// status error with the given error code as its wrapped error.
|
|
func spannerErrorf(code codes.Code, format string, args ...interface{}) error {
|
|
msg := fmt.Sprintf(format, args...)
|
|
wrapped := status.Error(code, msg)
|
|
return &Error{
|
|
Code: code,
|
|
err: wrapped,
|
|
Desc: msg,
|
|
}
|
|
}
|
|
|
|
// toSpannerError converts general Go error to *spanner.Error.
|
|
func toSpannerError(err error) error {
|
|
return toSpannerErrorWithCommitInfo(err, false)
|
|
}
|
|
|
|
// toSpannerErrorWithCommitInfo converts general Go error to *spanner.Error
|
|
// with additional information if the error occurred during a Commit request.
|
|
//
|
|
// If err is already a *spanner.Error, err is returned unmodified.
|
|
func toSpannerErrorWithCommitInfo(err error, errorDuringCommit bool) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
var se *Error
|
|
if errorAs(err, &se) {
|
|
return se
|
|
}
|
|
switch {
|
|
case err == context.DeadlineExceeded || err == context.Canceled:
|
|
desc := err.Error()
|
|
wrapped := status.FromContextError(err).Err()
|
|
if errorDuringCommit {
|
|
desc = fmt.Sprintf("%s, %s", desc, transactionOutcomeUnknownMsg)
|
|
wrapped = &TransactionOutcomeUnknownError{err: wrapped}
|
|
}
|
|
return &Error{status.FromContextError(err).Code(), wrapped, desc, ""}
|
|
case status.Code(err) == codes.Unknown:
|
|
return &Error{codes.Unknown, err, err.Error(), ""}
|
|
default:
|
|
statusErr := status.Convert(err)
|
|
code, desc := statusErr.Code(), statusErr.Message()
|
|
wrapped := err
|
|
if errorDuringCommit && (code == codes.DeadlineExceeded || code == codes.Canceled) {
|
|
desc = fmt.Sprintf("%s, %s", desc, transactionOutcomeUnknownMsg)
|
|
wrapped = &TransactionOutcomeUnknownError{err: wrapped}
|
|
}
|
|
return &Error{code, wrapped, desc, ""}
|
|
}
|
|
}
|
|
|
|
// ErrCode extracts the canonical error code from a Go error.
|
|
func ErrCode(err error) codes.Code {
|
|
s, ok := status.FromError(err)
|
|
if !ok {
|
|
return codes.Unknown
|
|
}
|
|
return s.Code()
|
|
}
|
|
|
|
// ErrDesc extracts the Cloud Spanner error description from a Go error.
|
|
func ErrDesc(err error) string {
|
|
var se *Error
|
|
if !errorAs(err, &se) {
|
|
return err.Error()
|
|
}
|
|
return se.Desc
|
|
}
|
|
|
|
// extractResourceType extracts the resource type from any ResourceInfo detail
|
|
// included in the error.
|
|
func extractResourceType(err error) (string, bool) {
|
|
var s *status.Status
|
|
var se *Error
|
|
if errorAs(err, &se) {
|
|
// Unwrap statusError.
|
|
s = status.Convert(se.Unwrap())
|
|
} else {
|
|
s = status.Convert(err)
|
|
}
|
|
if s == nil {
|
|
return "", false
|
|
}
|
|
for _, detail := range s.Details() {
|
|
if resourceInfo, ok := detail.(*errdetails.ResourceInfo); ok {
|
|
return resourceInfo.ResourceType, true
|
|
}
|
|
}
|
|
return "", false
|
|
}
|