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 }