Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 29 additions & 17 deletions pkg/api/graphql_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,11 @@ func NewGraphQLClient(opts ClientOptions) (*GraphQLClient, error) {
endpoint = swapHost(endpoint, opts.APIHost)
}

graphqlHTTPClient := *httpClient
graphqlHTTPClient.Transport = graphqlErrorRoundTripper{rt: httpClient.Transport}

return &GraphQLClient{
client: graphql.NewClient(endpoint, httpClient),
client: graphql.NewClient(endpoint, &graphqlHTTPClient),
endpoint: endpoint,
httpClient: httpClient,
}, nil
Expand Down Expand Up @@ -122,6 +125,10 @@ func (c *GraphQLClient) Do(query string, variables map[string]interface{}, respo
// Provided input will be set as a variable named input.
func (c *GraphQLClient) MutateWithContext(ctx context.Context, name string, m interface{}, variables map[string]interface{}) error {
err := c.client.MutateNamed(ctx, name, m, variables)
return normalizeGraphQLError(err)
}

func normalizeGraphQLError(err error) error {
var graphQLErrs graphql.Errors
if err != nil && errors.As(err, &graphQLErrs) {
items := make([]GraphQLErrorItem, len(graphQLErrs))
Expand All @@ -134,8 +141,14 @@ func (c *GraphQLClient) MutateWithContext(ctx context.Context, name string, m in
Type: e.Type,
}
}
err = &GraphQLError{items}
return &GraphQLError{items}
}

var httpErr *HTTPError
if err != nil && errors.As(err, &httpErr) {
return httpErr
}

return err
}

Expand All @@ -151,21 +164,7 @@ func (c *GraphQLClient) Mutate(name string, m interface{}, variables map[string]
// to the GitHub GraphQL schema.
func (c *GraphQLClient) QueryWithContext(ctx context.Context, name string, q interface{}, variables map[string]interface{}) error {
err := c.client.QueryNamed(ctx, name, q, variables)
var graphQLErrs graphql.Errors
if err != nil && errors.As(err, &graphQLErrs) {
items := make([]GraphQLErrorItem, len(graphQLErrs))
for i, e := range graphQLErrs {
items[i] = GraphQLErrorItem{
Message: e.Message,
Locations: e.Locations,
Path: e.Path,
Extensions: e.Extensions,
Type: e.Type,
}
}
err = &GraphQLError{items}
}
return err
return normalizeGraphQLError(err)
}

// Query wraps QueryWithContext using context.Background.
Expand All @@ -178,6 +177,19 @@ type graphQLResponse struct {
Errors []GraphQLErrorItem
}

type graphqlErrorRoundTripper struct {
rt http.RoundTripper
}

func (rt graphqlErrorRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
resp, err := rt.rt.RoundTrip(req)
if err != nil || resp.StatusCode >= 200 && resp.StatusCode < 300 {
return resp, err
}

return resp, HandleHTTPError(resp)
}

func graphQLEndpoint(host string) string {
if isGarage(host) {
return fmt.Sprintf("https://%s/api/graphql", host)
Expand Down
48 changes: 48 additions & 0 deletions pkg/api/graphql_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,54 @@ func TestGraphQLClientMutateError(t *testing.T) {
assert.True(t, gock.IsDone(), printPendingMocks(gock.Pending()))
}

func TestGraphQLClientQueryHTTPError(t *testing.T) {
testutils.StubConfig(t, testConfig())
t.Cleanup(gock.Off)

gock.New("https://api.github.com").
Post("/graphql").
MatchHeader("Authorization", "token abc123").
Reply(http.StatusUnauthorized).
JSON(`{"message":"Bad credentials"}`)

client, err := DefaultGraphQLClient()
assert.NoError(t, err)

var res struct{ Viewer struct{ Login string } }
err = client.Query("QUERY", &res, nil)
var httpErr *HTTPError
assert.ErrorAs(t, err, &httpErr)
assert.Equal(t, http.StatusUnauthorized, httpErr.StatusCode)
assert.Equal(t, "Bad credentials", httpErr.Message)
assert.EqualError(t, err, "HTTP 401: Bad credentials (https://api.github.com/graphql)")
assert.True(t, gock.IsDone(), printPendingMocks(gock.Pending()))
}

func TestGraphQLClientMutateHTTPError(t *testing.T) {
testutils.StubConfig(t, testConfig())
t.Cleanup(gock.Off)

gock.New("https://api.github.com").
Post("/graphql").
MatchHeader("Authorization", "token abc123").
Reply(http.StatusForbidden).
JSON(`{"message":"Forbidden"}`)

client, err := DefaultGraphQLClient()
assert.NoError(t, err)

var mutation struct {
UpdateRepository struct{ Repository struct{ Name string } }
}
err = client.Mutate("MUTATE", &mutation, nil)
var httpErr *HTTPError
assert.ErrorAs(t, err, &httpErr)
assert.Equal(t, http.StatusForbidden, httpErr.StatusCode)
assert.Equal(t, "Forbidden", httpErr.Message)
assert.EqualError(t, err, "HTTP 403: Forbidden (https://api.github.com/graphql)")
assert.True(t, gock.IsDone(), printPendingMocks(gock.Pending()))
}

func TestGraphQLClientDo(t *testing.T) {
testutils.StubConfig(t, "")

Expand Down