Skip to content
Merged
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
4 changes: 2 additions & 2 deletions v2/pkg/engine/resolve/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,8 @@ func (c *Context) SubgraphErrors() error {
return c.subgraphErrors
}

func (c *Context) appendSubgraphError(err error) {
c.subgraphErrors = errors.Join(c.subgraphErrors, err)
func (c *Context) appendSubgraphErrors(errs ...error) {
c.subgraphErrors = errors.Join(c.subgraphErrors, errors.Join(errs...))
}

type Request struct {
Expand Down
43 changes: 38 additions & 5 deletions v2/pkg/engine/resolve/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"encoding/json"
goerrors "errors"
"fmt"
"github.com/wundergraph/graphql-go-tools/v2/pkg/errorcodes"
"net/http"
"net/http/httptrace"
"slices"
Expand All @@ -16,6 +15,8 @@ import (
"sync"
"time"

"github.com/wundergraph/graphql-go-tools/v2/pkg/errorcodes"

"github.com/buger/jsonparser"
"github.com/cespare/xxhash/v2"
"github.com/pkg/errors"
Expand Down Expand Up @@ -559,6 +560,11 @@ func (l *Loader) mergeResult(fetchItem *FetchItem, res *result, items []*astjson
}
value, err := astjson.ParseBytesWithoutCache(res.out.Bytes())
if err != nil {
// Fall back to status code if parsing fails and non-2XX
if (res.statusCode > 0 && res.statusCode < 200) || res.statusCode >= 300 {
return l.renderErrorsStatusFallback(fetchItem, res, res.statusCode)
}

return l.renderErrorsFailedToFetch(fetchItem, res, invalidGraphQLResponse)
}

Expand Down Expand Up @@ -587,6 +593,14 @@ func (l *Loader) mergeResult(fetchItem *FetchItem, res *result, items []*astjson
value = value.Get(res.postProcessing.SelectResponseDataPath...)
// Check if the not set or null
if astjson.ValueIsNull(value) {
// When:
// - No errors or data are present
// - Status code is not within the 2XX range
// We can fall back to a status code based error
if !hasErrors && ((res.statusCode > 0 && res.statusCode < 200) || res.statusCode >= 300) {
return l.renderErrorsStatusFallback(fetchItem, res, res.statusCode)
}

// If we didn't get any data nor errors, we return an error because the response is invalid
// Returning an error here also avoids the need to walk over it later.
if !hasErrors && !l.resolvable.options.ApolloCompatibilitySuppressFetchErrors {
Expand Down Expand Up @@ -699,7 +713,7 @@ func (l *Loader) appendSubgraphError(res *result, fetchItem *FetchItem, value *a
subgraphError.AppendDownstreamError(&gErr)
}

l.ctx.appendSubgraphError(goerrors.Join(res.err, subgraphError))
l.ctx.appendSubgraphErrors(res.err, subgraphError)

return nil
}
Expand Down Expand Up @@ -1012,7 +1026,7 @@ func (l *Loader) addApolloRouterCompatibilityError(res *result) error {
}

func (l *Loader) renderErrorsFailedToFetch(fetchItem *FetchItem, res *result, reason string) error {
l.ctx.appendSubgraphError(goerrors.Join(res.err, NewSubgraphError(res.ds, fetchItem.ResponsePath, reason, res.statusCode)))
l.ctx.appendSubgraphErrors(res.err, NewSubgraphError(res.ds, fetchItem.ResponsePath, reason, res.statusCode))
errorObject, err := astjson.ParseWithoutCache(l.renderSubgraphBaseError(res.ds, fetchItem.ResponsePath, reason))
if err != nil {
return err
Expand All @@ -1022,6 +1036,25 @@ func (l *Loader) renderErrorsFailedToFetch(fetchItem *FetchItem, res *result, re
return nil
}

func (l *Loader) renderErrorsStatusFallback(fetchItem *FetchItem, res *result, statusCode int) error {
reason := fmt.Sprintf("%d", statusCode)
if statusText := http.StatusText(statusCode); statusText != "" {
reason += fmt.Sprintf(": %s", statusText)
}

l.ctx.appendSubgraphErrors(res.err, NewSubgraphError(res.ds, fetchItem.ResponsePath, reason, res.statusCode))

errorObject, err := astjson.ParseWithoutCache(fmt.Sprintf(`{"message":"%s"}`, reason))
if err != nil {
return err
}

l.setSubgraphStatusCode([]*astjson.Value{errorObject}, res.statusCode)

astjson.AppendToArray(l.resolvable.errors, errorObject)
return nil
}

func (l *Loader) renderSubgraphBaseError(ds DataSourceInfo, path, reason string) string {
pathPart := l.renderAtPathErrorPart(path)
if ds.Name == "" {
Expand All @@ -1038,7 +1071,7 @@ func (l *Loader) renderSubgraphBaseError(ds DataSourceInfo, path, reason string)

func (l *Loader) renderAuthorizationRejectedErrors(fetchItem *FetchItem, res *result) error {
for i := range res.authorizationRejectedReasons {
l.ctx.appendSubgraphError(goerrors.Join(res.err, NewSubgraphError(res.ds, fetchItem.ResponsePath, res.authorizationRejectedReasons[i], res.statusCode)))
l.ctx.appendSubgraphErrors(res.err, NewSubgraphError(res.ds, fetchItem.ResponsePath, res.authorizationRejectedReasons[i], res.statusCode))
}
pathPart := l.renderAtPathErrorPart(fetchItem.ResponsePath)
extensionErrorCode := fmt.Sprintf(`"extensions":{"code":"%s"}`, errorcodes.UnauthorizedFieldOrType)
Expand Down Expand Up @@ -1079,7 +1112,7 @@ func (l *Loader) renderAuthorizationRejectedErrors(fetchItem *FetchItem, res *re
}

func (l *Loader) renderRateLimitRejectedErrors(fetchItem *FetchItem, res *result) error {
l.ctx.appendSubgraphError(goerrors.Join(res.err, NewRateLimitError(res.ds.Name, fetchItem.ResponsePath, res.rateLimitRejectedReason)))
l.ctx.appendSubgraphErrors(res.err, NewRateLimitError(res.ds.Name, fetchItem.ResponsePath, res.rateLimitRejectedReason))
pathPart := l.renderAtPathErrorPart(fetchItem.ResponsePath)
var (
err error
Expand Down
5 changes: 2 additions & 3 deletions v2/pkg/engine/resolve/resolvable.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"bytes"
"context"
"encoding/json"
goerrors "errors"
"fmt"
"io"
"strconv"
Expand Down Expand Up @@ -760,8 +759,8 @@ func (r *Resolvable) addRejectFieldError(reason string, ds DataSourceInfo, field
} else {
errorMessage = fmt.Sprintf("Unauthorized to load field '%s', Reason: %s.", fieldPath, reason)
}
r.ctx.appendSubgraphError(goerrors.Join(errors.New(errorMessage),
NewSubgraphError(ds, fieldPath, reason, 0)))
r.ctx.appendSubgraphErrors(errors.New(errorMessage),
NewSubgraphError(ds, fieldPath, reason, 0))
fastjsonext.AppendErrorWithExtensionsCodeToArray(r.astjsonArena, r.errors, errorMessage, errorcodes.UnauthorizedFieldOrType, r.path)
r.popNodePathElement(nodePath)
}
Expand Down
Loading