github.com/qioalice/ekago/v3@v3.3.2-0.20221202205325-5c262d586ee4/ekaerr/error_private.go (about) 1 // Copyright © 2020. All rights reserved. 2 // Author: Ilya Yuryevich. 3 // Contacts: iyuryevich@pm.me, https://github.com/qioalice 4 // License: https://opensource.org/licenses/MIT 5 6 package ekaerr 7 8 import ( 9 "runtime" 10 "strings" 11 "unicode/utf8" 12 13 "github.com/qioalice/ekago/v3/ekasys" 14 "github.com/qioalice/ekago/v3/ekatyp" 15 "github.com/qioalice/ekago/v3/internal/ekaletter" 16 ) 17 18 // noinspection GoSnakeCaseUsage 19 const ( 20 // These constants are indexes of some Error's entities stored into 21 // en embedded Letter's SystemFields array. 22 // See https://github.com/qioalice/ekago/internal/letter/letter.go for more details. 23 24 _ERR_SYS_FIELD_IDX_CLASS_ID = 0 25 _ERR_SYS_FIELD_IDX_CLASS_NAME = 1 26 _ERR_SYS_FIELD_IDX_ERROR_ID = 2 27 ) 28 29 // prepare prepares current Error for being used assuming that Error has been 30 // obtained from the Error's pool. Returns prepared Error. 31 func (e *Error) prepare() *Error { 32 33 // Because the main reason of Error existence is being logged later, 34 // we need to make sure that it will be returned to the pool. 35 if e.needSetFinalizer { 36 runtime.SetFinalizer(e, releaseErrorForFinalizer) 37 e.needSetFinalizer = false 38 } 39 40 return e 41 } 42 43 // cleanup frees all allocated resources (RAM in 99% cases) by Error, preparing 44 // it for being returned to the pool and being reused in the future. 45 func (e *Error) cleanup() *Error { 46 47 // We don't need to cleanup Namespace's ID or Class's ID, 48 // because it will be overwritten and also do not need to update SystemFields 49 // they will be overwritten too. 50 51 e.letter.StackTrace = nil 52 53 ekaletter.LReset(e.letter) 54 return e 55 } 56 57 // addField checks whether Error is valid and adds an ekaletter.LetterField 58 // to current Error, if field is addable. 59 func (e *Error) addField(f ekaletter.LetterField) *Error { 60 if e.IsValid() { 61 ekaletter.LAddFieldWithCheck(e.letter, f) 62 } 63 return e 64 } 65 66 // addFields is the same as addField() but works with an array of ekaletter.LetterField. 67 func (e *Error) addFields(fs []ekaletter.LetterField) *Error { 68 if e.IsValid() { 69 for i, n := 0, len(fs); i < n; i++ { 70 ekaletter.LAddFieldWithCheck(e.letter, fs[i]) 71 } 72 } 73 return e 74 } 75 76 // addFieldsParse creates a ekaletter.LetterField objects based on passed values, 77 // try to treating them as a key-value pairs of that fields. 78 // Then adds generated ekaletter.LetterField to the Error only if those fields are addable. 79 func (e *Error) addFieldsParse(fs []any, onlyFields bool) *Error { 80 if e.IsValid() && len(fs) > 0 { 81 ekaletter.LParseTo(e.letter, fs, onlyFields) 82 } 83 return e 84 } 85 86 // is reports whether e belongs to at least one of passed cls Class 87 // or any of Error's parent (base) Class is the same as one of passed (if deep is true). 88 func (e *Error) is(cls []Class, deep bool) bool { 89 90 if !e.IsValid() || len(cls) == 0 { 91 return false 92 } 93 94 if deep { 95 // lock once, do not lock each time at the classByID() call. 96 registeredClassesMap.RLock() 97 defer registeredClassesMap.RUnlock() 98 } 99 100 n := len(cls) 101 for classID := e.classID; isValidClassID(classID); { 102 103 for i := 0; i < n; i++ { 104 if cls[i].id == classID { 105 return true 106 } 107 } 108 109 if deep { 110 // do not lock, already locked 111 classID = classByID(classID, false).parentID 112 } else { 113 classID = _ERR_INVALID_CLASS_ID 114 } 115 } 116 117 return false 118 } 119 120 // of reports whether Error belongs to at least one of passed nss Namespace. 121 func (e *Error) of(nss []Namespace) bool { 122 123 if !e.IsValid() || len(nss) == 0 { 124 return false 125 } 126 127 for i, n := 0, len(nss); i < n; i++ { 128 if isValidNamespaceID(nss[i].id) && e.namespaceID == nss[i].id { 129 return true 130 } 131 } 132 133 return false 134 } 135 136 // init is a part of newError() func (Error's constructor). 137 // Generates the stacktrace and an unique error's ID (ULID) saving it along with 138 // classID and namespaceID to the Error and then returns it. 139 func (e *Error) init(classID ClassID, namespaceID NamespaceID, lightweight bool) *Error { 140 141 skip := 3 // init(), newError(), [Class.New(), Class.Wrap(), Class.LightNew(), Class.LightWrap()] 142 143 if !lightweight { 144 e.letter.StackTrace = ekasys.GetStackTrace(skip, -1).ExcludeInternal() 145 } 146 147 e.letter.SystemFields[_ERR_SYS_FIELD_IDX_CLASS_ID].IValue = int64(classID) 148 e.letter.SystemFields[_ERR_SYS_FIELD_IDX_CLASS_NAME].SValue = 149 classByID(classID, true).fullName 150 e.letter.SystemFields[_ERR_SYS_FIELD_IDX_ERROR_ID].SValue = 151 ekatyp.ULID_New_OrNil().String() 152 153 e.classID = classID 154 e.namespaceID = namespaceID 155 156 return e 157 } 158 159 // construct is a part of newError() func (Error's constructor). 160 // Must be called after init() call. Builds first e's stack frame's message basing on 161 // passed 'baseMessage' and 'legacyErr'. 162 func (e *Error) construct(baseMessage string, legacyErr error) *Error { 163 164 baseMessage = strings.TrimSpace(baseMessage) 165 legacyErrStr := "" 166 167 if legacyErr != nil { 168 legacyErrStr = strings.TrimSpace(legacyErr.Error()) 169 } 170 171 // isSkipCharByte is for ASCII strings and reports whether 'b' char must be ignored 172 // or not while building string based on 'baseMessage' and 'legacyErr'. 173 isSkipCharByte := func(b byte) bool { 174 return b <= 32 || b == '!' || b == '?' || b == '.' || b == ',' || b == '-' 175 } 176 // isSkipCharByte is for UTF8 strings and reports whether 'r' rune must be ignored 177 // or not while building string based on 'baseMessage' and 'legacyErr'. 178 isSkipCharRune := func(r rune) bool { 179 return r <= 32 || r == '!' || r == '?' || r == '.' || r == ',' || r == '-' 180 } 181 182 switch { 183 case legacyErrStr != "" && baseMessage != "": 184 l, rl := len(baseMessage), utf8.RuneCountInString(baseMessage) 185 if l == rl { 186 // 'baseMessage' is ASCII, fast path 187 for rl--; rl >= 0 && isSkipCharByte(baseMessage[rl]); rl-- { 188 } 189 if rl > -1 { 190 baseMessage = baseMessage[:rl+1] 191 } else { 192 baseMessage = "" 193 } 194 } else { 195 // 'baseMessage' is UTF8, slow path. 196 // The algorithm is: 197 // 1. Reverse 'baseMessage' (because we can't access UTF8 chars at index) 198 // 2. Find you where we can start from 199 // 3. Cut the string 200 // 4. Reverse back 201 202 // 1. Reverse 'baseMessage' 203 b := make([]rune, rl) // 'baseMessage' work buffer (reversed bytes atm) 204 for _, rune_ := range baseMessage { 205 rl-- 206 b[rl] = rune_ 207 } 208 // 2. Find out where we can start from 209 i := 0 210 rl = len(b) 211 for ; i < rl && isSkipCharRune(b[i]); i++ { 212 } 213 214 // 4. Reverse back but only starting from significant chars 215 if i < rl { 216 rl-- 217 for j := i; j < rl; { 218 b[j], b[rl] = b[rl], b[j] 219 j++ 220 rl-- 221 } 222 // 3. Cut the string, transform types 223 baseMessage = string(b[i:]) 224 } else { 225 // the whole string must be ignored (cut) 226 baseMessage = "" 227 } 228 } 229 if baseMessage != "" { 230 baseMessage += ", cause: " + legacyErrStr + "." 231 break 232 } 233 fallthrough 234 235 case legacyErrStr != "" && baseMessage == "": 236 baseMessage = legacyErrStr 237 } 238 239 if baseMessage != "" { 240 ekaletter.LSetMessage(e.letter, baseMessage, true) 241 } 242 243 return e 244 } 245 246 // newError is an Error's constructor. 247 // There are several steps: 248 // 249 // 1. Getting an *Error object (from the pool or allocate at the RAM heap). 250 // 2. Generate stacktrace, generate unique Error's ID, save it along with 251 // provided 'classID' and 'namespaceID' into an Error object. 252 // 3. Initialize the first message using 'legacyErr' and 'message. 253 // 4. Parse passed 'args' and also add it as first stack frame's fields. 254 // 5. Mark first stack frame if generated message (p.3) is not empty. 255 func newError( 256 257 lightweight bool, 258 classID ClassID, namespaceID NamespaceID, 259 legacyErr error, message string, args []any, 260 261 ) *Error { 262 263 return acquireError(). 264 init(classID, namespaceID, lightweight). 265 construct(message, legacyErr). 266 addFieldsParse(args, false) 267 }