@@ -12,8 +12,8 @@ import (
1212 "strings"
1313
1414 "github.com/cli/cli/v2/internal/ghinstance"
15+ graphql "github.com/cli/shurcooL-graphql"
1516 "github.com/henvic/httpretty"
16- "github.com/shurcooL/graphql"
1717)
1818
1919// ClientOption represents an argument to NewClient
@@ -98,6 +98,22 @@ func ReplaceTripper(tr http.RoundTripper) ClientOption {
9898 }
9999}
100100
101+ // ExtractHeader extracts a named header from any response received by this client and, if non-blank, saves
102+ // it to dest.
103+ func ExtractHeader (name string , dest * string ) ClientOption {
104+ return func (tr http.RoundTripper ) http.RoundTripper {
105+ return & funcTripper {roundTrip : func (req * http.Request ) (* http.Response , error ) {
106+ res , err := tr .RoundTrip (req )
107+ if err == nil {
108+ if value := res .Header .Get (name ); value != "" {
109+ * dest = value
110+ }
111+ }
112+ return res , err
113+ }}
114+ }
115+ }
116+
101117type funcTripper struct {
102118 roundTrip func (* http.Request ) (* http.Response , error )
103119}
@@ -124,7 +140,18 @@ type graphQLResponse struct {
124140type GraphQLError struct {
125141 Type string
126142 Message string
127- // Path []interface // mixed strings and numbers
143+ Path []interface {} // mixed strings and numbers
144+ }
145+
146+ func (ge GraphQLError ) PathString () string {
147+ var res strings.Builder
148+ for i , v := range ge .Path {
149+ if i > 0 {
150+ res .WriteRune ('.' )
151+ }
152+ fmt .Fprintf (& res , "%v" , v )
153+ }
154+ return res .String ()
128155}
129156
130157// GraphQLErrorResponse contains errors returned in a GraphQL response
@@ -135,9 +162,31 @@ type GraphQLErrorResponse struct {
135162func (gr GraphQLErrorResponse ) Error () string {
136163 errorMessages := make ([]string , 0 , len (gr .Errors ))
137164 for _ , e := range gr .Errors {
138- errorMessages = append (errorMessages , e .Message )
165+ msg := e .Message
166+ if p := e .PathString (); p != "" {
167+ msg = fmt .Sprintf ("%s (%s)" , msg , p )
168+ }
169+ errorMessages = append (errorMessages , msg )
170+ }
171+ return fmt .Sprintf ("GraphQL: %s" , strings .Join (errorMessages , ", " ))
172+ }
173+
174+ // Match checks if this error is only about a specific type on a specific path. If the path argument ends
175+ // with a ".", it will match all its subpaths as well.
176+ func (gr GraphQLErrorResponse ) Match (expectType , expectPath string ) bool {
177+ for _ , e := range gr .Errors {
178+ if e .Type != expectType || ! matchPath (e .PathString (), expectPath ) {
179+ return false
180+ }
181+ }
182+ return true
183+ }
184+
185+ func matchPath (p , expect string ) bool {
186+ if strings .HasSuffix (expect , "." ) {
187+ return strings .HasPrefix (p , expect ) || p == strings .TrimSuffix (expect , "." )
139188 }
140- return fmt . Sprintf ( "GraphQL error: %s" , strings . Join ( errorMessages , " \n " ))
189+ return p == expect
141190}
142191
143192// HTTPError is an error returned by a failed API call
@@ -173,7 +222,7 @@ func (err HTTPError) ScopesSuggestion() string {
173222// ScopesSuggestion is an error messaging utility that prints the suggestion to request additional OAuth
174223// scopes in case a server response indicates that there are missing scopes.
175224func ScopesSuggestion (resp * http.Response ) string {
176- if resp .StatusCode < 400 || resp .StatusCode > 499 {
225+ if resp .StatusCode < 400 || resp .StatusCode > 499 || resp . StatusCode == 422 {
177226 return ""
178227 }
179228
@@ -221,7 +270,8 @@ func EndpointNeedsScopes(resp *http.Response, s string) *http.Response {
221270 return resp
222271}
223272
224- // GraphQL performs a GraphQL request and parses the response
273+ // GraphQL performs a GraphQL request and parses the response. If there are errors in the response,
274+ // *GraphQLErrorResponse will be returned, but the data will also be parsed into the receiver.
225275func (c Client ) GraphQL (hostname string , query string , variables map [string ]interface {}, data interface {}) error {
226276 reqBody , err := json .Marshal (map [string ]interface {}{"query" : query , "variables" : variables })
227277 if err != nil {
0 commit comments