package gperr import ( "bytes" "encoding/json" "errors" "slices" "github.com/yusing/go-proxy/internal/utils/strutils/ansi" ) //nolint:errname type withSubject struct { Subjects []string Err error pendingSubject string } const subjectSep = " > " type highlightFunc func(subject string) string var _ PlainError = (*withSubject)(nil) var _ MarkdownError = (*withSubject)(nil) func highlightANSI(subject string) string { return ansi.HighlightRed + subject + ansi.Reset } func highlightMarkdown(subject string) string { return "**" + subject + "**" } func noHighlight(subject string) string { return subject } func PrependSubject(subject string, err error) error { if err == nil { return nil } //nolint:errorlint switch err := err.(type) { case *withSubject: return err.Prepend(subject) case *wrappedError: return &wrappedError{ Err: PrependSubject(subject, err.Err), Message: err.Message, } case Error: return err.Subject(subject) } return &withSubject{[]string{subject}, err, ""} } func (err *withSubject) Prepend(subject string) *withSubject { if subject == "" { return err } clone := *err switch subject[0] { case '[', '(', '{': // since prepend is called in depth-first order, // the subject of the index is not yet seen // add it when the next subject is seen clone.pendingSubject += subject default: clone.Subjects = append(clone.Subjects, subject) if clone.pendingSubject != "" { clone.Subjects[len(clone.Subjects)-1] = subject + clone.pendingSubject clone.pendingSubject = "" } } return &clone } func (err *withSubject) Is(other error) bool { return errors.Is(other, err.Err) } func (err *withSubject) Unwrap() error { return err.Err } func (err *withSubject) Error() string { return string(err.fmtError(highlightANSI)) } func (err *withSubject) Plain() []byte { return err.fmtError(noHighlight) } func (err *withSubject) Markdown() []byte { return err.fmtError(highlightMarkdown) } func (err *withSubject) fmtError(highlight highlightFunc) []byte { // subject is in reversed order size := 0 errStr := err.Err.Error() subjects := err.Subjects if err.pendingSubject != "" { subjects = append(subjects, err.pendingSubject) } var buf bytes.Buffer for _, s := range subjects { size += len(s) } n := len(subjects) buf.Grow(size + 2 + n*len(subjectSep) + len(errStr) + len(highlight(""))) for i := n - 1; i > 0; i-- { buf.WriteString(subjects[i]) buf.WriteString(subjectSep) } buf.WriteString(highlight(subjects[0])) if errStr != "" { buf.WriteString(": ") buf.WriteString(errStr) } return buf.Bytes() } // MarshalJSON implements the json.Marshaler interface. func (err *withSubject) MarshalJSON() ([]byte, error) { subjects := slices.Clone(err.Subjects) slices.Reverse(subjects) reversed := struct { Subjects []string `json:"subjects"` Err error `json:"err"` }{ Subjects: subjects, Err: err.Err, } return json.Marshal(reversed) }