github.com/gogf/gf@v1.16.9/errors/gerror/gerror_error.go (about)

     1  // Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
     2  //
     3  // This Source Code Form is subject to the terms of the MIT License.
     4  // If a copy of the MIT was not distributed with this file,
     5  // You can obtain one at https://github.com/gogf/gf.
     6  
     7  package gerror
     8  
     9  import (
    10  	"bytes"
    11  	"errors"
    12  	"fmt"
    13  	"github.com/gogf/gf/errors/gcode"
    14  	"github.com/gogf/gf/internal/utils"
    15  	"io"
    16  	"runtime"
    17  	"strings"
    18  )
    19  
    20  // Error is custom error for additional features.
    21  type Error struct {
    22  	error error      // Wrapped error.
    23  	stack stack      // Stack array, which records the stack information when this error is created or wrapped.
    24  	text  string     // Error text, which is created by New* functions.
    25  	code  gcode.Code // Error code if necessary.
    26  }
    27  
    28  const (
    29  	// Filtering key for current error module paths.
    30  	stackFilterKeyLocal = "/errors/gerror/gerror"
    31  )
    32  
    33  var (
    34  	// goRootForFilter is used for stack filtering purpose.
    35  	// Mainly for development environment.
    36  	goRootForFilter = runtime.GOROOT()
    37  )
    38  
    39  func init() {
    40  	if goRootForFilter != "" {
    41  		goRootForFilter = strings.Replace(goRootForFilter, "\\", "/", -1)
    42  	}
    43  }
    44  
    45  // Error implements the interface of Error, it returns all the error as string.
    46  func (err *Error) Error() string {
    47  	if err == nil {
    48  		return ""
    49  	}
    50  	errStr := err.text
    51  	if errStr == "" && err.code != nil {
    52  		errStr = err.code.Message()
    53  	}
    54  	if err.error != nil {
    55  		if errStr != "" {
    56  			errStr += ": "
    57  		}
    58  		errStr += err.error.Error()
    59  	}
    60  	return errStr
    61  }
    62  
    63  // Code returns the error code.
    64  // It returns CodeNil if it has no error code.
    65  func (err *Error) Code() gcode.Code {
    66  	if err == nil {
    67  		return gcode.CodeNil
    68  	}
    69  	return err.code
    70  }
    71  
    72  // Cause returns the root cause error.
    73  func (err *Error) Cause() error {
    74  	if err == nil {
    75  		return nil
    76  	}
    77  	loop := err
    78  	for loop != nil {
    79  		if loop.error != nil {
    80  			if e, ok := loop.error.(*Error); ok {
    81  				// Internal Error struct.
    82  				loop = e
    83  			} else if e, ok := loop.error.(apiCause); ok {
    84  				// Other Error that implements ApiCause interface.
    85  				return e.Cause()
    86  			} else {
    87  				return loop.error
    88  			}
    89  		} else {
    90  			// return loop
    91  			// To be compatible with Case of https://github.com/pkg/errors.
    92  			return errors.New(loop.text)
    93  		}
    94  	}
    95  	return nil
    96  }
    97  
    98  // Format formats the frame according to the fmt.Formatter interface.
    99  //
   100  // %v, %s   : Print all the error string;
   101  // %-v, %-s : Print current level error string;
   102  // %+s      : Print full stack error list;
   103  // %+v      : Print the error string and full stack error list;
   104  func (err *Error) Format(s fmt.State, verb rune) {
   105  	switch verb {
   106  	case 's', 'v':
   107  		switch {
   108  		case s.Flag('-'):
   109  			if err.text != "" {
   110  				io.WriteString(s, err.text)
   111  			} else {
   112  				io.WriteString(s, err.Error())
   113  			}
   114  		case s.Flag('+'):
   115  			if verb == 's' {
   116  				io.WriteString(s, err.Stack())
   117  			} else {
   118  				io.WriteString(s, err.Error()+"\n"+err.Stack())
   119  			}
   120  		default:
   121  			io.WriteString(s, err.Error())
   122  		}
   123  	}
   124  }
   125  
   126  // Stack returns the stack callers as string.
   127  // It returns an empty string if the <err> does not support stacks.
   128  func (err *Error) Stack() string {
   129  	if err == nil {
   130  		return ""
   131  	}
   132  	var (
   133  		loop   = err
   134  		index  = 1
   135  		buffer = bytes.NewBuffer(nil)
   136  	)
   137  	for loop != nil {
   138  		buffer.WriteString(fmt.Sprintf("%d. %-v\n", index, loop))
   139  		index++
   140  		formatSubStack(loop.stack, buffer)
   141  		if loop.error != nil {
   142  			if e, ok := loop.error.(*Error); ok {
   143  				loop = e
   144  			} else {
   145  				buffer.WriteString(fmt.Sprintf("%d. %s\n", index, loop.error.Error()))
   146  				index++
   147  				break
   148  			}
   149  		} else {
   150  			break
   151  		}
   152  	}
   153  	return buffer.String()
   154  }
   155  
   156  // Current creates and returns the current level error.
   157  // It returns nil if current level error is nil.
   158  func (err *Error) Current() error {
   159  	if err == nil {
   160  		return nil
   161  	}
   162  	return &Error{
   163  		error: nil,
   164  		stack: err.stack,
   165  		text:  err.text,
   166  		code:  err.code,
   167  	}
   168  }
   169  
   170  // Next returns the next level error.
   171  // It returns nil if current level error or the next level error is nil.
   172  func (err *Error) Next() error {
   173  	if err == nil {
   174  		return nil
   175  	}
   176  	return err.error
   177  }
   178  
   179  // MarshalJSON implements the interface MarshalJSON for json.Marshal.
   180  // Note that do not use pointer as its receiver here.
   181  func (err *Error) MarshalJSON() ([]byte, error) {
   182  	return []byte(`"` + err.Error() + `"`), nil
   183  }
   184  
   185  // formatSubStack formats the stack for error.
   186  func formatSubStack(st stack, buffer *bytes.Buffer) {
   187  	if st == nil {
   188  		return
   189  	}
   190  	index := 1
   191  	space := "  "
   192  	for _, p := range st {
   193  		if fn := runtime.FuncForPC(p - 1); fn != nil {
   194  			file, line := fn.FileLine(p - 1)
   195  			// Custom filtering.
   196  			if !utils.IsDebugEnabled() {
   197  				if strings.Contains(file, utils.StackFilterKeyForGoFrame) {
   198  					continue
   199  				}
   200  			} else {
   201  				if strings.Contains(file, stackFilterKeyLocal) {
   202  					continue
   203  				}
   204  			}
   205  			// Avoid stack string like "<autogenerated>"
   206  			if strings.Contains(file, "<") {
   207  				continue
   208  			}
   209  			// Ignore GO ROOT paths.
   210  			if goRootForFilter != "" &&
   211  				len(file) >= len(goRootForFilter) &&
   212  				file[0:len(goRootForFilter)] == goRootForFilter {
   213  				continue
   214  			}
   215  			// Graceful indent.
   216  			if index > 9 {
   217  				space = " "
   218  			}
   219  			buffer.WriteString(fmt.Sprintf(
   220  				"   %d).%s%s\n    \t%s:%d\n",
   221  				index, space, fn.Name(), file, line,
   222  			))
   223  			index++
   224  		}
   225  	}
   226  }