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  }