Skip to content

Commit

Permalink
Feat(GraphQL): This PR allows to return errors from custom REST endpo…
Browse files Browse the repository at this point in the history
…int. (#6604)

Fixes GRAPHQL-359

Returned errors are parsed in type GqlErrorList []*GqlError where GqlError is


type GqlError struct {
        Message     string                   `json:"message"`
	Locations   []Location               `json:"locations,omitempty"`
	Path        []interface{}            `json:"path,omitempty"`
	Extensions  map[string]interface{}   `json:"extensions,omitempty"`
}
Example:
{"errors":[{"message": "Rest API returns Error for myFavoriteMovies query","locations": [ { "line": 5, "column": 4 } ],"path": ["Movies","name"]}]}
`
  • Loading branch information
JatinDev543 authored Oct 5, 2020
1 parent c7e0d81 commit c64c889
Show file tree
Hide file tree
Showing 3 changed files with 282 additions and 26 deletions.
41 changes: 41 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,26 @@ func getDefaultResponse() []byte {
return []byte(resTemplate)
}

func getRestError(w http.ResponseWriter, err []byte) {
w.WriteHeader(http.StatusBadRequest)
check2(w.Write(err))
}

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
}

getRestError(w, []byte(`{"errors":[{"message": "Rest API returns Error for myFavoriteMovies query","locations": [ { "line": 5, "column": 4 } ],"path": ["Movies","name"]}]}`))
}

func getFavMoviesHandler(w http.ResponseWriter, r *http.Request) {
err := verifyRequest(r, expectedRequest{
method: http.MethodGet,
Expand Down Expand Up @@ -398,6 +418,20 @@ 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
}
getRestError(w, []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 +870,10 @@ func userNameHandler(w http.ResponseWriter, r *http.Request) {
nameHandler(w, r, &inputBody)
}

func userNameErrorHandler(w http.ResponseWriter, r *http.Request) {
getRestError(w, []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 +1248,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 +1257,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 +1272,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
187 changes: 184 additions & 3 deletions graphql/e2e/custom_logic/custom_logic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ func TestCustomQueryWithNonExistentURLShouldReturnError(t *testing.T) {
require.Equal(t, x.GqlErrorList{
{
Message: "Evaluation of custom field failed because external request returned an " +
"error: unexpected status code: 404 for field: myFavoriteMovies within" +
"error: unexpected error with: 404 for field: myFavoriteMovies within" +
" type: Query.",
Locations: []x.Location{{Line: 3, Column: 3}},
},
Expand Down Expand Up @@ -557,10 +557,10 @@ func TestCustomQueryShouldPropagateErrorFromFields(t *testing.T) {

expectedErrors := x.GqlErrorList{
&x.GqlError{Message: "Evaluation of custom field failed because external request " +
"returned an error: unexpected status code: 404 for field: cars within type: Person.",
"returned an error: unexpected error with: 404 for field: cars within type: Person.",
Locations: []x.Location{{Line: 6, Column: 4}}},
&x.GqlError{Message: "Evaluation of custom field failed because external request returned" +
" an error: unexpected status code: 404 for field: bikes within type: Person.",
" an error: unexpected error with: 404 for field: bikes within type: Person.",
Locations: []x.Location{{Line: 9, Column: 4}}},
}
require.Contains(t, result.Errors, expectedErrors[0])
Expand Down Expand Up @@ -2808,3 +2808,184 @@ 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",
Locations: []x.Location{{Line: 5, Column: 4}},
Path: []interface{}{"Movies", "name"},
},
}, 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)

}
Loading

0 comments on commit c64c889

Please sign in to comment.