github.com/opiuman/genqlient@v1.0.0/generate/errors.go (about) 1 package generate 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "go/scanner" 8 "math" 9 "strconv" 10 "strings" 11 12 "github.com/vektah/gqlparser/v2/ast" 13 "github.com/vektah/gqlparser/v2/gqlerror" 14 ) 15 16 type errorPos struct { 17 filename string 18 line, col int 19 } 20 21 func (pos *errorPos) String() string { 22 filename, lineOffset := splitFilename(pos.filename) 23 line := lineOffset + pos.line 24 if line != 0 { 25 return fmt.Sprintf("%v:%v", filename, line) 26 } else { 27 return filename 28 } 29 } 30 31 type genqlientError struct { 32 pos *errorPos 33 msg string 34 wrapped error 35 } 36 37 func splitFilename(filename string) (name string, lineOffset int) { 38 split := strings.Split(filename, ":") 39 if len(split) != 2 { 40 return filename, 0 41 } 42 43 offset, err := strconv.Atoi(split[1]) 44 if err != nil { 45 return split[0], 0 46 } 47 return split[0], offset - 1 48 } 49 50 func (err *genqlientError) Error() string { 51 if err.pos != nil { 52 return err.pos.String() + ": " + err.msg 53 } else { 54 return err.msg 55 } 56 } 57 58 func (err *genqlientError) Unwrap() error { 59 return err.wrapped 60 } 61 62 func errorf(pos *ast.Position, msg string, args ...interface{}) error { 63 // TODO: alternately accept a filename only, or maybe even a go-parser pos 64 65 // We do all our own wrapping, because if the wrapped error already has a 66 // pos, we want to extract it out and put it at the front, not in the 67 // middle. 68 69 var wrapped error 70 var wrapIndex int 71 for i, arg := range args { 72 if wrapped == nil { 73 var ok bool 74 wrapped, ok = arg.(error) 75 if ok { 76 wrapIndex = i 77 } 78 } 79 } 80 81 var wrappedGenqlient *genqlientError 82 isGenqlient := errors.As(wrapped, &wrappedGenqlient) 83 var wrappedGraphQL *gqlerror.Error 84 isGraphQL := errors.As(wrapped, &wrappedGraphQL) 85 if !isGraphQL { 86 var wrappedGraphQLList gqlerror.List 87 isGraphQLList := errors.As(wrapped, &wrappedGraphQLList) 88 if isGraphQLList && len(wrappedGraphQLList) > 0 { 89 isGraphQL = true 90 wrappedGraphQL = wrappedGraphQLList[0] 91 } 92 } 93 94 var errPos *errorPos 95 if pos != nil { 96 errPos = &errorPos{ 97 filename: pos.Src.Name, 98 line: pos.Line, 99 col: pos.Column, 100 } 101 } else if isGenqlient { 102 errPos = wrappedGenqlient.pos 103 } else if isGraphQL { 104 filename, _ := wrappedGraphQL.Extensions["file"].(string) 105 if filename != "" { 106 var loc gqlerror.Location 107 if len(wrappedGraphQL.Locations) > 0 { 108 loc = wrappedGraphQL.Locations[0] 109 } 110 errPos = &errorPos{ 111 filename: filename, 112 line: loc.Line, 113 col: loc.Column, 114 } 115 } 116 } 117 118 if wrapped != nil { 119 errText := wrapped.Error() 120 if isGenqlient { 121 errText = wrappedGenqlient.msg 122 } else if isGraphQL { 123 errText = wrappedGraphQL.Message 124 } 125 args[wrapIndex] = errText 126 } 127 128 msg = fmt.Sprintf(msg, args...) 129 130 return &genqlientError{ 131 msg: msg, 132 pos: errPos, 133 wrapped: wrapped, 134 } 135 } 136 137 // goSourceError processes the error(s) returned by go tooling (gofmt, etc.) 138 // into a nice error message. 139 // 140 // In practice, such errors are genqlient internal errors, but it's still 141 // useful to format them nicely for debugging. 142 func goSourceError( 143 failedOperation string, // e.g. "gofmt", for the error message 144 source []byte, 145 err error, 146 ) error { 147 var errTexts []string 148 var scanErrs scanner.ErrorList 149 var scanErr *scanner.Error 150 var badLines map[int]bool 151 152 if errors.As(err, &scanErrs) { 153 errTexts = make([]string, len(scanErrs)) 154 badLines = make(map[int]bool, len(scanErrs)) 155 for i, scanErr := range scanErrs { 156 errTexts[i] = err.Error() 157 badLines[scanErr.Pos.Line] = true 158 } 159 } else if errors.As(err, &scanErr) { 160 errTexts = []string{scanErr.Error()} 161 badLines = map[int]bool{scanErr.Pos.Line: true} 162 } else { 163 errTexts = []string{err.Error()} 164 } 165 166 lines := bytes.SplitAfter(source, []byte("\n")) 167 lineNoWidth := int(math.Ceil(math.Log10(float64(len(lines) + 1)))) 168 for i, line := range lines { 169 prefix := " " 170 if badLines[i] { 171 prefix = "> " 172 } 173 lineNo := strconv.Itoa(i + 1) 174 padding := strings.Repeat(" ", lineNoWidth-len(lineNo)) 175 lines[i] = []byte(fmt.Sprintf("%s%s%s | %s", prefix, padding, lineNo, line)) 176 } 177 178 return errorf(nil, 179 "genqlient internal error: failed to %s code:\n\t%s---source code---\n%s", 180 failedOperation, strings.Join(errTexts, "\n\t"), bytes.Join(lines, nil)) 181 }