Validate variable names are unique

This commit is contained in:
Eugen Wissner 2020-09-19 18:18:26 +02:00
parent 9a08aa5de7
commit 21a7d9cce4
9 changed files with 59 additions and 17 deletions

View File

@ -24,8 +24,15 @@ and this project adheres to
- `Validate.Validation.Path` was moved to `Error`. - `Validate.Validation.Path` was moved to `Error`.
### Added ### Added
- `Validate.Validation.Rule`: `SelectionRule`, `FieldRule`, `FragmentRule`, - `Validate.Validation.Rule` constructors:
`FragmentSpreadRule`, `ArgumentsRule`, `DirectivesRule` constructors. - `SelectionRule`
- `FieldRule`
- `FragmentRule`
- `FragmentSpreadRule`
- `ArgumentsRule`
- `DirectivesRule`
- `VariablesRule`
- `Validate.Rules`: - `Validate.Rules`:
- `fragmentsOnCompositeTypesRule` - `fragmentsOnCompositeTypesRule`
- `fragmentSpreadTargetDefinedRule` - `fragmentSpreadTargetDefinedRule`
@ -34,6 +41,7 @@ and this project adheres to
- `noFragmentCyclesRule` - `noFragmentCyclesRule`
- `uniqueArgumentNamesRule` - `uniqueArgumentNamesRule`
- `uniqueDirectiveNamesRule` - `uniqueDirectiveNamesRule`
- `uniqueVariableNamesRule`
- `AST.Document.Field`. - `AST.Document.Field`.
- `AST.Document.FragmentSpread`. - `AST.Document.FragmentSpread`.
- `AST.Document.InlineFragment`. - `AST.Document.InlineFragment`.

View File

@ -253,7 +253,8 @@ data ObjectField a = ObjectField Name a
-- --
-- Variables are usually passed along with the query, but not in the query -- Variables are usually passed along with the query, but not in the query
-- itself. They make queries reusable. -- itself. They make queries reusable.
data VariableDefinition = VariableDefinition Name Type (Maybe ConstValue) data VariableDefinition =
VariableDefinition Name Type (Maybe ConstValue) Location
deriving (Eq, Show) deriving (Eq, Show)
-- ** Type References -- ** Type References

View File

@ -95,7 +95,7 @@ variableDefinitions formatter
= parensCommas formatter $ variableDefinition formatter = parensCommas formatter $ variableDefinition formatter
variableDefinition :: Formatter -> VariableDefinition -> Lazy.Text variableDefinition :: Formatter -> VariableDefinition -> Lazy.Text
variableDefinition formatter (VariableDefinition var ty defaultValue') variableDefinition formatter (VariableDefinition var ty defaultValue' _)
= variable var = variable var
<> eitherFormat formatter ": " ":" <> eitherFormat formatter ": " ":"
<> type' ty <> type' ty

View File

@ -492,12 +492,13 @@ variableDefinitions = listOptIn parens variableDefinition
<?> "VariableDefinitions" <?> "VariableDefinitions"
variableDefinition :: Parser VariableDefinition variableDefinition :: Parser VariableDefinition
variableDefinition = VariableDefinition variableDefinition = label "VariableDefinition" $ do
<$> variable location <- getLocation
<* colon variableName <- variable
<*> type' colon
<*> defaultValue variableType <- type'
<?> "VariableDefinition" variableValue <- defaultValue
pure $ VariableDefinition variableName variableType variableValue location
variable :: Parser Name variable :: Parser Name
variable = dollar *> name <?> "Variable" variable = dollar *> name <?> "Variable"

View File

@ -180,7 +180,7 @@ coerceVariableValues types operationDefinition variableValues =
$ foldr forEach (Just HashMap.empty) variableDefinitions $ foldr forEach (Just HashMap.empty) variableDefinitions
where where
forEach variableDefinition coercedValues = do forEach variableDefinition coercedValues = do
let Full.VariableDefinition variableName variableTypeName defaultValue = let Full.VariableDefinition variableName variableTypeName defaultValue _ =
variableDefinition variableDefinition
let defaultValue' = constValue <$> defaultValue let defaultValue' = constValue <$> defaultValue
variableType <- lookupInputType variableTypeName types variableType <- lookupInputType variableTypeName types

View File

@ -125,11 +125,14 @@ inputValueDefinition rule (InputValueDefinition _ _ _ _ directives') =
directives rule directives' directives rule directives'
operationDefinition :: Rule m -> OperationDefinition -> Seq (RuleT m) operationDefinition :: Rule m -> OperationDefinition -> Seq (RuleT m)
operationDefinition (OperationDefinitionRule rule) operationDefinition' = operationDefinition rule operation
pure $ rule operationDefinition' | OperationDefinitionRule operationRule <- rule =
operationDefinition rule (SelectionSet selections _) = pure $ operationRule operation
selectionSet rule selections | VariablesRule variablesRule <- rule
operationDefinition rule (OperationDefinition _ _ _ directives' selections _) = , OperationDefinition _ _ variables _ _ _ <- operation =
pure $ variablesRule variables
| SelectionSet selections _ <- operation = selectionSet rule selections
| OperationDefinition _ _ _ directives' selections _ <- operation =
selectionSet rule selections >< directives rule directives' selectionSet rule selections >< directives rule directives'
fragmentDefinition :: Rule m -> FragmentDefinition -> Seq (RuleT m) fragmentDefinition :: Rule m -> FragmentDefinition -> Seq (RuleT m)

View File

@ -22,6 +22,7 @@ module Language.GraphQL.Validate.Rules
, uniqueDirectiveNamesRule , uniqueDirectiveNamesRule
, uniqueFragmentNamesRule , uniqueFragmentNamesRule
, uniqueOperationNamesRule , uniqueOperationNamesRule
, uniqueVariableNamesRule
) where ) where
import Control.Monad (foldM) import Control.Monad (foldM)
@ -64,6 +65,8 @@ specifiedRules =
, noFragmentCyclesRule , noFragmentCyclesRule
-- Directives. -- Directives.
, uniqueDirectiveNamesRule , uniqueDirectiveNamesRule
-- Variables.
, uniqueVariableNamesRule
] ]
-- | Definition must be OperationDefinition or FragmentDefinition. -- | Definition must be OperationDefinition or FragmentDefinition.
@ -492,3 +495,13 @@ filterDuplicates extract nodeType = lift
, Text.unpack $ fst $ extract directive , Text.unpack $ fst $ extract directive
, "\"." , "\"."
] ]
-- | If any operation defines more than one variable with the same name, it is
-- ambiguous and invalid. It is invalid even if the type of the duplicate
-- variable is the same.
uniqueVariableNamesRule :: forall m. Rule m
uniqueVariableNamesRule = VariablesRule
$ filterDuplicates extract "variable"
where
extract (VariableDefinition variableName _ _ location) =
(variableName, location)

View File

@ -43,6 +43,7 @@ data Rule m
| FieldRule (Field -> RuleT m) | FieldRule (Field -> RuleT m)
| ArgumentsRule (Field -> RuleT m) (Directive -> RuleT m) | ArgumentsRule (Field -> RuleT m) (Directive -> RuleT m)
| DirectivesRule ([Directive] -> RuleT m) | DirectivesRule ([Directive] -> RuleT m)
| VariablesRule ([VariableDefinition] -> RuleT m)
-- | Monad transformer used by the rules. -- | Monad transformer used by the rules.
type RuleT m = ReaderT (Validation m) Seq Error type RuleT m = ReaderT (Validation m) Seq Error

View File

@ -441,3 +441,18 @@ spec =
, locations = [AST.Location 3 23, AST.Location 3 39] , locations = [AST.Location 3 23, AST.Location 3 39]
} }
in validate queryString `shouldBe` Seq.singleton expected in validate queryString `shouldBe` Seq.singleton expected
it "rejects duplicate variables" $
let queryString = [r|
query houseTrainedQuery($atOtherHomes: Boolean, $atOtherHomes: Boolean) {
dog {
isHousetrained(atOtherHomes: $atOtherHomes)
}
}
|]
expected = Error
{ message =
"There can be only one variable named \"atOtherHomes\"."
, locations = [AST.Location 2 39, AST.Location 2 63]
}
in validate queryString `shouldBe` Seq.singleton expected