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 }