github.com/qioalice/ekago/v3@v3.3.2-0.20221202205325-5c262d586ee4/ekaerr/error.go (about)

     1  // Copyright © 2020-2021. All rights reserved.
     2  // Author: Ilya Stroy.
     3  // Contacts: iyuryevich@pm.me, https://github.com/qioalice
     4  // License: https://opensource.org/licenses/MIT
     5  
     6  package ekaerr
     7  
     8  import (
     9  	"fmt"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/qioalice/ekago/v3/internal/ekaletter"
    14  )
    15  
    16  type (
    17  	// Error is an object representation of your abstract error.
    18  	// Error accumulates and stores your data that will help you to log it later.
    19  	//
    20  	// In your code you must now use *Error as error indicating. Like
    21  	//     func foo() *Error
    22  	// You still may return nil if no error has occurred, or create an Error
    23  	// object and return it (by reference).
    24  	//
    25  	// TLDR:
    26  	// - DO NOT CREATE ERROR OBJECTS MANUALLY, USE Class's CONSTRUCTORS INSTEAD.
    27  	// - DO NOT FORGET TO USE Throw().
    28  	// - IF YOU WANT TO DO SOMETHING WITH YOUR ERROR, DO IT BEFORE LOGGING.
    29  	// - ALL ERROR OBJECTS ARE THREAD-UNSAFE. AVOID POTENTIAL DATA RACES!
    30  	// - NEVER USE ERROR OBJECT AS VALUE, ALWAYS USE BY REFERENCE.
    31  	//
    32  	// -----
    33  	//
    34  	// ERROR OBJECTS CREATED MANUALLY CONSIDERED NOT INITIALIZED AND WILL NOT
    35  	// WORK PROPERLY, WILL NOT CONTAIN ANY DATA AND WILL NOT WORK AT ALL!
    36  	//
    37  	// Use Class.New(), Class.Wrap(), Class.LightNew(), Class.LightWrap() methods
    38  	// to create an *Error object.
    39  	//
    40  	// BECAUSE OF THE MAIN IDEA OF THIS PACKAGE AND EKALOG PACKAGE,
    41  	// ERROR AND ITS LOGGING ARE STRONGLY LINKED AND ERROR EXISTENCE WITHOUT LOGGING
    42  	// IS MEANINGLESS.
    43  	//
    44  	// In accordance with the above, and in pursuit of better performance
    45  	// (decreasing unnecessary allocations / freeing => RAM reusing)
    46  	// once allocated *Error objects may be reused in the future.
    47  	// Because of that, follow these rules:
    48  	//
    49  	// IF YOU LOG AN ERROR, YOU MUST NOT USE AN ERROR OBJECT THEN!
    50  	// DO WHATEVER YOU WANT WITH AN ERROR OBJECT BEFORE YOU WILL LOG IT USING EKALOG.
    51  	// AFTER YOU LOG YOUR ERROR THAT ERROR OBJECT WILL BE BROKEN AND CANNOT BE USED.
    52  	//
    53  	// This is because after calling error's log finisher
    54  	// ( from https://github.com/qioalice/ekago/ekaerr/error_ekalog.go )
    55  	// an internal part of *Error object is returned to the pool for being reused
    56  	// (RAM optimisation).
    57  	//
    58  	// If you do not want to log an error but want to return it manually to the pool
    59  	// (I don't know the case when you needed it, but whatever) you can just call
    60  	// ReleaseError(err).
    61  	// If you don't do it, an allocated Error will be returned to the pool automatically
    62  	// when it goes out from the scope.
    63  	//
    64  	// -----
    65  	//
    66  	// Lightweight errors.
    67  	//
    68  	// Lightweight errors is just the same Error but w/o stacktrace generating.
    69  	// You can also add fields or messages, but they won't be linked to some stacktrace.
    70  	// Thus, Throw() call in that case is meaningless and will do nothing.
    71  	//
    72  	// Often it's useful when you don't want to log your error but do something instead.
    73  	//
    74  	// If you log lightweight Error, make sure your encoder supports lightweight errors.
    75  	// Both of ekalog.CI_ConsoleEncoder, ekalog.CI_JSONEncoder provides that.
    76  	//
    77  	// -----
    78  	//
    79  	// Error's ID.
    80  	//
    81  	// Each Error object (even lightweight) has its own ID.
    82  	// You can use that ID to find and determine Error by its ID.
    83  	//
    84  	// Earlier an UUIDv4 was used to generate ID.
    85  	// Since 3.0 ver, an ULID is used instead.
    86  	//
    87  	// Read more about ULID here: https://github.com/ulid/spec
    88  	// and here https://github.com/oklog/ulid .
    89  	Error struct {
    90  
    91  		// letter is the main internal part of Error object.
    92  		// It stores a stacktrace, fields, messages, public message, unique error's ID.
    93  		//
    94  		// If it's nil, the Error object is considered broken and not valid.
    95  		// Breaks after Error's logging. Under pool reusing (RAM optimisation).
    96  		//
    97  		// See https://github.com/qioalice/ekago/internal/letter/letter.go .
    98  		letter *ekaletter.Letter
    99  
   100  		// classID is just an ID of Class what has been used to create this object.
   101  		classID ClassID
   102  
   103  		// namespaceID is just an ID of Namespace
   104  		// the Class that has been used to create this object, belongs to.
   105  		namespaceID NamespaceID
   106  
   107  		needSetFinalizer bool
   108  	}
   109  )
   110  
   111  // IsValid reports whether Error is valid Error object or not.
   112  //
   113  // It returns false if either Error is nil, or Error has not been initialized properly
   114  // (instantiated manually instead of Class's constructors calling).
   115  //
   116  // You can also use IsNil() and IsNotNil() to make your code more clean.
   117  func (e *Error) IsValid() bool {
   118  	return e != nil && e.letter != nil
   119  }
   120  
   121  // IsNotNil is IsValid() alias. Does absolutely the same thing.
   122  // Introduced to increase your code's cleaning.
   123  func (e *Error) IsNotNil() bool {
   124  	return e.IsValid()
   125  }
   126  
   127  // IsNil is `!Error.IsValid()` code alias. Does exactly that thing, nothing more.
   128  // Introduced to increase your code's cleaning (and easy chaining typing).
   129  func (e *Error) IsNil() bool {
   130  	return !e.IsValid()
   131  }
   132  
   133  // Throw is an OOP style of raising an error up.
   134  //
   135  // YOU MUST CALL THIS METHOD EACH TIME YOU RETURNING AN ERROR OBJECT FROM THE FUNC.
   136  // THIS CALL MUST BE THE LAST ONE AT THE RETURN STATEMENT.
   137  //
   138  // Nil safe. Returns this.
   139  //
   140  // Typically, you have two cases you must follow.
   141  //  1. An Error instantiating:
   142  //     func foo() *Error {
   143  //     return ekaerr.IllegalState.New("something happen").Throw()
   144  //     }
   145  //  2. Already existed error raising up:
   146  //     (an example with adding custom stack frame's message and field)
   147  //     if err := foo(); err != nil {
   148  //     return err.S("foo failed").W("id", 42).Throw()
   149  //     }
   150  //
   151  // Does nothing for lightweight errors.
   152  // Nil safe.
   153  func (e *Error) Throw() *Error {
   154  	if e.IsValid() {
   155  		ekaletter.LIncStackIdx(e.letter)
   156  	}
   157  	return e
   158  }
   159  
   160  // AddMessage adds a message to your current Error's stack frame.
   161  // Nil safe. Returns this.
   162  func (e *Error) AddMessage(message string) *Error {
   163  	if e.IsValid() {
   164  		if message = strings.TrimSpace(message); message != "" {
   165  			ekaletter.LSetMessage(e.letter, message, true)
   166  		}
   167  	}
   168  	return e
   169  }
   170  
   171  // With adds provided ekaletter.LetterField to your current Error stack frame.
   172  // Nil safe. Returns this.
   173  func (e *Error) With(f ekaletter.LetterField) *Error { return e.addField(f) }
   174  
   175  // Methods below are code-generated.
   176  
   177  func (e *Error) WithBool(key string, value bool) *Error {
   178  	return e.addField(ekaletter.FBool(key, value))
   179  }
   180  func (e *Error) WithInt(key string, value int) *Error {
   181  	return e.addField(ekaletter.FInt(key, value))
   182  }
   183  func (e *Error) WithInt8(key string, value int8) *Error {
   184  	return e.addField(ekaletter.FInt8(key, value))
   185  }
   186  func (e *Error) WithInt16(key string, value int16) *Error {
   187  	return e.addField(ekaletter.FInt16(key, value))
   188  }
   189  func (e *Error) WithInt32(key string, value int32) *Error {
   190  	return e.addField(ekaletter.FInt32(key, value))
   191  }
   192  func (e *Error) WithInt64(key string, value int64) *Error {
   193  	return e.addField(ekaletter.FInt64(key, value))
   194  }
   195  func (e *Error) WithUint(key string, value uint) *Error {
   196  	return e.addField(ekaletter.FUint(key, value))
   197  }
   198  func (e *Error) WithUint8(key string, value uint8) *Error {
   199  	return e.addField(ekaletter.FUint8(key, value))
   200  }
   201  func (e *Error) WithUint16(key string, value uint16) *Error {
   202  	return e.addField(ekaletter.FUint16(key, value))
   203  }
   204  func (e *Error) WithUint32(key string, value uint32) *Error {
   205  	return e.addField(ekaletter.FUint32(key, value))
   206  }
   207  func (e *Error) WithUint64(key string, value uint64) *Error {
   208  	return e.addField(ekaletter.FUint64(key, value))
   209  }
   210  func (e *Error) WithUintptr(key string, value uintptr) *Error {
   211  	return e.addField(ekaletter.FUintptr(key, value))
   212  }
   213  func (e *Error) WithFloat32(key string, value float32) *Error {
   214  	return e.addField(ekaletter.FFloat32(key, value))
   215  }
   216  func (e *Error) WithFloat64(key string, value float64) *Error {
   217  	return e.addField(ekaletter.FFloat64(key, value))
   218  }
   219  func (e *Error) WithComplex64(key string, value complex64) *Error {
   220  	return e.addField(ekaletter.FComplex64(key, value))
   221  }
   222  func (e *Error) WithComplex128(key string, value complex128) *Error {
   223  	return e.addField(ekaletter.FComplex128(key, value))
   224  }
   225  func (e *Error) WithString(key string, value string) *Error {
   226  	return e.addField(ekaletter.FString(key, value))
   227  }
   228  func (e *Error) WithStringFromBytes(key string, value []byte) *Error {
   229  	return e.addField(ekaletter.FStringFromBytes(key, value))
   230  }
   231  func (e *Error) WithBoolp(key string, value *bool) *Error {
   232  	return e.addField(ekaletter.FBoolp(key, value))
   233  }
   234  func (e *Error) WithIntp(key string, value *int) *Error {
   235  	return e.addField(ekaletter.FIntp(key, value))
   236  }
   237  func (e *Error) WithInt8p(key string, value *int8) *Error {
   238  	return e.addField(ekaletter.FInt8p(key, value))
   239  }
   240  func (e *Error) WithInt16p(key string, value *int16) *Error {
   241  	return e.addField(ekaletter.FInt16p(key, value))
   242  }
   243  func (e *Error) WithInt32p(key string, value *int32) *Error {
   244  	return e.addField(ekaletter.FInt32p(key, value))
   245  }
   246  func (e *Error) WithInt64p(key string, value *int64) *Error {
   247  	return e.addField(ekaletter.FInt64p(key, value))
   248  }
   249  func (e *Error) WithUintp(key string, value *uint) *Error {
   250  	return e.addField(ekaletter.FUintp(key, value))
   251  }
   252  func (e *Error) WithUint8p(key string, value *uint8) *Error {
   253  	return e.addField(ekaletter.FUint8p(key, value))
   254  }
   255  func (e *Error) WithUint16p(key string, value *uint16) *Error {
   256  	return e.addField(ekaletter.FUint16p(key, value))
   257  }
   258  func (e *Error) WithUint32p(key string, value *uint32) *Error {
   259  	return e.addField(ekaletter.FUint32p(key, value))
   260  }
   261  func (e *Error) WithUint64p(key string, value *uint64) *Error {
   262  	return e.addField(ekaletter.FUint64p(key, value))
   263  }
   264  func (e *Error) WithFloat32p(key string, value *float32) *Error {
   265  	return e.addField(ekaletter.FFloat32p(key, value))
   266  }
   267  func (e *Error) WithFloat64p(key string, value *float64) *Error {
   268  	return e.addField(ekaletter.FFloat64p(key, value))
   269  }
   270  func (e *Error) WithStringp(key string, value *string) *Error {
   271  	return e.addField(ekaletter.FStringp(key, value))
   272  }
   273  func (e *Error) WithType(key string, value any) *Error {
   274  	return e.addField(ekaletter.FType(key, value))
   275  }
   276  func (e *Error) WithStringer(key string, value fmt.Stringer) *Error {
   277  	return e.addField(ekaletter.FStringer(key, value))
   278  }
   279  func (e *Error) WithAddr(key string, value any) *Error {
   280  	return e.addField(ekaletter.FAddr(key, value))
   281  }
   282  func (e *Error) WithUnixFromStd(key string, value time.Time) *Error {
   283  	return e.addField(ekaletter.FUnixFromStd(key, value))
   284  }
   285  func (e *Error) WithUnixNanoFromStd(key string, value time.Time) *Error {
   286  	return e.addField(ekaletter.FUnixNanoFromStd(key, value))
   287  }
   288  func (e *Error) WithUnix(key string, value int64) *Error {
   289  	return e.addField(ekaletter.FUnix(key, value))
   290  }
   291  func (e *Error) WithUnixNano(key string, value int64) *Error {
   292  	return e.addField(ekaletter.FUnixNano(key, value))
   293  }
   294  func (e *Error) WithDuration(key string, value time.Duration) *Error {
   295  	return e.addField(ekaletter.FDuration(key, value))
   296  }
   297  func (e *Error) WithArray(key string, value any) *Error {
   298  	return e.addField(ekaletter.FArray(key, value))
   299  }
   300  func (e *Error) WithObject(key string, value any) *Error {
   301  	return e.addField(ekaletter.FObject(key, value))
   302  }
   303  func (e *Error) WithMap(key string, value any) *Error {
   304  	return e.addField(ekaletter.FMap(key, value))
   305  }
   306  func (e *Error) WithExtractedMap(key string, value map[string]any) *Error {
   307  	return e.addField(ekaletter.FExtractedMap(key, value))
   308  }
   309  func (e *Error) WithAny(key string, value any) *Error {
   310  	return e.addField(ekaletter.FAny(key, value))
   311  }
   312  
   313  func (e *Error) WithMany(fields ...ekaletter.LetterField) *Error {
   314  	return e.addFields(fields)
   315  }
   316  
   317  func (e *Error) WithManyAny(fields ...any) *Error {
   318  	return e.addFieldsParse(fields, true)
   319  }
   320  
   321  func (e *Error) WithDescription(description string) *Error {
   322  	return e.WithString("description", description)
   323  }
   324  
   325  // Apply calls f callback passing the current Error object into and returning
   326  // the Error object, callback is return what.
   327  // Nil safe.
   328  //
   329  // Does nothing if f is nil.
   330  //
   331  // Why?
   332  // You can write your own fields appender like:
   333  //
   334  //	func (this *YourType) errAddIdentifiers(err *ekaerr.Error) *ekaerr.Error {
   335  //	    return err.WithManyAny("id", this.ID, "date", this.Date)
   336  //	}
   337  //
   338  // And then use it like:
   339  //
   340  //	yt := new(YourType)
   341  //	return ekaerr.IllegalState.New("Unexpected state of world").
   342  //	    Apply(yt.errAddIdentifiers).
   343  //	    Throw()
   344  //
   345  // Brilliant, isn't? And most importantly it's so clean.
   346  func (e *Error) Apply(f func(err *Error) *Error) *Error {
   347  	if e.IsValid() && f != nil {
   348  		return f(e)
   349  	}
   350  	return e
   351  }
   352  
   353  // Is reports whether Error has been instantiated by cls Class's constructors.
   354  // Returns false if either Error is not valid or Class is invalid.
   355  // Nil safe.
   356  func (e *Error) Is(cls Class) bool {
   357  	return e.IsValid() && isValidClassID(cls.id) && e.classID == cls.id
   358  }
   359  
   360  // IsAny reports whether Error belongs to at least one of passed cls Class
   361  // (has been instantiated using one of them).
   362  // Returns false if Error is not valid or no one class has been passed.
   363  // Nil-safe.
   364  func (e *Error) IsAny(cls ...Class) bool {
   365  	return e.is(cls, false)
   366  }
   367  
   368  // Of reports whether Error has been instantiated by some Class that belongs to
   369  // ns Namespace. Returns false if either Error is not valid or Namespace is invalid.
   370  // Nil safe.
   371  func (e *Error) Of(ns Namespace) bool {
   372  	return e.IsValid() && isValidNamespaceID(ns.id) && e.namespaceID == ns.id
   373  }
   374  
   375  // OfAny reports whether Error belongs to at least one of passed nss Namespace
   376  // (has been instantiated by the Class that belongs to one of nss Namespace).
   377  // Returns false if either Error is not valid or no one namespace has been passed.
   378  // Nil safe.
   379  func (e *Error) OfAny(nss ...Namespace) bool {
   380  	return e.of(nss)
   381  }
   382  
   383  // IsAnyDeep reports whether Error belongs to at least one of passed cls Class
   384  // or any of its parent (base) Class is the same as one of passed cls.
   385  // Returns false if Error is not valid or no one class has been passed.
   386  // Nil-safe.
   387  //
   388  // IsAnyDeep has increased algorithmic complexity (uses recursive algorithm)
   389  // and MUCH SLOWER than IsAny() if you pass subclasses. So, make sure it's what you need.
   390  func (e *Error) IsAnyDeep(cls ...Class) bool {
   391  	return e.is(cls, true)
   392  }
   393  
   394  // Class returns Error's Class. A special invalidClass is returned if Error is nil
   395  // or has been manually instantiated instead of constructor using.
   396  // Nil safe.
   397  func (e *Error) Class() Class {
   398  	if !e.IsValid() {
   399  		return invalidClass
   400  	}
   401  	return classByID(e.classID, true)
   402  }
   403  
   404  // ReplaceClass replaces both of Error's Class and Namespace to the provided Class
   405  // and its Namespace.
   406  // Does nothing if any of Error or new Class is invalid.
   407  // Nil safe.
   408  func (e *Error) ReplaceClass(newClass Class) *Error {
   409  	if e.IsValid() && newClass.IsValid() {
   410  		e.classID = newClass.id
   411  		e.namespaceID = newClass.namespaceID
   412  	}
   413  	return e
   414  }
   415  
   416  // ID returns an unique Error's ID as ULID. You can tell this ID to the user and
   417  // log this error. Then it will be easy to find an associated error.
   418  // Returns "" if Error is not valid.
   419  // Nil safe.
   420  func (e *Error) ID() string {
   421  	if !e.IsValid() {
   422  		return ""
   423  	}
   424  	return e.letter.SystemFields[_ERR_SYS_FIELD_IDX_ERROR_ID].SValue
   425  }
   426  
   427  // ReleaseError prepares Error for being reused in the future and releases
   428  // its internal parts (returning them to the pool).
   429  //
   430  // YOU MUST NOT USE ERROR OBJECT AFTER PASSING THEM INTO THIS FUNCTION.
   431  // YOU DO NOT NEED TO CALL THIS FUNCTION IF YOUR ERROR WILL BE LOGGED USING EKALOG.
   432  func ReleaseError(err *Error) {
   433  	if err.IsValid() {
   434  		releaseError(err)
   435  	}
   436  }