github.com/machinefi/w3bstream@v1.6.5-rc9.0.20240426031326-b8c7c4876e72/pkg/depends/kit/statusx/error.go (about)

     1  package statusx
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"net/http"
     7  	"regexp"
     8  	"sort"
     9  	"strconv"
    10  	"strings"
    11  
    12  	"github.com/pkg/errors"
    13  )
    14  
    15  type Error interface {
    16  	StatusErr() *StatusErr
    17  	Error() string
    18  }
    19  
    20  type ServiceCode interface {
    21  	ServiceCode() int
    22  }
    23  
    24  func IsStatusErr(err error) (*StatusErr, bool) {
    25  	if err == nil {
    26  		return nil, false
    27  	}
    28  
    29  	if e, ok := err.(Error); ok {
    30  		return e.StatusErr(), ok
    31  	}
    32  
    33  	se, ok := err.(*StatusErr)
    34  	return se, ok
    35  }
    36  
    37  func FromErr(err error) *StatusErr {
    38  	if err == nil {
    39  		return nil
    40  	}
    41  	if se, ok := IsStatusErr(err); ok {
    42  		return se
    43  	}
    44  	return NewUnknownErr().WithDesc(err.Error())
    45  }
    46  
    47  func Wrap(err error, code int, key string, msgs ...string) *StatusErr {
    48  	if err == nil {
    49  		return nil
    50  	}
    51  
    52  	if len(strconv.Itoa(code)) == 3 {
    53  		code = code * 1e6
    54  	}
    55  
    56  	msg := key
    57  
    58  	if len(msgs) > 0 {
    59  		msg = msgs[0]
    60  	}
    61  
    62  	desc := ""
    63  
    64  	if len(msgs) > 1 {
    65  		desc = strings.Join(msgs[1:], "\n")
    66  	} else {
    67  		desc = err.Error()
    68  	}
    69  
    70  	// err = errors.WithMessage(err, "asdfasdfasdfasdfass")
    71  	s := &StatusErr{
    72  		Key:   key,
    73  		Code:  code,
    74  		Msg:   msg,
    75  		Desc:  desc,
    76  		error: errors.WithStack(err),
    77  	}
    78  
    79  	return s
    80  }
    81  
    82  func NewUnknownErr() *StatusErr {
    83  	return NewStatusErr("UnknownError", http.StatusInternalServerError*1e6, "unknown error")
    84  }
    85  
    86  func NewStatusErr(key string, code int, msg string) *StatusErr {
    87  	return &StatusErr{
    88  		Key:  key,
    89  		Code: code,
    90  		Msg:  msg,
    91  	}
    92  }
    93  
    94  type StatusErr struct {
    95  	Key       string      `json:"key"       xml:"key"`       // key of err
    96  	Code      int         `json:"code"      xml:"code"`      // unique err code
    97  	Msg       string      `json:"msg"       xml:"msg"`       // msg of err
    98  	Desc      string      `json:"desc"      xml:"desc"`      // desc of err
    99  	CanBeTalk bool        `json:"canBeTalk" xml:"canBeTalk"` // can be task error; for client to should error msg to end user
   100  	ID        string      `json:"id"        xml:"id"`        // request ID or other request context
   101  	Sources   []string    `json:"sources"   xml:"sources"`   // error tracing
   102  	Fields    ErrorFields `json:"fields"    xml:"fields"`    // error in where fields
   103  	error     error
   104  }
   105  
   106  // @err[UnknownError][500000000][unknown error]
   107  var regexpStatusErrSummary = regexp.MustCompile(`@StatusErr\[(.+)\]\[(.+)\]\[(.+)\](!)?`)
   108  
   109  func ParseStatusErrSummary(s string) (*StatusErr, error) {
   110  	if !regexpStatusErrSummary.Match([]byte(s)) {
   111  		return nil, fmt.Errorf("unsupported status err summary: %s", s)
   112  	}
   113  
   114  	matched := regexpStatusErrSummary.FindStringSubmatch(s)
   115  
   116  	code, _ := strconv.ParseInt(matched[2], 10, 64)
   117  
   118  	return &StatusErr{
   119  		Key:       matched[1],
   120  		Code:      int(code),
   121  		Msg:       matched[3],
   122  		CanBeTalk: matched[4] != "",
   123  	}, nil
   124  }
   125  
   126  func (se *StatusErr) Summary() string {
   127  	s := fmt.Sprintf(
   128  		`@StatusErr[%s][%d][%s]`,
   129  		se.Key,
   130  		se.Code,
   131  		se.Msg,
   132  	)
   133  
   134  	if se.CanBeTalk {
   135  		return s + "!"
   136  	}
   137  	return s
   138  }
   139  
   140  func (se *StatusErr) Is(err error) bool {
   141  	e := FromErr(err)
   142  	if se == nil || e == nil {
   143  		return false
   144  	}
   145  	return e.Key == se.Key && e.Code == se.Code
   146  }
   147  
   148  func StatusCodeFromCode(code int) int {
   149  	strCode := fmt.Sprintf("%d", code)
   150  	if len(strCode) < 3 {
   151  		return 0
   152  	}
   153  	statusCode, _ := strconv.Atoi(strCode[:3])
   154  	return statusCode
   155  }
   156  
   157  func (se *StatusErr) StatusCode() int {
   158  	return StatusCodeFromCode(se.Code)
   159  }
   160  
   161  func (se *StatusErr) Error() string {
   162  	s := fmt.Sprintf(
   163  		"[%s]%s%s",
   164  		strings.Join(se.Sources, ","),
   165  		se.Summary(),
   166  		se.Fields,
   167  	)
   168  
   169  	if se.Desc != "" {
   170  		s += " " + se.Desc
   171  	}
   172  
   173  	return s
   174  }
   175  
   176  func (se StatusErr) WithMsg(msg string) *StatusErr {
   177  	se.Msg = msg
   178  	return &se
   179  }
   180  
   181  func (se StatusErr) WithDesc(desc string) *StatusErr {
   182  	se.Desc = desc
   183  	return &se
   184  }
   185  
   186  func (se StatusErr) WithID(id string) *StatusErr {
   187  	se.ID = id
   188  	return &se
   189  }
   190  
   191  func (se StatusErr) AppendSource(sourceName string) *StatusErr {
   192  	length := len(se.Sources)
   193  	if length == 0 || se.Sources[length-1] != sourceName {
   194  		se.Sources = append(se.Sources, sourceName)
   195  	}
   196  	return &se
   197  }
   198  
   199  func (se StatusErr) EnableErrTalk() *StatusErr {
   200  	se.CanBeTalk = true
   201  	return &se
   202  }
   203  
   204  func (se StatusErr) DisableErrTalk() *StatusErr {
   205  	se.CanBeTalk = false
   206  	return &se
   207  }
   208  
   209  func (se StatusErr) AppendErrorField(in string, field string, msg string) *StatusErr {
   210  	se.Fields = append(se.Fields, NewErrorField(in, field, msg))
   211  	return &se
   212  }
   213  
   214  func (se StatusErr) AppendErrorFields(errorFields ...*ErrorField) *StatusErr {
   215  	se.Fields = append(se.Fields, errorFields...)
   216  	return &se
   217  }
   218  
   219  func NewErrorField(in string, field string, msg string) *ErrorField {
   220  	return &ErrorField{
   221  		In:    in,
   222  		Field: field,
   223  		Msg:   msg,
   224  	}
   225  }
   226  
   227  type ErrorField struct {
   228  	Field string `json:"field" xml:"field"` // Field path: prop.slice[2].a
   229  	Msg   string `json:"msg"   xml:"msg"`   // Msg message
   230  	In    string `json:"in"    xml:"in"`    // In location eq. body, query, header, path, formData
   231  }
   232  
   233  func (s ErrorField) String() string {
   234  	return s.Field + " in " + s.In + " - " + s.Msg
   235  }
   236  
   237  type ErrorFields []*ErrorField
   238  
   239  func (fs ErrorFields) String() string {
   240  	if len(fs) == 0 {
   241  		return ""
   242  	}
   243  
   244  	sort.Sort(fs)
   245  
   246  	buf := &bytes.Buffer{}
   247  	buf.WriteString("<")
   248  	for i, f := range fs {
   249  		if i > 0 {
   250  			buf.WriteString(", ")
   251  		}
   252  		buf.WriteString(f.String())
   253  	}
   254  	buf.WriteString(">")
   255  	return buf.String()
   256  }
   257  
   258  func (fs ErrorFields) Len() int {
   259  	return len(fs)
   260  }
   261  
   262  func (fs ErrorFields) Swap(i, j int) {
   263  	fs[i], fs[j] = fs[j], fs[i]
   264  }
   265  
   266  func (fs ErrorFields) Less(i, j int) bool {
   267  	return fs[i].Field < fs[j].Field
   268  }