Validate the subscription root

…not to be an introspection field.
This commit is contained in:
Eugen Wissner 2024-12-01 21:47:29 +01:00
parent 2dcefff76a
commit 7ea76865e6
Signed by: belka
GPG Key ID: A27FDC1E8EE902C0
3 changed files with 26 additions and 21 deletions

View File

@ -9,6 +9,8 @@ and this project adheres to
## [Unreleased] ## [Unreleased]
### Changed ### Changed
- Remove deprecated 'gql' quasi quoter. - Remove deprecated 'gql' quasi quoter.
- Validate the subscription root not to be an introspection field
(`singleFieldSubscriptionsRule`).
## [1.4.0.0] - 2024-10-26 ## [1.4.0.0] - 2024-10-26
### Changed ### Changed

View File

@ -137,25 +137,28 @@ singleFieldSubscriptionsRule :: forall m. Rule m
singleFieldSubscriptionsRule = OperationDefinitionRule $ \case singleFieldSubscriptionsRule = OperationDefinitionRule $ \case
Full.OperationDefinition Full.Subscription name' _ _ rootFields location' -> do Full.OperationDefinition Full.Subscription name' _ _ rootFields location' -> do
groupedFieldSet <- evalStateT (collectFields rootFields) HashSet.empty groupedFieldSet <- evalStateT (collectFields rootFields) HashSet.empty
case HashSet.size groupedFieldSet of case HashSet.toList groupedFieldSet of
1 -> lift mempty [rootName]
_ | Text.isPrefixOf "__" rootName -> makeError location' name'
| Just name <- name' -> pure $ Error "exactly one top level field, which must not be an introspection field."
| otherwise -> lift mempty
[] -> makeError location' name' "exactly one top level field."
_ -> makeError location' name' "only one top level field."
_ -> lift mempty
where
makeError location' (Just operationName) errorLine = pure $ Error
{ message = concat { message = concat
[ "Subscription \"" [ "Subscription \""
, Text.unpack name , Text.unpack operationName
, "\" must select only one top level field." , "\" must select "
, errorLine
] ]
, locations = [location'] , locations = [location']
} }
| otherwise -> pure $ Error makeError location' Nothing errorLine = pure $ Error
{ message = errorMessage { message = "Anonymous Subscription must select " <> errorLine
, locations = [location'] , locations = [location']
} }
_ -> lift mempty
where
errorMessage =
"Anonymous Subscription must select only one top level field."
collectFields = foldM forEach HashSet.empty collectFields = foldM forEach HashSet.empty
forEach accumulator = \case forEach accumulator = \case
Full.FieldSelection fieldSelection -> forField accumulator fieldSelection Full.FieldSelection fieldSelection -> forField accumulator fieldSelection

View File

@ -18,7 +18,7 @@ import qualified Language.GraphQL.AST.DirectiveLocation as DirectiveLocation
import qualified Language.GraphQL.Type.In as In import qualified Language.GraphQL.Type.In as In
import qualified Language.GraphQL.Type.Out as Out import qualified Language.GraphQL.Type.Out as Out
import Language.GraphQL.Validate import Language.GraphQL.Validate
import Test.Hspec (Spec, context, describe, it, shouldBe, shouldContain, xit) import Test.Hspec (Spec, context, describe, it, shouldBe, shouldContain)
import Text.Megaparsec (parse, errorBundlePretty) import Text.Megaparsec (parse, errorBundlePretty)
petSchema :: Schema IO petSchema :: Schema IO
@ -206,14 +206,14 @@ spec =
} }
in validate queryString `shouldContain` [expected] in validate queryString `shouldContain` [expected]
xit "rejects an introspection field as the subscription root" $ it "rejects an introspection field as the subscription root" $
let queryString = "subscription sub {\n\ let queryString = "subscription sub {\n\
\ __typename\n\ \ __typename\n\
\}" \}"
expected = Error expected = Error
{ message = { message =
"Subscription \"sub\" must select only one top \ "Subscription \"sub\" must select exactly one top \
\level field." \level field, which must not be an introspection field."
, locations = [AST.Location 1 1] , locations = [AST.Location 1 1]
} }
in validate queryString `shouldContain` [expected] in validate queryString `shouldContain` [expected]