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 }