github.com/jxskiss/gopkg/v2@v2.14.9-0.20240514120614-899f3e7952b4/infra/errcode/code.go (about)

     1  package errcode
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"strings"
     8  
     9  	"github.com/jxskiss/gopkg/v2/internal/unsafeheader"
    10  )
    11  
    12  // ErrCode is the interface implemented by an error code.
    13  type ErrCode interface {
    14  
    15  	// Error returns the error message, it implements the error interface.
    16  	Error() string
    17  
    18  	// Code returns the integer error code.
    19  	Code() int32
    20  
    21  	// Message returns the registered message for the error code.
    22  	// If message is not available, it returns an empty string "".
    23  	Message() string
    24  
    25  	// Details returns the error details attached to the error.
    26  	// It returns nil if no details are attached.
    27  	Details() []any
    28  }
    29  
    30  // Code represents an error code. It can be created by calling
    31  // Registry.Register or Registry.RegisterReserved.
    32  // Code implements the ErrCode interface.
    33  type Code struct {
    34  	code    int32
    35  	msg     string
    36  	details []any
    37  	reg     *Registry
    38  }
    39  
    40  func (e *Code) String() string { return e.Error() }
    41  
    42  func (e *Code) Format(f fmt.State, c rune) {
    43  	if c == 'v' && f.Flag('+') {
    44  		e.formatWithDetails(f)
    45  	} else {
    46  		errMsg := e.Error()
    47  		f.Write(unsafeheader.StringToBytes(errMsg))
    48  	}
    49  }
    50  
    51  func (e *Code) formatWithDetails(w io.Writer) {
    52  	const (
    53  		sep    = "\n -  "
    54  		indent = "\n    "
    55  	)
    56  	io.WriteString(w, e.Error())
    57  	if len(e.details) > 0 {
    58  		io.WriteString(w, "\ndetails:")
    59  		for _, x := range e.details {
    60  			s := fmt.Sprintf("%+v", x)
    61  			s = strings.ReplaceAll(s, "\n", indent)
    62  			io.WriteString(w, sep)
    63  			io.WriteString(w, s)
    64  		}
    65  	}
    66  }
    67  
    68  // Error returns the error message, it implements the error interface.
    69  // If message is not registered for the error code, it uses "unknown"
    70  // as a default message.
    71  func (e *Code) Error() string {
    72  	code := e.Code()
    73  	msg := e.Message()
    74  	if msg == "" {
    75  		msg = "unknown"
    76  	}
    77  	return fmt.Sprintf("[%d] %s", code, msg)
    78  }
    79  
    80  // Code returns the integer error code.
    81  func (e *Code) Code() int32 { return e.code }
    82  
    83  // Message returns the error message associated with the error code.
    84  // If message is not available, it returns an empty string "".
    85  func (e *Code) Message() string {
    86  	if e.msg != "" {
    87  		return e.msg
    88  	}
    89  	return e.reg.getMessage(e.code)
    90  }
    91  
    92  // Details returns the error details attached to the Code.
    93  // It returns nil if no details are attached.
    94  func (e *Code) Details() []any { return e.details }
    95  
    96  func (e *Code) clone() *Code {
    97  	detailsLen := len(e.details)
    98  	return &Code{
    99  		code:    e.code,
   100  		msg:     e.msg,
   101  		details: e.details[:detailsLen:detailsLen],
   102  		reg:     e.reg,
   103  	}
   104  }
   105  
   106  // WithMessage returns a copy of Code with the given message.
   107  func (e *Code) WithMessage(msg string) (code *Code) {
   108  	code = e.clone()
   109  	code.msg = msg
   110  	return
   111  }
   112  
   113  // AddDetails returns a copy of Code with new error details attached
   114  // to the returned Code.
   115  func (e *Code) AddDetails(details ...any) (code *Code) {
   116  	code = e.clone()
   117  	code.details = append(code.details, details...)
   118  	return
   119  }
   120  
   121  // RemoveDetails returns a copy of Code without the error details.
   122  // If the Code does not have error details, it returns the Code
   123  // directly instead of a copy.
   124  // When returning an error code to end-users, you may want to remove
   125  // the error details which generally should not be exposed to them.
   126  func (e *Code) RemoveDetails() (code *Code) {
   127  	if len(e.details) == 0 {
   128  		return e
   129  	}
   130  	return &Code{code: e.code, msg: e.msg, reg: e.reg}
   131  }
   132  
   133  type jsonCode struct {
   134  	Code    int32  `json:"code"`
   135  	Message string `json:"message,omitempty"`
   136  	Details []any  `json:"details,omitempty"`
   137  }
   138  
   139  // MarshalJSON implements json.Marshaler.
   140  func (e *Code) MarshalJSON() ([]byte, error) {
   141  	out := &jsonCode{
   142  		Code:    e.Code(),
   143  		Message: e.Message(),
   144  		Details: e.details,
   145  	}
   146  	return json.Marshal(out)
   147  }
   148  
   149  // UnmarshalJSON implements json.Unmarshaler.
   150  func (e *Code) UnmarshalJSON(data []byte) error {
   151  	tmp := &jsonCode{}
   152  	err := json.Unmarshal(data, tmp)
   153  	if err != nil {
   154  		return err
   155  	}
   156  	e.code = tmp.Code
   157  	e.msg = tmp.Message
   158  	e.details = tmp.Details
   159  	return nil
   160  }
   161  
   162  // Is reports whether an error is ErrCode and the code is same.
   163  //
   164  // This method allows Code to be tested using errors.Is.
   165  func (e *Code) Is(target error) bool {
   166  	if errCode, ok := target.(ErrCode); ok {
   167  		return e.code == errCode.Code()
   168  	}
   169  	return false
   170  }
   171  
   172  // Details returns the details attached to err if it is an ErrCode,
   173  // it returns nil if err is not an ErrCode.
   174  func Details(err error) []any {
   175  	if errCode := unwrapErrCode(err); errCode != nil {
   176  		return errCode.Details()
   177  	}
   178  	return nil
   179  }
   180  
   181  // Is reports whether any error in err's chain matches the target ErrCode.
   182  func Is(err error, target ErrCode) bool {
   183  	errCode := unwrapErrCode(err)
   184  	return errCode != nil && errCode.Code() == target.Code()
   185  }
   186  
   187  // IsErrCode reports whether any error in err's chain is an ErrCode.
   188  func IsErrCode(err error) bool {
   189  	if errCode := unwrapErrCode(err); errCode != nil {
   190  		return true
   191  	}
   192  	return false
   193  }
   194  
   195  func unwrapErrCode(err error) ErrCode {
   196  	type causer interface {
   197  		Cause() error
   198  	}
   199  	type wrapper interface {
   200  		Unwrap() error
   201  	}
   202  
   203  	// We make sure that a poor implementation that causes a cycle
   204  	// does not run forever.
   205  	const unwrapLimit = 100
   206  
   207  	for i := 0; err != nil && i < unwrapLimit; i++ {
   208  		if code, _ := err.(ErrCode); code != nil {
   209  			return code
   210  		}
   211  		switch e := err.(type) {
   212  		case causer:
   213  			err = e.Cause()
   214  		case wrapper:
   215  			err = e.Unwrap()
   216  		default:
   217  			return nil
   218  		}
   219  	}
   220  	return nil
   221  }