Skip to content
Snippets Groups Projects
Select Git revision
  • eda30797c33a55153a5db5c0f4605b7d1bd0417e
  • dev default protected
  • prod protected
  • 1.0.58
  • 1.0.57
  • 1.0.52
  • 1.0.56
  • 1.0.51
  • 1.0.50
  • 1.0.33
  • 1.0.32
  • 1.0.31
  • 1.0.30
  • 1.0.29
  • 1.0.28
  • 1.0.27
  • 1.0.26
  • 1.0.25
  • 1.0.24
  • 1.0.23
  • 1.0.22
  • 1.0.21
  • 1.0.20
23 results

BobGo.php

Blame
  • error.go 4.69 KiB
    package errors
    
    import (
    	"fmt"
    	"path"
    	"strconv"
    	"strings"
    )
    
    //CustomError implements the following interfaces:
    //	error
    //	github.com/pkg/errors: Cause
    type CustomError struct {
    	message string
    	caller  Caller
    	cause   error
    }
    
    //implement interface error:
    func (err CustomError) Error() string {
    	return err.Formatted(FormattingOptions{Causes: true})
    }
    
    //implement github.com/pkg/errors: Cause
    func (err CustomError) Cause() error {
    	return err.cause
    }
    
    func (err CustomError) Description() Description {
    	info := err.caller.Info()
    	desc := &Description{
    		Message: err.message,
    		Source:  &info,
    	}
    	if err.cause != nil {
    		causeWithStack, ok := err.cause.(*CustomError)
    		if !ok {
    			//external cause without our stack
    			//if github.com/pkg/errors, we can still get caller reference
    			desc.Cause = pkgDescription(0, err.cause)
    		} else {
    			//cause has our stack
    			causeDesription := causeWithStack.Description()
    			desc.Cause = &causeDesription
    		}
    	}
    	return *desc
    }
    
    func (err CustomError) Format(s fmt.State, v rune) {
    	s.Write([]byte(
    		err.Formatted(
    			FormattingOptions{
    				Causes:   (v == 'v' || v == 'c'), //include causes for %c and %v, s is only this error
    				NewLines: v == 'v',               //use newlines only on v, c is more compact on single line
    				Source:   s.Flag('+'),            //include source references when %+v or %+c
    			},
    		),
    	))
    }
    
    type FormattingOptions struct {
    	Causes   bool
    	NewLines bool
    	Source   bool
    }
    
    func (err CustomError) Formatted(opts FormattingOptions) string {
    	//start with this error
    	thisError := ""
    	if opts.Source {
    		thisError += fmt.Sprintf("%s/%s(%d): %s() ",
    			err.caller.Package(),
    			path.Base(err.caller.File()),
    			err.caller.Line(),
    			err.caller.Function(),
    		)
    	}
    	thisError += err.message
    	if !opts.Causes {
    		return thisError
    	}
    
    	if err.cause == nil {
    		return thisError
    	}
    
    	sep := ", because"
    	if opts.NewLines {
    		sep += "\n\t"
    	} else {
    		sep += " "
    	}
    
    	if causeWithStack, ok := err.cause.(*CustomError); ok {
    		return thisError + sep + causeWithStack.Formatted(opts)
    	}
    
    	//this level does not have our own stack, but we can detect github.com/pkg/errors stack, then:
    	//note: do not use fmt.Sprintf("%+v", err.cause) because it will repeat the stack if multiple were captured
    	//note: do not use err.cause.Error() because it does not include any stack
    	//instead, get first layer that implements it and log it
    	return thisError + pkgStack(err.cause, opts)
    }
    
    func pkgStack(err error, opts FormattingOptions) string {
    	s := ""
    	e := err
    	for e != nil {
    		if errWithStackTracer, ok := e.(stackTracer); ok {
    			st := errWithStackTracer.StackTrace()
    			for _, f := range st {
    				//source := fmt.Sprintf("%n %s %d", f, f, f) - this shows only package name, not fully qualified package name :-(
    				sources := strings.SplitN(fmt.Sprintf("%+s(%d)\n%n", f, f, f), "\n", 3)
    				//package            <-- sources[0]
    				//full filename:line <-- source[1]
    				//function name      <-- source[2]
    
    				//skip runtime packages
    				if strings.HasPrefix(sources[0], "runtime") {
    					break
    				}
    				if opts.NewLines {
    					s += ", because \n\t"
    				} else {
    					s += ", because "
    				}
    				if opts.Source {
    					s += sources[0] + "/" + path.Base(sources[1]) + ": " + sources[2] + "(): "
    				}
    				s += fmt.Sprintf("%s", e)
    			}
    			return s
    		}
    		errWithCause, ok := e.(ErrorWithCause)
    		if !ok {
    			break
    		}
    		e = errWithCause.Cause()
    	}
    	return s
    }
    
    func pkgDescription(level int, err error) *Description {
    	desc := &Description{
    		Message: fmt.Sprintf("%s", err),
    		Source:  nil,
    		Cause:   nil,
    	}
    
    	//recursively fill causes first
    	if errWithCause, ok := err.(ErrorWithCause); ok {
    		causeErr := errWithCause.Cause()
    		if causeErr != nil {
    			desc.Cause = pkgDescription(level+1, causeErr)
    		}
    	}
    
    	if errWithStackTracer, ok := err.(stackTracer); ok {
    		tempDesc := desc
    		st := errWithStackTracer.StackTrace()
    		for _, f := range st {
    			if tempDesc == nil {
    				break //stop if no more causes populated
    			}
    
    			//source := fmt.Sprintf("%n %s %d", f, f, f) - this shows only package name, not fully qualified package name :-(
    			sources := strings.SplitN(fmt.Sprintf("%+s\n%d\n%n", f, f, f), "\n", 4)
    			//package            <-- sources[0]
    			//full filename      <-- source[1]
    			//line               <-- source[2]
    			//function name      <-- source[3]
    
    			//stop if entering runtime part of the stack
    			if strings.HasPrefix(sources[0], "runtime") {
    				break
    			}
    			tempDesc.Source = &CallerInfo{
    				Package: sources[0],
    				File:    path.Base(sources[1]),
    				//Line:     sources[2],
    				Function: sources[3],
    			}
    			if i64, err := strconv.ParseInt(sources[2], 10, 64); err == nil {
    				tempDesc.Source.Line = int(i64)
    			}
    			tempDesc = tempDesc.Cause
    		} //for each stack level
    	} //if has stack
    	return desc
    }