Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat(GraphQL): This PR allows to return errors from custom REST endpoint. #6604

Merged
merged 16 commits into from
Oct 5, 2020
36 changes: 36 additions & 0 deletions graphql/e2e/custom_logic/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,20 @@ func getDefaultResponse() []byte {
return []byte(resTemplate)
}

func getFavMoviesErrorHandler(w http.ResponseWriter, r *http.Request) {
err := verifyRequest(r, expectedRequest{
method: http.MethodGet,
urlSuffix: "/0x123?name=Author&num=10",
body: "",
headers: nil,
})
if err != nil {
check2(w.Write([]byte(err.Error())))
return
}
check2(w.Write([]byte(`{"errors":[{"message": "Rest API returns Error for myFavoriteMovies query"}]}`)))
}

func getFavMoviesHandler(w http.ResponseWriter, r *http.Request) {
err := verifyRequest(r, expectedRequest{
method: http.MethodGet,
Expand Down Expand Up @@ -398,6 +412,21 @@ func favMoviesCreateHandler(w http.ResponseWriter, r *http.Request) {
]`)))
}

func favMoviesCreateErrorHandler(w http.ResponseWriter, r *http.Request) {
err := verifyRequest(r, expectedRequest{
method: http.MethodPost,
urlSuffix: "/favMoviesCreateError",
body: `{"movies":[{"director":[{"name":"Dir1"}],"name":"Mov1"},{"name":"Mov2"}]}`,
headers: nil,
})
if err != nil {
check2(w.Write([]byte(err.Error())))
return
}

check2(w.Write([]byte(`{"errors":[{"message": "Rest API returns Error for FavoriteMoviesCreate query"}]}`)))
}

func favMoviesCreateWithNullBodyHandler(w http.ResponseWriter, r *http.Request) {
err := verifyRequest(r, expectedRequest{
method: http.MethodPost,
Expand Down Expand Up @@ -836,6 +865,10 @@ func userNameHandler(w http.ResponseWriter, r *http.Request) {
nameHandler(w, r, &inputBody)
}

func userNameErrorHandler(w http.ResponseWriter, r *http.Request) {
check2(w.Write([]byte(`{"errors":[{"message": "Rest API returns Error for field name"}]}`)))
}

func userNameWithoutAddressHandler(w http.ResponseWriter, r *http.Request) {
expectedRequest := expectedRequest{
body: `{"uid":"0x5"}`,
Expand Down Expand Up @@ -1210,6 +1243,7 @@ func main() {

// for queries
http.HandleFunc("/favMovies/", getFavMoviesHandler)
http.HandleFunc("/favMoviesError/", getFavMoviesErrorHandler)
http.HandleFunc("/favMoviesPost/", postFavMoviesHandler)
http.HandleFunc("/favMoviesPostWithBody/", postFavMoviesWithBodyHandler)
http.HandleFunc("/verifyHeaders", verifyHeadersHandler)
Expand All @@ -1218,6 +1252,7 @@ func main() {

// for mutations
http.HandleFunc("/favMoviesCreate", favMoviesCreateHandler)
http.HandleFunc("/favMoviesCreateError", favMoviesCreateErrorHandler)
http.HandleFunc("/favMoviesUpdate/", favMoviesUpdateHandler)
http.HandleFunc("/favMoviesDelete/", favMoviesDeleteHandler)
http.HandleFunc("/favMoviesCreateWithNullBody", favMoviesCreateWithNullBodyHandler)
Expand All @@ -1232,6 +1267,7 @@ func main() {

// for testing single mode
http.HandleFunc("/userName", userNameHandler)
http.HandleFunc("/userNameError", userNameErrorHandler)
http.HandleFunc("/userNameWithoutAddress", userNameWithoutAddressHandler)
http.HandleFunc("/checkHeadersForUserName", userNameHandlerWithHeaders)
http.HandleFunc("/car", carHandler)
Expand Down
179 changes: 179 additions & 0 deletions graphql/e2e/custom_logic/custom_logic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2808,3 +2808,182 @@ func TestCustomDQL(t *testing.T) {
]
}`, string(result.Data))
}

func TestCustomGetQuerywithRESTError(t *testing.T) {
schema := customTypes + `
type Query {
myFavoriteMovies(id: ID!, name: String!, num: Int): [Movie] @custom(http: {
url: "http://mock:8888/favMoviesError/$id?name=$name&num=$num",
method: "GET"
})
}`
updateSchemaRequireNoGQLErrors(t, schema)
time.Sleep(2 * time.Second)

query := `
query {
myFavoriteMovies(id: "0x123", name: "Author", num: 10) {
id
name
director {
id
name
}
}
}`
params := &common.GraphQLParams{
Query: query,
}

result := params.ExecuteAsPost(t, alphaURL)
require.Equal(t, x.GqlErrorList{
{
Message: "Rest API returns Error for myFavoriteMovies query",
},
}, result.Errors)

}

func TestCustomFieldsWithRestError(t *testing.T) {
schema := `
type Car @remote {
id: ID!
name: String!
}

type User {
id: String! @id @search(by: [hash, regexp])
name: String
@custom(
http: {
url: "http://mock:8888//userNameError"
method: "GET"
body: "{uid: $id}"
mode: SINGLE,
}
)
age: Int! @search
cars: Car
@custom(
http: {
url: "http://mock:8888/cars"
method: "GET"
body: "{uid: $id}"
mode: BATCH,
}
)
}
`

updateSchemaRequireNoGQLErrors(t, schema)
time.Sleep(2 * time.Second)

params := &common.GraphQLParams{
Query: `mutation addUser {
addUser(input: [{ id:"0x1", age: 10 }]) {
user {
id
age
}
}
}`,
}

result := params.ExecuteAsPost(t, alphaURL)
common.RequireNoGQLErrors(t, result)

queryUser := `
query ($id: String!){
queryUser(filter: {id: {eq: $id}}) {
id
name
age
cars{
name
}
}
}`

params = &common.GraphQLParams{
Query: queryUser,
Variables: map[string]interface{}{"id": "0x1"},
}

result = params.ExecuteAsPost(t, alphaURL)

expected := `
{
"queryUser": [
{
"id": "0x1",
"name": null,
"age": 10,
"cars": {
"name": "car-0x1"
}
}
]
}`

require.Equal(t, x.GqlErrorList{
{
Message: "Rest API returns Error for field name",
},
}, result.Errors)

require.JSONEq(t, expected, string(result.Data))

}

func TestCustomPostMutationWithRESTError(t *testing.T) {
schema := customTypes + `
input MovieDirectorInput {
id: ID
name: String
directed: [MovieInput]
}
input MovieInput {
id: ID
name: String
director: [MovieDirectorInput]
}
type Mutation {
createMyFavouriteMovies(input: [MovieInput!]): [Movie] @custom(http: {
url: "http://mock:8888/favMoviesCreateError",
method: "POST",
body: "{ movies: $input}"
})
}`
updateSchemaRequireNoGQLErrors(t, schema)
time.Sleep(2 * time.Second)

params := &common.GraphQLParams{
Query: `
mutation createMovies($movs: [MovieInput!]) {
createMyFavouriteMovies(input: $movs) {
id
name
director {
id
name
}
}
}`,
Variables: map[string]interface{}{
"movs": []interface{}{
map[string]interface{}{
"name": "Mov1",
"director": []interface{}{map[string]interface{}{"name": "Dir1"}},
},
map[string]interface{}{"name": "Mov2"},
}},
}

result := params.ExecuteAsPost(t, alphaURL)
require.Equal(t, x.GqlErrorList{
{
Message: "Rest API returns Error for FavoriteMoviesCreate query",
},
}, result.Errors)

}
31 changes: 27 additions & 4 deletions graphql/resolve/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -889,6 +889,10 @@ func resolveCustomField(f schema.Field, vals []interface{}, mu *sync.RWMutex, er
// To collect errors from remote GraphQL endpoint and those encountered during execution.
var errs error
var result []interface{}
type RESTErr struct{
Errors x.GqlErrorList `json:"errors,omitempty"`
}
var customErr RESTErr
if graphql {
resp := &graphqlResp{}
err = json.Unmarshal(b, resp)
Expand All @@ -906,6 +910,10 @@ func resolveCustomField(f schema.Field, vals []interface{}, mu *sync.RWMutex, er
errCh <- schema.AppendGQLErrs(errs, keyNotFoundError(f, fconf.RemoteGqlQueryName))
return
}
} else if err := json.Unmarshal(b, &customErr); err == nil && len(customErr.Errors)!=0 {
errCh<-customErr.Errors
return

} else if err := json.Unmarshal(b, &result); err != nil {
errCh <- x.GqlErrorList{jsonUnmarshalError(err, f)}
return
Expand Down Expand Up @@ -979,11 +987,14 @@ func resolveCustomField(f schema.Field, vals []interface{}, mu *sync.RWMutex, er
}

var result interface{}
type RESTErr struct{
Errors x.GqlErrorList `json:"errors,omitempty"`
}
var customErr RESTErr
var errs error
if graphql {
resp := &graphqlResp{}
err = json.Unmarshal(b, resp)
if err != nil {
if err = json.Unmarshal(b, resp); err != nil {
errChan <- x.GqlErrorList{jsonUnmarshalError(err, f)}
return
}
Expand All @@ -998,6 +1009,10 @@ func resolveCustomField(f schema.Field, vals []interface{}, mu *sync.RWMutex, er
keyNotFoundError(f, fconf.RemoteGqlQueryName))
return
}
} else if err := json.Unmarshal(b, &customErr); err == nil && len(customErr.Errors)!=0 {
errCh<-customErr.Errors
return

} else if err := json.Unmarshal(b, &result); err != nil {
errChan <- x.GqlErrorList{jsonUnmarshalError(err, f)}
return
Expand Down Expand Up @@ -1848,12 +1863,20 @@ func (hr *httpResolver) rewriteAndExecute(ctx context.Context, field schema.Fiel
// this means it had body and not graphql, so just unmarshal it and return
if hrc.RemoteGqlQueryName == "" {
var result interface{}
if err := json.Unmarshal(b, &result); err != nil {
return emptyResult(jsonUnmarshalError(err, field))
type RESTErr struct{
Errors x.GqlErrorList `json:"errors,omitempty"`
}
var customErr RESTErr
if err := json.Unmarshal(b, &customErr); err != nil || len(customErr.Errors)==0 {
if err := json.Unmarshal(b, &result); err != nil {
return emptyResult(jsonUnmarshalError(err, field))
}

}
return &Resolved{
Data: map[string]interface{}{field.Name(): result},
Field: field,
Err: customErr.Errors,
}
}

Expand Down