Compare commits
4 Commits
97627ffc36
...
v1.5.0.0
Author | SHA1 | Date | |
---|---|---|---|
324a4c55ff
|
|||
7ea76865e6
|
|||
2dcefff76a
|
|||
27a5a0b44e
|
10
CHANGELOG.md
10
CHANGELOG.md
@ -6,10 +6,14 @@ The format is based on
|
||||
and this project adheres to
|
||||
[Haskell Package Versioning Policy](https://pvp.haskell.org/).
|
||||
|
||||
## [Unreleased]
|
||||
### Changed
|
||||
## [1.5.0.0] - 2024-12-03
|
||||
### Removed
|
||||
- Remove deprecated 'gql' quasi quoter.
|
||||
|
||||
### Changed
|
||||
- Validate the subscription root not to be an introspection field
|
||||
(`singleFieldSubscriptionsRule`).
|
||||
|
||||
## [1.4.0.0] - 2024-10-26
|
||||
### Changed
|
||||
- `Schema.Directive` is extended to contain a boolean argument, representing
|
||||
@ -542,7 +546,7 @@ and this project adheres to
|
||||
### Added
|
||||
- Data types for the GraphQL language.
|
||||
|
||||
[Unreleased]: https://git.caraus.tech/OSS/graphql/compare/v1.4.0.0...master
|
||||
[1.5.0.0]: https://git.caraus.tech/OSS/graphql/compare/v1.4.0.0...v1.5.0.0
|
||||
[1.4.0.0]: https://git.caraus.tech/OSS/graphql/compare/v1.3.0.0...v1.4.0.0
|
||||
[1.3.0.0]: https://git.caraus.tech/OSS/graphql/compare/v1.2.0.3...v1.3.0.0
|
||||
[1.2.0.3]: https://git.caraus.tech/OSS/graphql/compare/v1.2.0.2...v1.2.0.3
|
||||
|
@ -1,7 +1,7 @@
|
||||
cabal-version: 3.0
|
||||
|
||||
name: graphql
|
||||
version: 1.4.0.0
|
||||
version: 1.5.0.0
|
||||
synopsis: Haskell GraphQL implementation
|
||||
description: Haskell <https://spec.graphql.org/June2018/ GraphQL> implementation.
|
||||
category: Language
|
||||
|
@ -137,25 +137,28 @@ singleFieldSubscriptionsRule :: forall m. Rule m
|
||||
singleFieldSubscriptionsRule = OperationDefinitionRule $ \case
|
||||
Full.OperationDefinition Full.Subscription name' _ _ rootFields location' -> do
|
||||
groupedFieldSet <- evalStateT (collectFields rootFields) HashSet.empty
|
||||
case HashSet.size groupedFieldSet of
|
||||
1 -> lift mempty
|
||||
_
|
||||
| Just name <- name' -> pure $ Error
|
||||
case HashSet.toList groupedFieldSet of
|
||||
[rootName]
|
||||
| Text.isPrefixOf "__" rootName -> makeError location' name'
|
||||
"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
|
||||
[ "Subscription \""
|
||||
, Text.unpack name
|
||||
, "\" must select only one top level field."
|
||||
, Text.unpack operationName
|
||||
, "\" must select "
|
||||
, errorLine
|
||||
]
|
||||
, locations = [location']
|
||||
}
|
||||
| otherwise -> pure $ Error
|
||||
{ message = errorMessage
|
||||
makeError location' Nothing errorLine = pure $ Error
|
||||
{ message = "Anonymous Subscription must select " <> errorLine
|
||||
, locations = [location']
|
||||
}
|
||||
_ -> lift mempty
|
||||
where
|
||||
errorMessage =
|
||||
"Anonymous Subscription must select only one top level field."
|
||||
collectFields = foldM forEach HashSet.empty
|
||||
forEach accumulator = \case
|
||||
Full.FieldSelection fieldSelection -> forField accumulator fieldSelection
|
||||
@ -856,8 +859,8 @@ knownArgumentNamesRule = ArgumentsRule fieldRule directiveRule
|
||||
, "\"."
|
||||
]
|
||||
|
||||
-- | GraphQL servers define what directives they support. For each usage of a
|
||||
-- directive, the directive must be available on that server.
|
||||
-- | GraphQL services define what directives they support. For each usage of a
|
||||
-- directive, the directive must be available on that service.
|
||||
knownDirectiveNamesRule :: Rule m
|
||||
knownDirectiveNamesRule = DirectivesRule $ const $ \directives' -> do
|
||||
definitions' <- asks $ Schema.directives . schema
|
||||
@ -909,9 +912,9 @@ knownInputFieldNamesRule = ValueRule go constGo
|
||||
, "\"."
|
||||
]
|
||||
|
||||
-- | GraphQL servers define what directives they support and where they support
|
||||
-- | GraphQL services define what directives they support and where they support
|
||||
-- them. For each usage of a directive, the directive must be used in a location
|
||||
-- that the server has declared support for.
|
||||
-- that the service has declared support for.
|
||||
directivesInValidLocationsRule :: Rule m
|
||||
directivesInValidLocationsRule = DirectivesRule directivesRule
|
||||
where
|
||||
|
@ -94,7 +94,7 @@ dogType = ObjectType "Dog" Nothing [petType] $ HashMap.fromList
|
||||
, ("nickname", nicknameResolver)
|
||||
, ("barkVolume", barkVolumeResolver)
|
||||
, ("doesKnowCommand", doesKnowCommandResolver)
|
||||
, ("isHousetrained", isHousetrainedResolver)
|
||||
, ("isHouseTrained", isHouseTrainedResolver)
|
||||
, ("owner", ownerResolver)
|
||||
]
|
||||
where
|
||||
@ -105,10 +105,10 @@ dogType = ObjectType "Dog" Nothing [petType] $ HashMap.fromList
|
||||
$ In.Argument Nothing (In.NonNullEnumType dogCommandType) Nothing
|
||||
doesKnowCommandResolver = ValueResolver doesKnowCommandField
|
||||
$ pure $ Boolean True
|
||||
isHousetrainedField = Field Nothing (Out.NonNullScalarType boolean)
|
||||
isHouseTrainedField = Field Nothing (Out.NonNullScalarType boolean)
|
||||
$ HashMap.singleton "atOtherHomes"
|
||||
$ In.Argument Nothing (In.NamedScalarType boolean) Nothing
|
||||
isHousetrainedResolver = ValueResolver isHousetrainedField
|
||||
isHouseTrainedResolver = ValueResolver isHouseTrainedField
|
||||
$ pure $ Boolean True
|
||||
ownerField = Field Nothing (Out.NamedObjectType humanType) mempty
|
||||
ownerResolver = ValueResolver ownerField $ pure Null
|
||||
@ -206,6 +206,18 @@ spec =
|
||||
}
|
||||
in validate queryString `shouldContain` [expected]
|
||||
|
||||
it "rejects an introspection field as the subscription root" $
|
||||
let queryString = "subscription sub {\n\
|
||||
\ __typename\n\
|
||||
\}"
|
||||
expected = Error
|
||||
{ message =
|
||||
"Subscription \"sub\" must select exactly one top \
|
||||
\level field, which must not be an introspection field."
|
||||
, locations = [AST.Location 1 1]
|
||||
}
|
||||
in validate queryString `shouldContain` [expected]
|
||||
|
||||
it "rejects multiple subscription root fields coming from a fragment" $
|
||||
let queryString = "subscription sub {\n\
|
||||
\ ...multipleSubscriptions\n\
|
||||
@ -455,7 +467,7 @@ spec =
|
||||
it "rejects duplicate field arguments" $
|
||||
let queryString = "{\n\
|
||||
\ dog {\n\
|
||||
\ isHousetrained(atOtherHomes: true, atOtherHomes: true)\n\
|
||||
\ isHouseTrained(atOtherHomes: true, atOtherHomes: true)\n\
|
||||
\ }\n\
|
||||
\}"
|
||||
expected = Error
|
||||
@ -492,7 +504,7 @@ spec =
|
||||
it "rejects duplicate variables" $
|
||||
let queryString = "query houseTrainedQuery($atOtherHomes: Boolean, $atOtherHomes: Boolean) {\n\
|
||||
\ dog {\n\
|
||||
\ isHousetrained(atOtherHomes: $atOtherHomes)\n\
|
||||
\ isHouseTrained(atOtherHomes: $atOtherHomes)\n\
|
||||
\ }\n\
|
||||
\}"
|
||||
expected = Error
|
||||
@ -507,7 +519,7 @@ spec =
|
||||
it "rejects non-input types as variables" $
|
||||
let queryString = "query takesDogBang($dog: Dog!) {\n\
|
||||
\ dog {\n\
|
||||
\ isHousetrained(atOtherHomes: $dog)\n\
|
||||
\ isHouseTrained(atOtherHomes: $dog)\n\
|
||||
\ }\n\
|
||||
\}"
|
||||
expected = Error
|
||||
@ -522,12 +534,12 @@ spec =
|
||||
it "rejects undefined variables" $
|
||||
let queryString = "query variableIsNotDefinedUsedInSingleFragment {\n\
|
||||
\ dog {\n\
|
||||
\ ...isHousetrainedFragment\n\
|
||||
\ ...isHouseTrainedFragment\n\
|
||||
\ }\n\
|
||||
\}\n\
|
||||
\\n\
|
||||
\fragment isHousetrainedFragment on Dog {\n\
|
||||
\ isHousetrained(atOtherHomes: $atOtherHomes)\n\
|
||||
\fragment isHouseTrainedFragment on Dog {\n\
|
||||
\ isHouseTrained(atOtherHomes: $atOtherHomes)\n\
|
||||
\}"
|
||||
expected = Error
|
||||
{ message =
|
||||
@ -566,7 +578,7 @@ spec =
|
||||
it "rejects unused variables" $
|
||||
let queryString = "query variableUnused($atOtherHomes: Boolean) {\n\
|
||||
\ dog {\n\
|
||||
\ isHousetrained\n\
|
||||
\ isHouseTrained\n\
|
||||
\ }\n\
|
||||
\}"
|
||||
expected = Error
|
||||
@ -648,7 +660,7 @@ spec =
|
||||
it "rejects directive arguments missing in the definition" $
|
||||
let queryString = "{\n\
|
||||
\ dog {\n\
|
||||
\ isHousetrained(atOtherHomes: true) @include(unless: false, if: true)\n\
|
||||
\ isHouseTrained(atOtherHomes: true) @include(unless: false, if: true)\n\
|
||||
\ }\n\
|
||||
\}"
|
||||
expected = Error
|
||||
@ -663,7 +675,7 @@ spec =
|
||||
it "rejects undefined directives" $
|
||||
let queryString = "{\n\
|
||||
\ dog {\n\
|
||||
\ isHousetrained(atOtherHomes: true) @ignore(if: true)\n\
|
||||
\ isHouseTrained(atOtherHomes: true) @ignore(if: true)\n\
|
||||
\ }\n\
|
||||
\}"
|
||||
expected = Error
|
||||
@ -740,13 +752,13 @@ spec =
|
||||
let queryString = "{\n\
|
||||
\ dog {\n\
|
||||
\ doesKnowCommand(dogCommand: SIT)\n\
|
||||
\ doesKnowCommand: isHousetrained(atOtherHomes: true)\n\
|
||||
\ doesKnowCommand: isHouseTrained(atOtherHomes: true)\n\
|
||||
\ }\n\
|
||||
\}"
|
||||
expected = Error
|
||||
{ message =
|
||||
"Fields \"doesKnowCommand\" conflict because \
|
||||
\\"doesKnowCommand\" and \"isHousetrained\" are \
|
||||
\\"doesKnowCommand\" and \"isHouseTrained\" are \
|
||||
\different fields. Use different aliases on the \
|
||||
\fields to fetch both if this was intentional."
|
||||
, locations = [AST.Location 3 5, AST.Location 4 5]
|
||||
@ -761,13 +773,13 @@ spec =
|
||||
\ }\n\
|
||||
\ dog {\n\
|
||||
\ name\n\
|
||||
\ doesKnowCommand: isHousetrained(atOtherHomes: true)\n\
|
||||
\ doesKnowCommand: isHouseTrained(atOtherHomes: true)\n\
|
||||
\ }\n\
|
||||
\}"
|
||||
expected = Error
|
||||
{ message =
|
||||
"Fields \"doesKnowCommand\" conflict because \
|
||||
\\"doesKnowCommand\" and \"isHousetrained\" are \
|
||||
\\"doesKnowCommand\" and \"isHouseTrained\" are \
|
||||
\different fields. Use different aliases on the \
|
||||
\fields to fetch both if this was intentional."
|
||||
, locations = [AST.Location 4 5, AST.Location 8 5]
|
||||
@ -860,7 +872,7 @@ spec =
|
||||
it "rejects wrongly typed variable arguments" $
|
||||
let queryString = "query intCannotGoIntoBoolean($intArg: Int) {\n\
|
||||
\ dog {\n\
|
||||
\ isHousetrained(atOtherHomes: $intArg)\n\
|
||||
\ isHouseTrained(atOtherHomes: $intArg)\n\
|
||||
\ }\n\
|
||||
\}"
|
||||
expected = Error
|
||||
@ -875,7 +887,7 @@ spec =
|
||||
it "rejects values of incorrect types" $
|
||||
let queryString = "{\n\
|
||||
\ dog {\n\
|
||||
\ isHousetrained(atOtherHomes: 3)\n\
|
||||
\ isHouseTrained(atOtherHomes: 3)\n\
|
||||
\ }\n\
|
||||
\}"
|
||||
expected = Error
|
||||
|
Reference in New Issue
Block a user