mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-11-10 10:29:12 +03:00
Prevent empty subscription roots (fix hasura/graphql-engine#6898)
### Description We always build a subscription root, even when there was no possible fields. This breaks some third party clients, as the spec does not allow empty types in the schema. This PR fixes this by changing the `buildSubscriptionParser` helper to return a `Maybe` value, and harmonizes / cleans places where we build the subscription root. https://github.com/hasura/graphql-engine-mono/pull/2357 GitOrigin-RevId: 1aeae25e321eee957e7645c436d17e69207309fd
This commit is contained in:
parent
9bae641ac4
commit
e2ce1972f6
@ -9,6 +9,7 @@
|
||||
- server: update `create_scheduled_event` API to return `event_id` in response
|
||||
- server: fix bug which allowed inconsistent metadata to exist after the `replace_metadata` API even though `allow_inconsistent_object` is set to `false`.
|
||||
- server: fix explicit `null` values not allowed in nested object relationship inserts (#7484)
|
||||
- server: prevent empty subscription roots in the schema (#6898)
|
||||
- console: support tracking of functions with return a single row
|
||||
|
||||
## v2.0.9
|
||||
|
@ -92,9 +92,7 @@
|
||||
"queryType": {
|
||||
"name": "query_root"
|
||||
},
|
||||
"subscriptionType": {
|
||||
"name": "subscription_root"
|
||||
},
|
||||
"subscriptionType": null,
|
||||
"types": [
|
||||
{
|
||||
"inputFields": null,
|
||||
@ -766,16 +764,6 @@
|
||||
"enumValues": null,
|
||||
"description": null,
|
||||
"fields": []
|
||||
},
|
||||
{
|
||||
"inputFields": null,
|
||||
"kind": "OBJECT",
|
||||
"possibleTypes": null,
|
||||
"interfaces": [],
|
||||
"name": "subscription_root",
|
||||
"enumValues": null,
|
||||
"description": null,
|
||||
"fields": []
|
||||
}
|
||||
],
|
||||
"mutationType": null
|
||||
|
@ -256,7 +256,7 @@ buildRelayRoleContext
|
||||
mutationParserBackend <-
|
||||
buildMutationParser mempty allActionInfos nonObjectCustomTypes mutationBackendFields
|
||||
subscriptionParser <-
|
||||
P.safeSelectionSet subscriptionRoot Nothing queryPGFields <&> fmap (fmap typenameToRawRF)
|
||||
buildSubscriptionParser queryPGFields []
|
||||
queryParserFrontend <-
|
||||
queryWithIntrospectionHelper queryPGFields mutationParserFrontend subscriptionParser
|
||||
queryParserBackend <-
|
||||
@ -341,16 +341,14 @@ unauthenticatedContext
|
||||
-> RemoteSchemaPermsCtx
|
||||
-> m GQLContext
|
||||
unauthenticatedContext adminQueryRemotes adminMutationRemotes remoteSchemaPermsCtx = P.runSchemaT $ do
|
||||
let isRemoteSchemaPermsEnabled = remoteSchemaPermsCtx == RemoteSchemaPermsEnabled
|
||||
queryFields = bool (fmap (fmap RFRemote) adminQueryRemotes) [] isRemoteSchemaPermsEnabled
|
||||
mutationFields = bool (fmap (fmap RFRemote) adminMutationRemotes) [] isRemoteSchemaPermsEnabled
|
||||
mutationParser <-
|
||||
if null adminMutationRemotes
|
||||
then pure Nothing
|
||||
else P.safeSelectionSet mutationRoot Nothing mutationFields <&> Just . fmap (fmap typenameToRawRF)
|
||||
subscriptionParser <-
|
||||
P.safeSelectionSet subscriptionRoot Nothing [] <&> fmap (fmap typenameToRawRF)
|
||||
queryParser <- queryWithIntrospectionHelper queryFields mutationParser subscriptionParser
|
||||
let
|
||||
isRemoteSchemaPermsEnabled = remoteSchemaPermsCtx == RemoteSchemaPermsEnabled
|
||||
queryFields = bool (fmap (fmap RFRemote) adminQueryRemotes) [] isRemoteSchemaPermsEnabled
|
||||
mutationFields = bool (fmap (fmap RFRemote) adminMutationRemotes) [] isRemoteSchemaPermsEnabled
|
||||
mutationParser <- whenMaybe (not $ null mutationFields) $
|
||||
P.safeSelectionSet mutationRoot (Just $ G.Description "mutation root") mutationFields
|
||||
<&> fmap (fmap typenameToRawRF)
|
||||
queryParser <- queryWithIntrospectionHelper queryFields mutationParser Nothing
|
||||
pure $ GQLContext (finalizeParser queryParser) (finalizeParser <$> mutationParser)
|
||||
|
||||
|
||||
@ -543,7 +541,7 @@ buildQueryParser
|
||||
-> [ActionInfo]
|
||||
-> NonObjectTypeMap
|
||||
-> Maybe (Parser 'Output n (OMap.InsOrdHashMap G.Name (MutationRootField UnpreparedValue)))
|
||||
-> Parser 'Output n (OMap.InsOrdHashMap G.Name (QueryRootField UnpreparedValue))
|
||||
-> Maybe (Parser 'Output n (OMap.InsOrdHashMap G.Name (QueryRootField UnpreparedValue)))
|
||||
-> m (Parser 'Output n (OMap.InsOrdHashMap G.Name (QueryRootField UnpreparedValue)))
|
||||
buildQueryParser pgQueryFields remoteFields allActions nonObjectCustomTypes mutationParser subscriptionParser = do
|
||||
actionQueryFields <- concat <$> traverse (buildActionQueryFields nonObjectCustomTypes) allActions
|
||||
@ -554,18 +552,18 @@ queryWithIntrospectionHelper
|
||||
:: forall n m. (MonadSchema n m, MonadError QErr m)
|
||||
=> [P.FieldParser n (QueryRootField UnpreparedValue)]
|
||||
-> Maybe (Parser 'Output n (OMap.InsOrdHashMap G.Name (MutationRootField UnpreparedValue)))
|
||||
-> Parser 'Output n (OMap.InsOrdHashMap G.Name (QueryRootField UnpreparedValue))
|
||||
-> Maybe (Parser 'Output n (OMap.InsOrdHashMap G.Name (QueryRootField UnpreparedValue)))
|
||||
-> m (Parser 'Output n (OMap.InsOrdHashMap G.Name (QueryRootField UnpreparedValue)))
|
||||
queryWithIntrospectionHelper basicQueryFP mutationP subscriptionP = do
|
||||
basicQueryP <- queryRootFromFields basicQueryFP
|
||||
emptyIntro <- emptyIntrospection
|
||||
let directives = directivesInfo @n
|
||||
allBasicTypes <- collectTypes $
|
||||
[ P.TypeDefinitionsWrapper $ P.parserType basicQueryP
|
||||
, P.TypeDefinitionsWrapper $ P.parserType subscriptionP
|
||||
, P.TypeDefinitionsWrapper $ P.diArguments =<< directives
|
||||
allBasicTypes <- collectTypes $ catMaybes
|
||||
[ Just $ P.TypeDefinitionsWrapper $ P.parserType basicQueryP
|
||||
, Just $ P.TypeDefinitionsWrapper $ P.diArguments =<< directives
|
||||
, P.TypeDefinitionsWrapper . P.parserType <$> mutationP
|
||||
, P.TypeDefinitionsWrapper . P.parserType <$> subscriptionP
|
||||
]
|
||||
++ maybeToList (P.TypeDefinitionsWrapper . P.parserType <$> mutationP)
|
||||
allIntrospectionTypes <- collectTypes . P.parserType =<< queryRootFromFields emptyIntro
|
||||
let allTypes = Map.unions
|
||||
[ allBasicTypes
|
||||
@ -576,7 +574,7 @@ queryWithIntrospectionHelper basicQueryFP mutationP subscriptionP = do
|
||||
, sTypes = allTypes
|
||||
, sQueryType = P.parserType basicQueryP
|
||||
, sMutationType = P.parserType <$> mutationP
|
||||
, sSubscriptionType = Just $ P.parserType subscriptionP
|
||||
, sSubscriptionType = P.parserType <$> subscriptionP
|
||||
, sDirectives = directives
|
||||
}
|
||||
let partialQueryFields =
|
||||
@ -630,11 +628,13 @@ buildSubscriptionParser
|
||||
)
|
||||
=> [P.FieldParser n (QueryRootField UnpreparedValue)]
|
||||
-> [ActionInfo]
|
||||
-> m (Parser 'Output n (OMap.InsOrdHashMap G.Name (QueryRootField UnpreparedValue)))
|
||||
-> m (Maybe (Parser 'Output n (OMap.InsOrdHashMap G.Name (QueryRootField UnpreparedValue))))
|
||||
buildSubscriptionParser queryFields allActions = do
|
||||
actionSubscriptionFields <- concat <$> traverse buildActionSubscriptionFields allActions
|
||||
let subscriptionFields = queryFields <> actionSubscriptionFields
|
||||
P.safeSelectionSet subscriptionRoot Nothing subscriptionFields <&> fmap (fmap typenameToRawRF)
|
||||
whenMaybe (not $ null subscriptionFields) $
|
||||
P.safeSelectionSet subscriptionRoot Nothing subscriptionFields
|
||||
<&> fmap (fmap typenameToRawRF)
|
||||
|
||||
buildMutationParser
|
||||
:: forall m n r
|
||||
@ -654,10 +654,9 @@ buildMutationParser allRemotes allActions nonObjectCustomTypes mutationFields =
|
||||
mutationFields <>
|
||||
actionParsers <>
|
||||
fmap (fmap RFRemote) allRemotes
|
||||
if null mutationFieldsParser
|
||||
then pure Nothing
|
||||
else P.safeSelectionSet mutationRoot (Just $ G.Description "mutation root") mutationFieldsParser
|
||||
<&> Just . fmap (fmap typenameToRawRF)
|
||||
whenMaybe (not $ null mutationFieldsParser) $
|
||||
P.safeSelectionSet mutationRoot (Just $ G.Description "mutation root") mutationFieldsParser
|
||||
<&> fmap (fmap typenameToRawRF)
|
||||
|
||||
|
||||
|
||||
|
@ -0,0 +1,31 @@
|
||||
- description: Empty schema does not have a mutation root object
|
||||
url: /v1/graphql
|
||||
status: 200
|
||||
response:
|
||||
data:
|
||||
__type: null
|
||||
query:
|
||||
query: |
|
||||
query {
|
||||
__type(name: "mutation_root") {
|
||||
fields {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- description: Empty schema does not have a subscription root object
|
||||
url: /v1/graphql
|
||||
status: 200
|
||||
response:
|
||||
data:
|
||||
__type: null
|
||||
query:
|
||||
query: |
|
||||
query {
|
||||
__type(name: "subscription_root") {
|
||||
fields {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
@ -45,6 +45,17 @@ class TestGraphQLQueryBasicMySQL:
|
||||
return 'queries/graphql_query/mysql'
|
||||
|
||||
|
||||
@pytest.mark.parametrize("transport", ['http', 'websocket'])
|
||||
class TestGraphQLEmpty:
|
||||
|
||||
def test_no_empty_roots(self, hge_ctx, transport):
|
||||
check_query_f(hge_ctx, self.dir() + '/check_no_empty_roots.yaml', transport)
|
||||
|
||||
@classmethod
|
||||
def dir(cls):
|
||||
return 'queries/graphql_query/empty'
|
||||
|
||||
|
||||
@pytest.mark.parametrize("transport", ['http', 'websocket'])
|
||||
@pytest.mark.parametrize("backend", ['bigquery'])
|
||||
@usefixtures('per_class_tests_db_state')
|
||||
|
Loading…
Reference in New Issue
Block a user