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

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