github.com/avenga/couper@v1.12.2/errors/error.go (about)

     1  package errors
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"strings"
     7  
     8  	"github.com/hashicorp/hcl/v2"
     9  )
    10  
    11  type Error struct {
    12  	// client: synopsis
    13  	// log: synopsis label message inner(Error())
    14  	httpStatus int
    15  	inner      error    // wrapped error
    16  	isParent   bool     // is a parent (no leaf) node in the error type hierarchy
    17  	kinds      []string // error_handler "event" names and relation
    18  	label      string   // mostly the user configured label for e.g. access_control or backend
    19  	message    string   // additional custom message
    20  	synopsis   string   // seen by client
    21  	Contexts   []string // context block types (e.g. api or endpoint)
    22  }
    23  
    24  type GoError interface {
    25  	error
    26  	HTTPStatus() int
    27  	LogError() string
    28  	Unwrap() error
    29  }
    30  
    31  var _ GoError = &Error{}
    32  
    33  func New() *Error {
    34  	return Server
    35  }
    36  
    37  // Equals returns true if the base type and kinds are equal.
    38  func Equals(a, b error) bool {
    39  	aerr, oka := a.(*Error)
    40  	berr, okb := b.(*Error)
    41  	if !oka || !okb {
    42  		return a == b
    43  	}
    44  	return aerr.synopsis == berr.synopsis &&
    45  		strings.Join(aerr.kinds, "") == strings.Join(berr.kinds, "")
    46  }
    47  
    48  // Status configures the http status-code which will be
    49  // written along with this error.
    50  func (e *Error) Status(s int) *Error {
    51  	err := e.clone()
    52  	err.httpStatus = s
    53  	return err
    54  }
    55  
    56  // Kind appends the given kind name to the existing ones.
    57  // Latest added should be the more specific ones.
    58  func (e *Error) Kind(name string) *Error {
    59  	err := e.clone()
    60  	e.isParent = true
    61  	err.isParent = false
    62  	err.kinds = append([]string{name}, err.kinds...)
    63  	return err
    64  }
    65  
    66  // Kinds returns all configured kinds, the
    67  // most specific one gets evaluated first.
    68  func (e *Error) Kinds() []string {
    69  	var kinds []string
    70  
    71  	if eer, ok := e.inner.(*Error); ok {
    72  		kinds = eer.Kinds()[:]
    73  	}
    74  
    75  	return append(kinds, e.kinds...)
    76  }
    77  
    78  // Context appends the given context block type to the existing ones.
    79  func (e *Error) Context(name string) *Error {
    80  	err := e.clone()
    81  	err.Contexts = append(err.Contexts, name)
    82  	return err
    83  }
    84  
    85  func (e *Error) IsParent() bool {
    86  	return e.isParent
    87  }
    88  
    89  func (e *Error) Label(name string) *Error {
    90  	err := e.clone()
    91  	err.label = name
    92  	return err
    93  }
    94  
    95  func (e *Error) Message(msg string) *Error {
    96  	err := e.clone()
    97  	err.message = msg
    98  	return err
    99  }
   100  
   101  func (e *Error) Messagef(msg string, args ...interface{}) *Error {
   102  	return e.Message(fmt.Sprintf(msg, args...))
   103  }
   104  
   105  func (e *Error) With(inner error) *Error {
   106  	if inner == nil {
   107  		return e
   108  	}
   109  	err := e.clone()
   110  	err.inner = inner
   111  	return err
   112  }
   113  
   114  func (e *Error) clone() *Error {
   115  	err := *e
   116  	err.kinds = e.kinds[:]
   117  	err.Contexts = e.Contexts[:]
   118  	err.isParent = e.isParent
   119  	return &err
   120  }
   121  
   122  func (e *Error) Error() string {
   123  	return e.synopsis
   124  }
   125  
   126  func (e *Error) Unwrap() error {
   127  	return e.inner
   128  }
   129  
   130  // LogError contains additional context which should be used for logging purposes only.
   131  func (e *Error) LogError() string {
   132  	if diags := e.getDiags(); diags != nil {
   133  		return diags.Error()
   134  	}
   135  
   136  	msg := AppendMsg(e.synopsis, e.label, e.message)
   137  
   138  	if e.inner != nil {
   139  		if inner, ok := e.inner.(*Error); ok {
   140  			if Equals(e, inner) {
   141  				inner.synopsis = "" // at least for one level, prevent duplicated synopsis
   142  			}
   143  			return AppendMsg(msg, inner.LogError())
   144  		}
   145  		msg = AppendMsg(msg, e.inner.Error())
   146  	}
   147  
   148  	return msg
   149  }
   150  
   151  func (e *Error) getDiags() hcl.Diagnostics {
   152  	if e.inner != nil {
   153  		if diags, ok := e.inner.(hcl.Diagnostics); ok {
   154  			return diags
   155  		}
   156  
   157  		if inner, ok := e.inner.(*Error); ok {
   158  			return inner.getDiags()
   159  		}
   160  	}
   161  
   162  	return nil
   163  }
   164  
   165  // HTTPStatus returns the configured http status code this error should be served with.
   166  func (e *Error) HTTPStatus() int {
   167  	if e.httpStatus == 0 {
   168  		return http.StatusInternalServerError
   169  	}
   170  	return e.httpStatus
   171  }
   172  
   173  // AppendMsg chains the given strings with ": " as separator.
   174  func AppendMsg(target string, messages ...string) string {
   175  	result := target
   176  	for _, m := range messages {
   177  		if result != "" && m != "" {
   178  			result += ": "
   179  		}
   180  		result += m
   181  	}
   182  	return result
   183  }