[server] Allow Nullable action response

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/2379
GitOrigin-RevId: eae2b0b401737ceb4e4885ba47c342e26f2026a1
This commit is contained in:
pranshi06 2021-10-11 12:25:05 +05:30 committed by hasura-bot
parent e3fa6bee0a
commit be1395de31
13 changed files with 211 additions and 6 deletions

View File

@ -3,6 +3,7 @@
## Next release
(Add entries below in the order of server, console, cli, docs, others)
### Function field names customization (#7405)
It is now possible to specify the GraphQL names of tracked SQL functions in
Postgres sources, and different names may be given to the `_aggregate` and
@ -12,6 +13,7 @@ suffix-less versions. Aliases may be set by both
### Bug fixes and improvements
- server: allow nullable action responses (#4405)
- server: add support for openapi json of REST Endpoints
- server: enable inherited roles by default in the graphql-engine
- server: support MSSQL insert mutations

View File

@ -38,12 +38,12 @@ of the webhook calls in the ``extensions.internal`` field.
{
"errors": [
{
"message": "expecting object or array of objects for action webhook response",
"message": "expecting null, object or array of objects for action webhook response",
"extensions": {
"code": "parse-failed",
"path": "$",
"internal": {
"error": "expecting object or array of objects for action webhook response",
"error": "expecting null, object or array of objects for action webhook response",
"response": {
"status": 200,
"headers": [

View File

@ -209,6 +209,7 @@ makeActionResponseNoRelations annFields webhookResponse =
case webhookResponse of
AWRArray objs -> AO.array $ map mkResponseObject objs
AWRObject obj -> mkResponseObject obj
AWRNull -> AO.Null
{- Note: [Async action architecture]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -543,9 +544,16 @@ callWebhook
if
| HTTP.statusIsSuccessful responseStatus -> do
let expectingArray = isListType outputType
expectingNull = isNullableType outputType
modifyQErr addInternalToErr $ do
webhookResponse <- decodeValue responseValue
case webhookResponse of
AWRNull -> do
unless expectingNull $
if expectingArray
then throwUnexpected "expecting array for action webhook response but got null"
else throwUnexpected "expecting object for action webhook response but got null"
validateResponseObject Map.empty
AWRArray objs -> do
unless expectingArray $
throwUnexpected "expecting object for action webhook response but got array"

View File

@ -80,17 +80,20 @@ $(J.deriveJSON (J.aesonDrop 5 J.snakeCase) ''ActionWebhookErrorResponse)
data ActionWebhookResponse
= AWRArray ![Map.HashMap G.Name J.Value]
| AWRObject !(Map.HashMap G.Name J.Value)
| AWRNull
deriving (Show, Eq)
instance J.FromJSON ActionWebhookResponse where
parseJSON v = case v of
J.Array {} -> AWRArray <$> J.parseJSON v
J.Object {} -> AWRObject <$> J.parseJSON v
_ -> fail "expecting object or array of objects for action webhook response"
J.Null {} -> pure AWRNull
_ -> fail "expecting null, object or array of objects for action webhook response"
instance J.ToJSON ActionWebhookResponse where
toJSON (AWRArray objects) = J.toJSON objects
toJSON (AWRObject obj) = J.toJSON obj
toJSON (AWRNull) = J.Null
data ActionRequestInfo = ActionRequestInfo
{ _areqiUrl :: !Text,

View File

@ -3,6 +3,7 @@ module Hasura.RQL.Types.CustomTypes
emptyCustomTypes,
GraphQLType (..),
isListType,
isNullableType,
EnumTypeName (..),
EnumValueDefinition (..),
EnumTypeDefinition (..),
@ -79,6 +80,9 @@ instance J.FromJSON GraphQLType where
isListType :: GraphQLType -> Bool
isListType (GraphQLType ty) = G.isListType ty
isNullableType :: GraphQLType -> Bool
isNullableType (GraphQLType ty) = G.isNullable ty
newtype InputObjectFieldName = InputObjectFieldName {unInputObjectFieldName :: G.Name}
deriving (Show, Eq, Ord, Hashable, J.FromJSON, J.ToJSON, ToTxt, Generic, NFData, Cacheable)

View File

@ -353,6 +353,10 @@ class ActionsWebhookHandler(http.server.BaseHTTPRequestHandler):
resp, status = self.intentional_error()
self._send_response(status, resp)
elif req_path == "/null-response":
resp, status = self.null_response()
self._send_response(status, resp)
else:
self.send_response(HTTPStatus.NO_CONTENT)
self.end_headers()
@ -471,6 +475,10 @@ class ActionsWebhookHandler(http.server.BaseHTTPRequestHandler):
return resp['data']['user'][0], HTTPStatus.OK
else:
return resp['data']['user'], HTTPStatus.OK
def null_response(self):
response = None
return response, HTTPStatus.OK

View File

@ -0,0 +1,69 @@
- description: Update actions webhook to another route which returns null response (also, output_type is non-nullable)
url: /v1/query
status: 200
response:
message: success
query:
type: update_action
args:
name: create_users
definition:
kind: synchronous
arguments:
output_type: '[UserId]!'
handler: http://127.0.0.1:5593/null-response
- description: Run create_users sync action (with null_response handler)
url: /v1/graphql
status: 200
query:
query: |
mutation {
create_users {
id
}
}
response:
errors:
- extensions:
internal:
error: unexpected response
response:
status: 200
body:
headers:
- value: application/json
name: Content-Type
- value: abcd
name: Set-Cookie
request:
body:
session_variables:
x-hasura-role: admin
input: {}
action:
name: create_users
request_query: "mutation {\n create_users {\n\
\ id\n }\n}\n"
url: http://127.0.0.1:5593/null-response
headers: []
path: $
code: unexpected
message: expecting array for action webhook response but got null
- description: Revert action definition
url: /v1/query
status: 200
response:
message: success
query:
type: update_action
args:
name: create_users
definition:
kind: synchronous
arguments:
- name: users
type: '[UserInput!]!'
output_type: '[UserId]'
handler: http://127.0.0.1:5593/create-users

View File

@ -0,0 +1,70 @@
- description: Update actions webhook to another route which returns null response (also, output_type is non-nullable)
url: /v1/query
status: 200
response:
message: success
query:
type: update_action
args:
name: create_user
definition:
kind: synchronous
arguments:
output_type: UserId!
handler: http://127.0.0.1:5593/null-response
- description: Run create_user sync action (with null_response handler)
url: /v1/graphql
status: 200
query:
query: |
mutation {
create_user {
id
}
}
response:
errors:
- extensions:
internal:
error: unexpected response
response:
status: 200
body:
headers:
- value: application/json
name: Content-Type
- value: abcd
name: Set-Cookie
request:
body:
session_variables:
x-hasura-role: admin
input: {}
action:
name: create_user
request_query: "mutation {\n create_user {\n id\n }\n}\n"
url: http://127.0.0.1:5593/null-response
headers: []
path: $
code: unexpected
message: expecting object for action webhook response but got null
- description: Revert action definition
url: /v1/query
status: 200
response:
message: success
query:
type: update_action
args:
name: create_user
definition:
kind: synchronous
arguments:
- name: email
type: String!
- name: name
type: String!
output_type: UserId
handler: http://127.0.0.1:5593/create-user

View File

@ -63,7 +63,7 @@
headers: []
path: $
code: parse-failed
message: expecting object or array of objects for action webhook response
message: expecting null, object or array of objects for action webhook response
- description: Revert action wehbook
url: /v1/query
status: 200

View File

@ -0,0 +1,13 @@
description: Null response should be allowed
url: /v1/graphql
status: 200
query:
query: |
mutation {
null_response {
id
}
}
response:
data:
null_response: null

View File

@ -84,6 +84,11 @@ args:
type: ID! # For issue https://github.com/hasura/graphql-engine/issues/4061
- name: name
type: String
- name: NullableResp
fields:
- name: id
type: Int
- type: create_action
args:
@ -187,6 +192,15 @@ args:
output_type: '[UserId]!'
handler: http://127.0.0.1:5593/intentional-error
- type: create_action
args:
name: null_response
definition:
kind: synchronous
arguments:
output_type: NullableResp
handler: http://127.0.0.1:5593/null-response
- type: create_select_permission
args:
table: user

View File

@ -28,6 +28,10 @@ args:
args:
name: intentional_error
clear_data: true
- type: drop_action
args:
name: null_response
clear_data: true
# clear custom types
- type: set_custom_types
args: {}

View File

@ -63,11 +63,21 @@ class TestActionsSync:
def test_invalid_webhook_response(self, hge_ctx):
check_query_secret(hge_ctx, self.dir() + '/invalid_webhook_response.yaml')
def test_expecting_object_response(self, hge_ctx):
def test_null_response(self, hge_ctx):
check_query_secret(hge_ctx, self.dir() + '/null_response.yaml')
def test_expecting_object_response_got_null(self, hge_ctx):
check_query_secret(hge_ctx, self.dir() + '/expecting_object_response_got_null.yaml')
def test_expecting_array_response_got_null(self, hge_ctx):
check_query_secret(hge_ctx, self.dir() + '/expecting_array_response_got_null.yaml')
def test_expecting_object_response_got_array(self, hge_ctx):
check_query_secret(hge_ctx, self.dir() + '/expecting_object_response.yaml')
def test_expecting_array_response(self, hge_ctx):
def test_expecting_array_response_got_object(self, hge_ctx):
check_query_secret(hge_ctx, self.dir() + '/expecting_array_response.yaml')
# Webhook response validation tests. See https://github.com/hasura/graphql-engine/issues/3977
def test_mirror_action_not_null(self, hge_ctx):