github.com/ydb-platform/ydb-go-sdk/v3@v3.89.2/internal/xerrors/operation.go (about)

     1  package xerrors
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strconv"
     7  
     8  	"github.com/ydb-platform/ydb-go-genproto/protos/Ydb"
     9  	"github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Issue"
    10  
    11  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/backoff"
    12  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/operation"
    13  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xstring"
    14  )
    15  
    16  // operationError reports about operation fail.
    17  type operationError struct {
    18  	code    Ydb.StatusIds_StatusCode
    19  	nodeID  uint32
    20  	issues  issues
    21  	address string
    22  	traceID string
    23  }
    24  
    25  func (e *operationError) isYdbError() {}
    26  
    27  func (e *operationError) Code() int32 {
    28  	return int32(e.code)
    29  }
    30  
    31  func (e *operationError) Name() string {
    32  	return "operation/" + e.code.String()
    33  }
    34  
    35  type issuesOption []*Ydb_Issue.IssueMessage
    36  
    37  func (issues issuesOption) applyToOperationError(oe *operationError) {
    38  	oe.issues = []*Ydb_Issue.IssueMessage(issues)
    39  }
    40  
    41  // WithIssues is an option for construct operation error with issues list
    42  // WithIssues must use as `Operation(WithIssues(issues))`
    43  func WithIssues(issues []*Ydb_Issue.IssueMessage) issuesOption {
    44  	return issues
    45  }
    46  
    47  type statusCodeOption Ydb.StatusIds_StatusCode
    48  
    49  func (code statusCodeOption) applyToOperationError(oe *operationError) {
    50  	oe.code = Ydb.StatusIds_StatusCode(code)
    51  }
    52  
    53  // WithStatusCode is an option for construct operation error with reason code
    54  // WithStatusCode must use as `Operation(WithStatusCode(reason))`
    55  func WithStatusCode(code Ydb.StatusIds_StatusCode) statusCodeOption {
    56  	return statusCodeOption(code)
    57  }
    58  
    59  func (address addressOption) applyToOperationError(oe *operationError) {
    60  	oe.address = string(address)
    61  }
    62  
    63  func (nodeID nodeIDOption) applyToOperationError(oe *operationError) {
    64  	oe.nodeID = uint32(nodeID)
    65  }
    66  
    67  type traceIDOption string
    68  
    69  func (traceID traceIDOption) applyToTransportError(te *transportError) {
    70  	te.traceID = string(traceID)
    71  }
    72  
    73  func (traceID traceIDOption) applyToOperationError(oe *operationError) {
    74  	oe.traceID = string(traceID)
    75  }
    76  
    77  // WithTraceID is an option for construct operation error with traceID
    78  func WithTraceID(traceID string) traceIDOption {
    79  	return traceIDOption(traceID)
    80  }
    81  
    82  type operationOption = operationError
    83  
    84  func (e *operationOption) applyToOperationError(oe *operationError) {
    85  	oe.code = e.code
    86  	oe.issues = e.issues
    87  }
    88  
    89  // FromOperation is an option for construct operation error from operation.Status
    90  // FromOperation must use as `Operation(FromOperation(operation.Status))`
    91  func FromOperation(operation operation.Status) *operationOption {
    92  	return &operationOption{
    93  		code:   operation.GetStatus(),
    94  		issues: operation.GetIssues(),
    95  	}
    96  }
    97  
    98  type oeOpt interface {
    99  	applyToOperationError(oe *operationError)
   100  }
   101  
   102  func Operation(opts ...oeOpt) error {
   103  	oe := &operationError{
   104  		code: Ydb.StatusIds_STATUS_CODE_UNSPECIFIED,
   105  	}
   106  	for _, opt := range opts {
   107  		if opt != nil {
   108  			opt.applyToOperationError(oe)
   109  		}
   110  	}
   111  
   112  	return oe
   113  }
   114  
   115  func (e *operationError) Issues() []*Ydb_Issue.IssueMessage {
   116  	return e.issues
   117  }
   118  
   119  func (e *operationError) Error() string {
   120  	b := xstring.Buffer()
   121  	defer b.Free()
   122  	b.WriteString(e.Name())
   123  	fmt.Fprintf(b, " (code = %d", e.code)
   124  	if len(e.address) > 0 {
   125  		b.WriteString(", address = ")
   126  		b.WriteString(e.address)
   127  	}
   128  	if e.nodeID > 0 {
   129  		b.WriteString(", nodeID = ")
   130  		b.WriteString(strconv.FormatUint(uint64(e.nodeID), 10))
   131  	}
   132  	if len(e.issues) > 0 {
   133  		b.WriteString(", issues = ")
   134  		b.WriteString(e.issues.String())
   135  	}
   136  	b.WriteString(")")
   137  
   138  	return b.String()
   139  }
   140  
   141  // IsOperationError reports whether err is operationError with given errType codes.
   142  func IsOperationError(err error, codes ...Ydb.StatusIds_StatusCode) bool {
   143  	var op *operationError
   144  	if !errors.As(err, &op) {
   145  		return false
   146  	}
   147  	if len(codes) == 0 {
   148  		return true
   149  	}
   150  	for _, code := range codes {
   151  		if op.code == code {
   152  			return true
   153  		}
   154  	}
   155  
   156  	return false
   157  }
   158  
   159  const issueCodeTransactionLocksInvalidated = 2001
   160  
   161  func IsOperationErrorTransactionLocksInvalidated(err error) (isTLI bool) {
   162  	if IsOperationError(err, Ydb.StatusIds_ABORTED) {
   163  		IterateByIssues(err, func(_ string, code Ydb.StatusIds_StatusCode, severity uint32) {
   164  			isTLI = isTLI || (code == issueCodeTransactionLocksInvalidated)
   165  		})
   166  	}
   167  
   168  	return isTLI
   169  }
   170  
   171  func (e *operationError) Type() Type {
   172  	switch e.code {
   173  	case
   174  		Ydb.StatusIds_ABORTED,
   175  		Ydb.StatusIds_UNAVAILABLE,
   176  		Ydb.StatusIds_OVERLOADED,
   177  		Ydb.StatusIds_BAD_SESSION,
   178  		Ydb.StatusIds_SESSION_BUSY:
   179  		return TypeRetryable
   180  	case
   181  		Ydb.StatusIds_UNDETERMINED,
   182  		Ydb.StatusIds_SESSION_EXPIRED:
   183  		return TypeConditionallyRetryable
   184  	default:
   185  		return TypeUndefined
   186  	}
   187  }
   188  
   189  func (e *operationError) BackoffType() backoff.Type {
   190  	switch e.code {
   191  	case Ydb.StatusIds_OVERLOADED:
   192  		return backoff.TypeSlow
   193  	case
   194  		Ydb.StatusIds_ABORTED,
   195  		Ydb.StatusIds_UNAVAILABLE,
   196  		Ydb.StatusIds_CANCELLED,
   197  		Ydb.StatusIds_SESSION_BUSY,
   198  		Ydb.StatusIds_UNDETERMINED:
   199  		return backoff.TypeFast
   200  	default:
   201  		return backoff.TypeNoBackoff
   202  	}
   203  }
   204  
   205  func (e *operationError) IsRetryObjectValid() bool {
   206  	switch e.code {
   207  	case
   208  		Ydb.StatusIds_BAD_SESSION,
   209  		Ydb.StatusIds_SESSION_EXPIRED,
   210  		Ydb.StatusIds_SESSION_BUSY:
   211  		return false
   212  	default:
   213  		return true
   214  	}
   215  }
   216  
   217  func OperationError(err error) Error {
   218  	var o *operationError
   219  	if errors.As(err, &o) {
   220  		return o
   221  	}
   222  
   223  	return nil
   224  }