forked from OSS/graphql
Validate variable names are unique
This commit is contained in:
parent
9a08aa5de7
commit
21a7d9cce4
12
CHANGELOG.md
12
CHANGELOG.md
@ -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`.
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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"
|
||||||
|
@ -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
|
||||||
|
@ -125,12 +125,15 @@ 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 =
|
||||||
selectionSet rule selections >< directives rule directives'
|
pure $ variablesRule variables
|
||||||
|
| SelectionSet selections _ <- operation = selectionSet rule selections
|
||||||
|
| OperationDefinition _ _ _ directives' selections _ <- operation =
|
||||||
|
selectionSet rule selections >< directives rule directives'
|
||||||
|
|
||||||
fragmentDefinition :: Rule m -> FragmentDefinition -> Seq (RuleT m)
|
fragmentDefinition :: Rule m -> FragmentDefinition -> Seq (RuleT m)
|
||||||
fragmentDefinition (FragmentDefinitionRule rule) fragmentDefinition' =
|
fragmentDefinition (FragmentDefinitionRule rule) fragmentDefinition' =
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user