txerr/report.go (112 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package txerr import ( "bufio" "fmt" "io" "strings" "github.com/elastic/go-txfile/internal/strbld" ) // Format provides a common formatting implementation for adding the // fmt.Formatter interface to custom errors. // Usage: // func (e *myError) Format(s fmt.State, c rune) { txerr.Format(e, s, c) } func Format(err error, s fmt.State, c rune) { switch c { case 'v': if s.Flag('+') { io.WriteString(s, Report(err, true)) return } fallthrough case 's': io.WriteString(s, Report(err, false)) case 'q': fmt.Fprintf(s, "%q", Report(err, true)) } } // Report formats a strings from an error value satisfying a subset of the // Error interface. A multiline message will be generated if the error has // nested errors and verbose is true. func Report(in error, verbose bool) string { buf := &strbld.Builder{} putStr(buf, directOp(in)) putStr(buf, directCtx(in)) // if hasMsg is false, new newline will be added when printing the 'cause' hasMsg := any( putKind(buf, directKind(in)), putStr(buf, directMsg(in)), ) if !verbose { return buf.String() } switch err := in.(type) { case withChild: putErr(buf, hasMsg, err.Cause()) case withChildren: for _, sub := range err.Causes() { putSubErr(buf, sub) } } if buf.Len() == 0 { return "unknown error" } return buf.String() } func putStr(b *strbld.Builder, s string) bool { if s != "" { b.Pad(": ") b.WriteString(s) return true } return false } func putErr(b *strbld.Builder, nl bool, err error) bool { if err == nil { return false } s := fmt.Sprintf("%+v", err) if s == "" { return false } if nl { b.Pad("\n\t") } else { b.Pad(": ") } b.WriteString(s) return true } func putSubErr(b *strbld.Builder, err error) bool { if err == nil { return false } s := fmt.Sprintf("%+v", err) if s == "" { return false } b.Pad("\n\t") // iterate lines r := strings.NewReader(s) scanner := bufio.NewScanner(r) first := true for scanner.Scan() { if !first { b.Pad("\n\t") } else { first = false } b.WriteString(scanner.Text()) } return true } func putKind(b *strbld.Builder, err error) bool { if err != nil { return putStr(b, err.Error()) } return false } func any(bs ...bool) bool { for _, b := range bs { if b { return true } } return false } func directCtx(in error) string { if err, ok := in.(withContext); ok { return err.Context() } return "" }