graphql/src/Language/GraphQL/Validate.hs

355 lines
15 KiB
Haskell
Raw Normal View History

2020-07-20 21:29:12 +02:00
{- This Source Code Form is subject to the terms of the Mozilla Public License,
v. 2.0. If a copy of the MPL was not distributed with this file, You can
obtain one at https://mozilla.org/MPL/2.0/. -}
2020-09-17 10:33:37 +02:00
{-# LANGUAGE LambdaCase #-}
2020-09-28 07:06:15 +02:00
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
2020-07-20 21:29:12 +02:00
2020-07-24 21:34:31 +02:00
-- | GraphQL validator.
2020-07-20 21:29:12 +02:00
module Language.GraphQL.Validate
2020-09-28 07:06:15 +02:00
( Validation.Error(..)
2020-07-20 21:29:12 +02:00
, document
, module Language.GraphQL.Validate.Rules
) where
import Control.Monad (join)
import Control.Monad.Trans.Class (MonadTrans(..))
import Control.Monad.Trans.Reader (runReaderT)
import Data.Foldable (toList)
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HashMap
2020-07-20 21:29:12 +02:00
import Data.Sequence (Seq(..), (><), (|>))
import qualified Data.Sequence as Seq
2020-09-28 07:06:15 +02:00
import Language.GraphQL.AST.DirectiveLocation (DirectiveLocation(..))
import qualified Language.GraphQL.AST.DirectiveLocation as DirectiveLocation
2020-07-20 21:29:12 +02:00
import Language.GraphQL.AST.Document
import Language.GraphQL.Type.Internal
2020-09-28 07:06:15 +02:00
import qualified Language.GraphQL.Type.Definition as Definition
import qualified Language.GraphQL.Type.In as In
import qualified Language.GraphQL.Type.Out as Out
2020-09-28 07:06:15 +02:00
import Language.GraphQL.Type.Schema (Schema)
import qualified Language.GraphQL.Type.Schema as Schema
2020-07-20 21:29:12 +02:00
import Language.GraphQL.Validate.Rules
2020-09-28 07:06:15 +02:00
import Language.GraphQL.Validate.Validation (Validation(Validation))
import qualified Language.GraphQL.Validate.Validation as Validation
2020-07-20 21:29:12 +02:00
2020-09-28 07:06:15 +02:00
type ApplySelectionRule m a
= HashMap Name (Schema.Type m)
-> Validation.Rule m
-> Maybe (Out.Type m)
-> a
-> Seq (Validation.RuleT m)
type ApplyRule m a = Validation.Rule m -> a -> Seq (Validation.RuleT m)
2020-07-24 21:34:31 +02:00
-- | Validates a document and returns a list of found errors. If the returned
-- list is empty, the document is valid.
2020-09-28 07:06:15 +02:00
document :: forall m
. Schema m
-> [Validation.Rule m]
-> Document
-> Seq Validation.Error
2020-07-20 21:29:12 +02:00
document schema' rules' document' =
runReaderT reader context
2020-07-20 21:29:12 +02:00
where
context = Validation
2020-09-28 07:06:15 +02:00
{ Validation.ast = document'
, Validation.schema = schema'
, Validation.types = collectReferencedTypes schema'
, Validation.directives = allDirectives
2020-07-20 21:29:12 +02:00
}
2020-09-28 07:06:15 +02:00
allDirectives =
HashMap.union (Schema.directives schema') defaultDirectives
defaultDirectives = HashMap.fromList
[ ("skip", skipDirective)
, ("include", includeDirective)
, ("deprecated", deprecatedDirective)
]
includeDirective =
Schema.Directive includeDescription skipIncludeLocations includeArguments
includeArguments = HashMap.singleton "if"
$ In.Argument (Just "Included when true.") ifType Nothing
includeDescription = Just
"Directs the executor to include this field or fragment only when the \
\`if` argument is true."
skipDirective =
Schema.Directive skipDescription skipIncludeLocations skipArguments
skipArguments = HashMap.singleton "if"
$ In.Argument (Just "skipped when true.") ifType Nothing
ifType = In.NonNullScalarType Definition.boolean
skipDescription = Just
"Directs the executor to skip this field or fragment when the `if` \
\argument is true."
skipIncludeLocations =
[ ExecutableDirectiveLocation DirectiveLocation.Field
, ExecutableDirectiveLocation DirectiveLocation.FragmentSpread
, ExecutableDirectiveLocation DirectiveLocation.InlineFragment
]
deprecatedDirective =
Schema.Directive deprecatedDescription deprecatedLocations deprecatedArguments
reasonDescription = Just
"Explains why this element was deprecated, usually also including a \
\suggestion for how to access supported similar data. Formatted using \
\the Markdown syntax, as specified by \
\[CommonMark](https://commonmark.org/).'"
deprecatedArguments = HashMap.singleton "reason"
$ In.Argument reasonDescription reasonType
$ Just "No longer supported"
reasonType = In.NamedScalarType Definition.string
deprecatedDescription = Just
"Marks an element of a GraphQL schema as no longer supported."
deprecatedLocations =
[ TypeSystemDirectiveLocation DirectiveLocation.FieldDefinition
, TypeSystemDirectiveLocation DirectiveLocation.ArgumentDefinition
, TypeSystemDirectiveLocation DirectiveLocation.InputFieldDefinition
, TypeSystemDirectiveLocation DirectiveLocation.EnumValue
]
reader = do
rule' <- lift $ Seq.fromList rules'
join $ lift $ foldr (definition rule' context) Seq.empty document'
2020-09-28 07:06:15 +02:00
definition :: Validation.Rule m
-> Validation m
-> Definition
2020-09-28 07:06:15 +02:00
-> Seq (Validation.RuleT m)
-> Seq (Validation.RuleT m)
definition (Validation.DefinitionRule rule) _ definition' accumulator =
2020-09-17 10:33:37 +02:00
accumulator |> rule definition'
definition rule context (ExecutableDefinition definition') accumulator =
accumulator >< executableDefinition rule context definition'
definition rule _ (TypeSystemDefinition typeSystemDefinition' _) accumulator =
2020-09-17 10:33:37 +02:00
accumulator >< typeSystemDefinition rule typeSystemDefinition'
definition rule _ (TypeSystemExtension extension _) accumulator =
2020-09-17 10:33:37 +02:00
accumulator >< typeSystemExtension rule extension
2020-09-28 07:06:15 +02:00
typeSystemExtension :: forall m. ApplyRule m TypeSystemExtension
2020-09-17 10:33:37 +02:00
typeSystemExtension rule = \case
SchemaExtension extension -> schemaExtension rule extension
TypeExtension extension -> typeExtension rule extension
2020-09-28 07:06:15 +02:00
typeExtension :: forall m. ApplyRule m TypeExtension
2020-09-17 10:33:37 +02:00
typeExtension rule = \case
ScalarTypeExtension _ directives' -> directives rule directives'
ObjectTypeFieldsDefinitionExtension _ _ directives' fields ->
directives rule directives' >< foldMap (fieldDefinition rule) fields
ObjectTypeDirectivesExtension _ _ directives' -> directives rule directives'
ObjectTypeImplementsInterfacesExtension _ _ -> mempty
InterfaceTypeFieldsDefinitionExtension _ directives' fields ->
directives rule directives' >< foldMap (fieldDefinition rule) fields
InterfaceTypeDirectivesExtension _ directives' ->
directives rule directives'
UnionTypeUnionMemberTypesExtension _ directives' _ ->
directives rule directives'
UnionTypeDirectivesExtension _ directives' -> directives rule directives'
EnumTypeEnumValuesDefinitionExtension _ directives' values ->
directives rule directives' >< foldMap (enumValueDefinition rule) values
EnumTypeDirectivesExtension _ directives' -> directives rule directives'
InputObjectTypeInputFieldsDefinitionExtension _ directives' fields
-> directives rule directives'
>< foldMap (inputValueDefinition rule) fields
InputObjectTypeDirectivesExtension _ directives' ->
directives rule directives'
2020-09-28 07:06:15 +02:00
schemaExtension :: forall m. ApplyRule m SchemaExtension
2020-09-17 10:33:37 +02:00
schemaExtension rule = \case
SchemaOperationExtension directives' _ -> directives rule directives'
SchemaDirectivesExtension directives' -> directives rule directives'
2020-09-28 07:06:15 +02:00
executableDefinition :: forall m
. Validation.Rule m
-> Validation m
-> ExecutableDefinition
2020-09-28 07:06:15 +02:00
-> Seq (Validation.RuleT m)
executableDefinition rule context (DefinitionOperation operation) =
operationDefinition rule context operation
executableDefinition rule context (DefinitionFragment fragment) =
fragmentDefinition rule context fragment
2020-09-28 07:06:15 +02:00
typeSystemDefinition :: forall m. ApplyRule m TypeSystemDefinition
2020-09-17 10:33:37 +02:00
typeSystemDefinition rule = \case
SchemaDefinition directives' _ -> directives rule directives'
TypeDefinition typeDefinition' -> typeDefinition rule typeDefinition'
DirectiveDefinition _ _ arguments' _ -> argumentsDefinition rule arguments'
2020-09-17 10:33:37 +02:00
2020-09-28 07:06:15 +02:00
typeDefinition :: forall m. ApplyRule m TypeDefinition
2020-09-17 10:33:37 +02:00
typeDefinition rule = \case
ScalarTypeDefinition _ _ directives' -> directives rule directives'
ObjectTypeDefinition _ _ _ directives' fields ->
directives rule directives' >< foldMap (fieldDefinition rule) fields
InterfaceTypeDefinition _ _ directives' fields ->
directives rule directives' >< foldMap (fieldDefinition rule) fields
UnionTypeDefinition _ _ directives' _ -> directives rule directives'
EnumTypeDefinition _ _ directives' values ->
directives rule directives' >< foldMap (enumValueDefinition rule) values
InputObjectTypeDefinition _ _ directives' fields
-> directives rule directives'
<> foldMap (inputValueDefinition rule) fields
2020-09-28 07:06:15 +02:00
enumValueDefinition :: forall m. ApplyRule m EnumValueDefinition
2020-09-17 10:33:37 +02:00
enumValueDefinition rule (EnumValueDefinition _ _ directives') =
directives rule directives'
2020-09-28 07:06:15 +02:00
fieldDefinition :: forall m. ApplyRule m FieldDefinition
fieldDefinition rule (FieldDefinition _ _ arguments' _ directives') =
directives rule directives' >< argumentsDefinition rule arguments'
2020-09-17 10:33:37 +02:00
2020-09-28 07:06:15 +02:00
argumentsDefinition :: forall m. ApplyRule m ArgumentsDefinition
2020-09-17 10:33:37 +02:00
argumentsDefinition rule (ArgumentsDefinition definitions) =
foldMap (inputValueDefinition rule) definitions
2020-09-28 07:06:15 +02:00
inputValueDefinition :: forall m. ApplyRule m InputValueDefinition
2020-09-17 10:33:37 +02:00
inputValueDefinition rule (InputValueDefinition _ _ _ _ directives') =
directives rule directives'
2020-09-28 07:06:15 +02:00
operationDefinition :: forall m
. Validation.Rule m
-> Validation m
-> OperationDefinition
2020-09-28 07:06:15 +02:00
-> Seq (Validation.RuleT m)
operationDefinition rule context operation
2020-09-28 07:06:15 +02:00
| Validation.OperationDefinitionRule operationRule <- rule =
2020-09-19 18:18:26 +02:00
pure $ operationRule operation
2020-09-28 07:06:15 +02:00
| Validation.VariablesRule variablesRule <- rule
, OperationDefinition _ _ variables _ _ _ <- operation
= Seq.fromList (variableDefinition rule <$> variables)
|> variablesRule variables
| SelectionSet selections _ <- operation =
selectionSet types' rule (getRootType Query) selections
| OperationDefinition operationType _ _ directives' selections _ <- operation
= selectionSet types' rule (getRootType operationType) selections
>< directives rule directives'
where
2020-09-28 07:06:15 +02:00
types' = Validation.types context
getRootType Query =
Just $ Out.NamedObjectType $ Schema.query $ Validation.schema context
getRootType Mutation =
Out.NamedObjectType <$> Schema.mutation (Validation.schema context)
getRootType Subscription =
2020-09-28 07:06:15 +02:00
Out.NamedObjectType <$> Schema.subscription (Validation.schema context)
typeToOut :: forall m. Schema.Type m -> Maybe (Out.Type m)
typeToOut (Schema.ObjectType objectType) =
Just $ Out.NamedObjectType objectType
typeToOut (Schema.InterfaceType interfaceType) =
Just $ Out.NamedInterfaceType interfaceType
typeToOut (Schema.UnionType unionType) = Just $ Out.NamedUnionType unionType
typeToOut (Schema.EnumType enumType) = Just $ Out.NamedEnumType enumType
typeToOut (Schema.ScalarType scalarType) = Just $ Out.NamedScalarType scalarType
typeToOut _ = Nothing
2020-09-28 07:06:15 +02:00
variableDefinition :: forall m
. Validation.Rule m
-> VariableDefinition
-> Validation.RuleT m
variableDefinition (Validation.ValueRule _ rule) (VariableDefinition _ _ value _) =
maybe (lift mempty) rule value
variableDefinition _ _ = lift mempty
fragmentDefinition :: forall m
2020-09-28 07:06:15 +02:00
. Validation.Rule m
-> Validation m
-> FragmentDefinition
2020-09-28 07:06:15 +02:00
-> Seq (Validation.RuleT m)
fragmentDefinition (Validation.FragmentDefinitionRule rule) _ definition' =
pure $ rule definition'
fragmentDefinition rule context definition'
| FragmentDefinition _ typeCondition directives' selections _ <- definition'
2020-09-28 07:06:15 +02:00
, Validation.FragmentRule definitionRule _ <- rule
= applyToChildren typeCondition directives' selections
|> definitionRule definition'
| FragmentDefinition _ typeCondition directives' selections _ <- definition'
= applyToChildren typeCondition directives' selections
2020-09-07 22:01:49 +02:00
where
2020-09-28 07:06:15 +02:00
types' = Validation.types context
applyToChildren typeCondition directives' selections
= selectionSet types' rule (lookupType' typeCondition) selections
2020-09-17 10:33:37 +02:00
>< directives rule directives'
lookupType' = flip lookupType types'
2020-09-07 22:01:49 +02:00
lookupType :: forall m
. TypeCondition
-> HashMap Name (Schema.Type m)
-> Maybe (Out.Type m)
lookupType typeCondition types' = HashMap.lookup typeCondition types'
>>= typeToOut
2020-09-28 07:06:15 +02:00
selectionSet :: Traversable t => forall m. ApplySelectionRule m (t Selection)
selectionSet types' rule = foldMap . selection types' rule
2020-09-28 07:06:15 +02:00
selection :: forall m. ApplySelectionRule m Selection
selection types' rule objectType selection'
2020-09-28 07:06:15 +02:00
| Validation.SelectionRule selectionRule <- rule =
applyToChildren |> selectionRule objectType selection'
| otherwise = applyToChildren
2020-08-28 08:32:21 +02:00
where
applyToChildren =
case selection' of
FieldSelection field' -> field types' rule objectType field'
InlineFragmentSelection inlineFragment' ->
inlineFragment types' rule objectType inlineFragment'
FragmentSpreadSelection fragmentSpread' ->
2020-09-17 10:33:37 +02:00
fragmentSpread rule fragmentSpread'
2020-09-28 07:06:15 +02:00
field :: forall m. ApplySelectionRule m Field
field types' rule objectType field' = go field'
2020-09-17 10:33:37 +02:00
where
2020-09-28 07:06:15 +02:00
go (Field _ fieldName _ _ _ _)
| Validation.FieldRule fieldRule <- rule =
applyToChildren fieldName |> fieldRule objectType field'
| Validation.ArgumentsRule argumentsRule _ <- rule =
applyToChildren fieldName |> argumentsRule objectType field'
| otherwise = applyToChildren fieldName
typeFieldType (Out.Field _ type' _) = type'
applyToChildren fieldName =
let Field _ _ arguments' directives' selections _ = field'
fieldType = objectType
>>= fmap typeFieldType . lookupTypeField fieldName
in selectionSet types' rule fieldType selections
>< directives rule directives'
>< arguments rule arguments'
2020-09-28 07:06:15 +02:00
arguments :: forall m. ApplyRule m [Argument]
arguments = (.) Seq.fromList . fmap . argument
2020-09-28 07:06:15 +02:00
argument :: forall m. Validation.Rule m -> Argument -> Validation.RuleT m
argument (Validation.ValueRule rule _) (Argument _ (Node value _) _) =
rule value
argument _ _ = lift mempty
2020-09-28 07:06:15 +02:00
inlineFragment :: forall m. ApplySelectionRule m InlineFragment
inlineFragment types' rule objectType inlineFragment' = go inlineFragment'
2020-09-17 10:33:37 +02:00
where
go (InlineFragment optionalType directives' selections _)
2020-09-28 07:06:15 +02:00
| Validation.FragmentRule _ fragmentRule <- rule
= applyToChildren (refineTarget optionalType) directives' selections
|> fragmentRule inlineFragment'
| otherwise = applyToChildren (refineTarget optionalType) directives' selections
refineTarget (Just typeCondition) = lookupType typeCondition types'
refineTarget Nothing = objectType
applyToChildren objectType' directives' selections
= selectionSet types' rule objectType' selections
2020-09-17 10:33:37 +02:00
>< directives rule directives'
2020-09-28 07:06:15 +02:00
fragmentSpread :: forall m. ApplyRule m FragmentSpread
2020-09-17 10:33:37 +02:00
fragmentSpread rule fragmentSpread'@(FragmentSpread _ directives' _)
2020-09-28 07:06:15 +02:00
| Validation.FragmentSpreadRule fragmentRule <- rule =
2020-09-17 10:33:37 +02:00
applyToChildren |> fragmentRule fragmentSpread'
| otherwise = applyToChildren
where
applyToChildren = directives rule directives'
2020-09-28 07:06:15 +02:00
directives :: Traversable t => forall m. ApplyRule m (t Directive)
directives rule directives'
2020-09-28 07:06:15 +02:00
| Validation.DirectivesRule directivesRule <- rule =
applyToChildren |> directivesRule directiveList
| otherwise = applyToChildren
where
directiveList = toList directives'
applyToChildren = foldMap (directive rule) directiveList
2020-09-28 07:06:15 +02:00
directive :: forall m. ApplyRule m Directive
directive (Validation.ArgumentsRule _ argumentsRule) directive' =
pure $ argumentsRule directive'
directive rule (Directive _ arguments' _) = arguments rule arguments'