forked from OSS/graphql
		
	Validate field selections on composite types
This commit is contained in:
		| @@ -27,7 +27,6 @@ and this project adheres to | ||||
| ### Added | ||||
| - `Validate.Validation.Rule` constructors: | ||||
|   - `SelectionRule` | ||||
|   - `FieldRule` | ||||
|   - `FragmentRule` | ||||
|   - `FragmentSpreadRule` | ||||
|   - `ArgumentsRule` | ||||
| @@ -47,6 +46,7 @@ and this project adheres to | ||||
|   - `noUndefinedVariablesRule` | ||||
|   - `noUnusedVariablesRule` | ||||
|   - `uniqueInputFieldNamesRule` | ||||
|   - `fieldsOnCorrectTypeRule` | ||||
| - `AST.Document.Field`. | ||||
| - `AST.Document.FragmentSpread`. | ||||
| - `AST.Document.InlineFragment`. | ||||
|   | ||||
| @@ -124,7 +124,7 @@ completeValue outputType@(Out.EnumBaseType enumType) _ (Type.Enum enum) = | ||||
|     let Type.EnumType _ _ enumMembers = enumType | ||||
|      in if HashMap.member enum enumMembers | ||||
|         then coerceResult outputType $ Enum enum | ||||
|         else addErrMsg "Value completion failed." | ||||
|         else addErrMsg "Enum value completion failed." | ||||
| completeValue (Out.ObjectBaseType objectType) fields result = | ||||
|     executeSelectionSet result objectType $ mergeSelectionSets fields | ||||
| completeValue (Out.InterfaceBaseType interfaceType) fields result | ||||
| @@ -134,7 +134,7 @@ completeValue (Out.InterfaceBaseType interfaceType) fields result | ||||
|         case concreteType of | ||||
|             Just objectType -> executeSelectionSet result objectType | ||||
|                 $ mergeSelectionSets fields | ||||
|             Nothing -> addErrMsg "Value completion failed." | ||||
|             Nothing -> addErrMsg "Interface value completion failed." | ||||
| completeValue (Out.UnionBaseType unionType) fields result | ||||
|     | Type.Object objectMap <- result = do | ||||
|         let abstractType = AbstractUnionType unionType | ||||
| @@ -142,7 +142,7 @@ completeValue (Out.UnionBaseType unionType) fields result | ||||
|         case concreteType of | ||||
|             Just objectType -> executeSelectionSet result objectType | ||||
|                 $ mergeSelectionSets fields | ||||
|             Nothing -> addErrMsg "Value completion failed." | ||||
|             Nothing -> addErrMsg "Union value completion failed." | ||||
| completeValue _ _ _ = addErrMsg "Value completion failed." | ||||
|  | ||||
| mergeSelectionSets :: MonadCatch m | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
|    obtain one at https://mozilla.org/MPL/2.0/. -} | ||||
|  | ||||
| {-# LANGUAGE ExplicitForAll #-} | ||||
| {-# LANGUAGE LambdaCase #-} | ||||
|  | ||||
| module Language.GraphQL.Type.Internal | ||||
|     ( AbstractType(..) | ||||
| @@ -12,10 +13,12 @@ module Language.GraphQL.Type.Internal | ||||
|     , instanceOf | ||||
|     , lookupInputType | ||||
|     , lookupTypeCondition | ||||
|     , lookupTypeField | ||||
|     ) where | ||||
|  | ||||
| import Data.HashMap.Strict (HashMap) | ||||
| import qualified Data.HashMap.Strict as HashMap | ||||
| import Data.Text (Text) | ||||
| import qualified Language.GraphQL.AST as Full | ||||
| import qualified Language.GraphQL.Type.Definition as Definition | ||||
| import qualified Language.GraphQL.Type.In as In | ||||
| @@ -55,41 +58,41 @@ collectReferencedTypes schema = | ||||
|     getField (Out.ValueResolver field _) = field | ||||
|     getField (Out.EventStreamResolver field _ _) = field | ||||
|     traverseInputType (In.InputObjectBaseType objectType) = | ||||
|         let (In.InputObjectType typeName _ inputFields) = objectType | ||||
|         let In.InputObjectType typeName _ inputFields = objectType | ||||
|             element = InputObjectType objectType | ||||
|             traverser = flip (foldr visitInputFields) inputFields | ||||
|          in collect traverser typeName element | ||||
|     traverseInputType (In.ListBaseType listType) = | ||||
|         traverseInputType listType | ||||
|     traverseInputType (In.ScalarBaseType scalarType) = | ||||
|         let (Definition.ScalarType typeName _) = scalarType | ||||
|         let Definition.ScalarType typeName _ = scalarType | ||||
|          in collect Prelude.id typeName (ScalarType scalarType) | ||||
|     traverseInputType (In.EnumBaseType enumType) = | ||||
|         let (Definition.EnumType typeName _ _) = enumType | ||||
|         let Definition.EnumType typeName _ _ = enumType | ||||
|          in collect Prelude.id typeName (EnumType enumType) | ||||
|     traverseOutputType (Out.ObjectBaseType objectType) = | ||||
|         traverseObjectType objectType | ||||
|     traverseOutputType (Out.InterfaceBaseType interfaceType) = | ||||
|         traverseInterfaceType interfaceType | ||||
|     traverseOutputType (Out.UnionBaseType unionType) = | ||||
|         let (Out.UnionType typeName _ types) = unionType | ||||
|         let Out.UnionType typeName _ types = unionType | ||||
|             traverser = flip (foldr traverseObjectType) types | ||||
|          in collect traverser typeName (UnionType unionType) | ||||
|     traverseOutputType (Out.ListBaseType listType) = | ||||
|         traverseOutputType listType | ||||
|     traverseOutputType (Out.ScalarBaseType scalarType) = | ||||
|         let (Definition.ScalarType typeName _) = scalarType | ||||
|         let Definition.ScalarType typeName _ = scalarType | ||||
|          in collect Prelude.id typeName (ScalarType scalarType) | ||||
|     traverseOutputType (Out.EnumBaseType enumType) = | ||||
|         let (Definition.EnumType typeName _ _) = enumType | ||||
|         let Definition.EnumType typeName _ _ = enumType | ||||
|          in collect Prelude.id typeName (EnumType enumType) | ||||
|     traverseObjectType objectType foundTypes = | ||||
|         let (Out.ObjectType typeName _ interfaces fields) = objectType | ||||
|         let Out.ObjectType typeName _ interfaces fields = objectType | ||||
|             element = ObjectType objectType | ||||
|             traverser = polymorphicTraverser interfaces (getField <$> fields) | ||||
|          in collect traverser typeName element foundTypes | ||||
|     traverseInterfaceType interfaceType foundTypes = | ||||
|         let (Out.InterfaceType typeName _ interfaces fields) = interfaceType | ||||
|         let Out.InterfaceType typeName _ interfaces fields = interfaceType | ||||
|             element = InterfaceType interfaceType | ||||
|             traverser = polymorphicTraverser interfaces fields | ||||
|          in collect traverser typeName element foundTypes | ||||
| @@ -161,3 +164,21 @@ lookupInputType (Full.TypeNonNull (Full.NonNullTypeNamed nonNull)) types  = | ||||
| lookupInputType (Full.TypeNonNull (Full.NonNullTypeList nonNull)) types | ||||
|     = In.NonNullListType | ||||
|     <$> lookupInputType nonNull types | ||||
|  | ||||
| lookupTypeField :: forall a. Text -> Out.Type a -> Maybe (Out.Type a) | ||||
| lookupTypeField fieldName = \case | ||||
|     Out.ObjectBaseType objectType -> | ||||
|         objectChild objectType | ||||
|     Out.InterfaceBaseType interfaceType -> | ||||
|         interfaceChild interfaceType | ||||
|     Out.ListBaseType listType -> lookupTypeField fieldName listType | ||||
|     _ -> Nothing | ||||
|   where | ||||
|     objectChild (Out.ObjectType _ _ _ resolvers) = | ||||
|         resolverType <$> HashMap.lookup fieldName resolvers | ||||
|     interfaceChild (Out.InterfaceType _ _ _ fields) = | ||||
|         fieldType <$> HashMap.lookup fieldName fields | ||||
|     resolverType (Out.ValueResolver objectField _) = fieldType objectField | ||||
|     resolverType (Out.EventStreamResolver objectField _ _) = | ||||
|         fieldType objectField | ||||
|     fieldType (Out.Field _ type' _) = type' | ||||
|   | ||||
| @@ -2,8 +2,8 @@ | ||||
|    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/. -} | ||||
|  | ||||
| {-# LANGUAGE ExplicitForAll #-} | ||||
| {-# LANGUAGE LambdaCase #-} | ||||
| {-# LANGUAGE ScopedTypeVariables #-} | ||||
|  | ||||
| -- | GraphQL validator. | ||||
| module Language.GraphQL.Validate | ||||
| @@ -16,14 +16,21 @@ 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 | ||||
| import Data.Sequence (Seq(..), (><), (|>)) | ||||
| import qualified Data.Sequence as Seq | ||||
| import Language.GraphQL.AST.Document | ||||
| import Language.GraphQL.Type.Internal | ||||
| import qualified Language.GraphQL.Type.Out as Out | ||||
| import Language.GraphQL.Type.Schema (Schema(..)) | ||||
| import qualified Language.GraphQL.Type.Schema as Schema | ||||
| import Language.GraphQL.Validate.Rules | ||||
| import Language.GraphQL.Validate.Validation | ||||
|  | ||||
| type ApplyRule m a = | ||||
|     HashMap Name (Schema.Type m) -> Rule m -> Maybe (Out.Type m) -> a -> Seq (RuleT m) | ||||
|  | ||||
| -- | Validates a document and returns a list of found errors. If the returned | ||||
| -- list is empty, the document is valid. | ||||
| document :: forall m. Schema m -> [Rule m] -> Document -> Seq Error | ||||
| @@ -37,16 +44,20 @@ document schema' rules' document' = | ||||
|         } | ||||
|     reader = do | ||||
|         rule' <- lift $ Seq.fromList rules' | ||||
|         join $ lift $ foldr (definition rule') Seq.empty document' | ||||
|         join $ lift $ foldr (definition rule' context) Seq.empty document' | ||||
|  | ||||
| definition :: Rule m -> Definition -> Seq (RuleT m) -> Seq (RuleT m) | ||||
| definition (DefinitionRule rule) definition' accumulator = | ||||
| definition :: Rule m | ||||
|     -> Validation m | ||||
|     -> Definition | ||||
|     -> Seq (RuleT m) | ||||
|     -> Seq (RuleT m) | ||||
| definition (DefinitionRule rule) _ definition' accumulator = | ||||
|     accumulator |> rule definition' | ||||
| definition rule (ExecutableDefinition executableDefinition') accumulator = | ||||
|     accumulator >< executableDefinition rule executableDefinition' | ||||
| definition rule (TypeSystemDefinition typeSystemDefinition' _) accumulator = | ||||
| definition rule context (ExecutableDefinition definition') accumulator = | ||||
|     accumulator >< executableDefinition rule context definition' | ||||
| definition rule _ (TypeSystemDefinition typeSystemDefinition' _) accumulator = | ||||
|     accumulator >< typeSystemDefinition rule typeSystemDefinition' | ||||
| definition rule (TypeSystemExtension extension _) accumulator = | ||||
| definition rule _ (TypeSystemExtension extension _) accumulator = | ||||
|     accumulator >< typeSystemExtension rule extension | ||||
|  | ||||
| typeSystemExtension :: Rule m -> TypeSystemExtension -> Seq (RuleT m) | ||||
| @@ -82,11 +93,14 @@ schemaExtension rule = \case | ||||
|     SchemaOperationExtension directives' _ -> directives rule directives' | ||||
|     SchemaDirectivesExtension directives' -> directives rule directives' | ||||
|  | ||||
| executableDefinition :: Rule m -> ExecutableDefinition -> Seq (RuleT m) | ||||
| executableDefinition rule (DefinitionOperation operation) = | ||||
|     operationDefinition rule operation | ||||
| executableDefinition rule (DefinitionFragment fragment) = | ||||
|     fragmentDefinition rule fragment | ||||
| executableDefinition :: Rule m | ||||
|     -> Validation m | ||||
|     -> ExecutableDefinition | ||||
|     -> Seq (RuleT m) | ||||
| executableDefinition rule context (DefinitionOperation operation) = | ||||
|     operationDefinition rule context operation | ||||
| executableDefinition rule context (DefinitionFragment fragment) = | ||||
|     fragmentDefinition rule context fragment | ||||
|  | ||||
| typeSystemDefinition :: Rule m -> TypeSystemDefinition -> Seq (RuleT m) | ||||
| typeSystemDefinition rule = \case | ||||
| @@ -124,60 +138,103 @@ inputValueDefinition :: Rule m -> InputValueDefinition -> Seq (RuleT m) | ||||
| inputValueDefinition rule (InputValueDefinition _ _ _ _ directives') = | ||||
|     directives rule directives' | ||||
|  | ||||
| operationDefinition :: Rule m -> OperationDefinition -> Seq (RuleT m) | ||||
| operationDefinition rule operation | ||||
| operationDefinition :: Rule m | ||||
|     -> Validation m | ||||
|     -> OperationDefinition | ||||
|     -> Seq (RuleT m) | ||||
| operationDefinition rule context operation | ||||
|     | OperationDefinitionRule operationRule <- rule = | ||||
|         pure $ operationRule operation | ||||
|     | VariablesRule variablesRule <- rule | ||||
|     , OperationDefinition _ _ variables _ _ _ <- operation | ||||
|         = Seq.fromList (variableDefinition rule <$> variables) | ||||
|         |> variablesRule variables | ||||
|     | SelectionSet selections _ <- operation = selectionSet rule selections | ||||
|     | OperationDefinition _ _ _ directives' selections _  <- operation = | ||||
|         selectionSet rule selections >< directives rule directives' | ||||
|     | SelectionSet selections _ <- operation = | ||||
|         selectionSet types' rule (getRootType Query) selections | ||||
|     | OperationDefinition operationType _ _ directives' selections _  <- operation | ||||
|         = selectionSet types' rule (getRootType operationType) selections | ||||
|         >< directives rule directives' | ||||
|   where | ||||
|     types' = types context | ||||
|     getRootType Query = Just $ Out.NamedObjectType $ query $ schema context | ||||
|     getRootType Mutation = Out.NamedObjectType <$> mutation (schema context) | ||||
|     getRootType Subscription = | ||||
|         Out.NamedObjectType <$> subscription (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 | ||||
|  | ||||
| variableDefinition :: Rule m -> VariableDefinition -> RuleT m | ||||
| variableDefinition (ValueRule _ rule) (VariableDefinition _ _ value _) = | ||||
|     maybe (lift mempty) rule value | ||||
| variableDefinition _ _ = lift mempty | ||||
|  | ||||
| fragmentDefinition :: Rule m -> FragmentDefinition -> Seq (RuleT m) | ||||
| fragmentDefinition (FragmentDefinitionRule rule) fragmentDefinition' = | ||||
|     pure $ rule fragmentDefinition' | ||||
| fragmentDefinition rule fragmentDefinition'@(FragmentDefinition _ _ directives' selections _) | ||||
|     | FragmentRule definitionRule _ <- rule = | ||||
|         applyToChildren |> definitionRule fragmentDefinition' | ||||
|     | otherwise = applyToChildren | ||||
| fragmentDefinition :: forall m | ||||
|     . Rule m | ||||
|     -> Validation m | ||||
|     -> FragmentDefinition | ||||
|     -> Seq (RuleT m) | ||||
| fragmentDefinition (FragmentDefinitionRule rule) _ definition' = | ||||
|     pure $ rule definition' | ||||
| fragmentDefinition rule context definition' | ||||
|     | FragmentDefinition _ typeCondition directives' selections _ <- definition' | ||||
|     , FragmentRule definitionRule _ <- rule | ||||
|         = applyToChildren typeCondition directives' selections | ||||
|         |> definitionRule definition' | ||||
|     | FragmentDefinition _ typeCondition directives' selections _ <- definition' | ||||
|         = applyToChildren typeCondition directives' selections | ||||
|   where | ||||
|     applyToChildren = selectionSet rule selections | ||||
|     types' = types context | ||||
|     applyToChildren typeCondition directives' selections | ||||
|         = selectionSet types' rule (lookupType' typeCondition) selections | ||||
|         >< directives rule directives' | ||||
|     lookupType' = flip lookupType types' | ||||
|  | ||||
| selectionSet :: Traversable t => Rule m -> t Selection -> Seq (RuleT m) | ||||
| selectionSet = foldMap . selection | ||||
| lookupType :: forall m | ||||
|     . TypeCondition | ||||
|     -> HashMap Name (Schema.Type m) | ||||
|     -> Maybe (Out.Type m) | ||||
| lookupType typeCondition types' = HashMap.lookup typeCondition types' | ||||
|     >>= typeToOut | ||||
|  | ||||
| selection :: Rule m -> Selection -> Seq (RuleT m) | ||||
| selection rule selection' | ||||
| selectionSet :: Traversable t => forall m. ApplyRule m (t Selection) | ||||
| selectionSet types' rule = foldMap . selection types' rule | ||||
|  | ||||
| selection :: forall m. ApplyRule m Selection | ||||
| selection types' rule objectType selection' | ||||
|     | SelectionRule selectionRule <- rule = | ||||
|         applyToChildren |> selectionRule selection' | ||||
|         applyToChildren |> selectionRule objectType selection' | ||||
|     | otherwise = applyToChildren | ||||
|   where | ||||
|     applyToChildren = | ||||
|         case selection' of | ||||
|             FieldSelection field' -> field rule field' | ||||
|             FieldSelection field' -> field types' rule objectType field' | ||||
|             InlineFragmentSelection inlineFragment' -> | ||||
|                 inlineFragment rule inlineFragment' | ||||
|                 inlineFragment types' rule objectType inlineFragment' | ||||
|             FragmentSpreadSelection fragmentSpread' -> | ||||
|                 fragmentSpread rule fragmentSpread' | ||||
|  | ||||
| field :: Rule m -> Field -> Seq (RuleT m) | ||||
| field rule field'@(Field _ _ arguments' directives' selections _) | ||||
|     | FieldRule fieldRule <- rule = applyToChildren |> fieldRule field' | ||||
|     | ArgumentsRule fieldRule _  <- rule = applyToChildren |> fieldRule field' | ||||
|     | otherwise = applyToChildren | ||||
| field :: forall m. ApplyRule m Field | ||||
| field types' rule objectType field' = go field' | ||||
|   where | ||||
|     applyToChildren = selectionSet rule selections | ||||
|         >< directives rule directives' | ||||
|         >< arguments rule arguments' | ||||
|     go (Field _ fieldName arguments' directives' selections _) | ||||
|         | ArgumentsRule fieldRule _  <- rule | ||||
|             = applyToChildren fieldName arguments' directives' selections | ||||
|             |> fieldRule field' | ||||
|         | otherwise = | ||||
|             applyToChildren fieldName arguments' directives' selections | ||||
|     applyToChildren fieldName arguments' directives' selections = | ||||
|         let child = objectType >>= lookupTypeField fieldName | ||||
|          in selectionSet types' rule child selections | ||||
|             >< directives rule directives' | ||||
|             >< arguments rule arguments' | ||||
|  | ||||
| arguments :: Rule m -> [Argument] -> Seq (RuleT m) | ||||
| arguments = (.) Seq.fromList . fmap . argument | ||||
| @@ -186,13 +243,18 @@ argument :: Rule m -> Argument -> RuleT m | ||||
| argument (ValueRule rule _) (Argument _ (Node value _) _) = rule value | ||||
| argument _ _ = lift mempty | ||||
|  | ||||
| inlineFragment :: Rule m -> InlineFragment -> Seq (RuleT m) | ||||
| inlineFragment rule inlineFragment'@(InlineFragment _ directives' selections _) | ||||
|     | FragmentRule _ fragmentRule <- rule = | ||||
|         applyToChildren |> fragmentRule inlineFragment' | ||||
|     | otherwise = applyToChildren | ||||
| inlineFragment :: forall m. ApplyRule m InlineFragment | ||||
| inlineFragment types' rule objectType inlineFragment' = go inlineFragment' | ||||
|   where | ||||
|     applyToChildren = selectionSet rule selections | ||||
|     go (InlineFragment optionalType directives' selections _) | ||||
|         | 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 | ||||
|         >< directives rule directives' | ||||
|  | ||||
| fragmentSpread :: Rule m -> FragmentSpread -> Seq (RuleT m) | ||||
|   | ||||
| @@ -10,6 +10,7 @@ | ||||
| -- | This module contains default rules defined in the GraphQL specification. | ||||
| module Language.GraphQL.Validate.Rules | ||||
|     ( executableDefinitionsRule | ||||
|     , fieldsOnCorrectTypeRule | ||||
|     , fragmentsOnCompositeTypesRule | ||||
|     , fragmentSpreadTargetDefinedRule | ||||
|     , fragmentSpreadTypeExistenceRule | ||||
| @@ -40,14 +41,16 @@ import Data.HashMap.Strict (HashMap) | ||||
| import Data.HashSet (HashSet) | ||||
| import qualified Data.HashSet as HashSet | ||||
| import Data.List (groupBy, sortBy, sortOn) | ||||
| import Data.Maybe (mapMaybe) | ||||
| import Data.Maybe (isJust, mapMaybe) | ||||
| import Data.Ord (comparing) | ||||
| import Data.Sequence (Seq(..)) | ||||
| import qualified Data.Sequence as Seq | ||||
| import Data.Text (Text) | ||||
| import qualified Data.Text as Text | ||||
| import Language.GraphQL.AST.Document | ||||
| import qualified Language.GraphQL.Type.Definition as Definition | ||||
| import Language.GraphQL.Type.Internal | ||||
| import qualified Language.GraphQL.Type.Out as Out | ||||
| import qualified Language.GraphQL.Type.Schema as Schema | ||||
| import Language.GraphQL.Validate.Validation | ||||
|  | ||||
| @@ -63,6 +66,8 @@ specifiedRules = | ||||
|     , singleFieldSubscriptionsRule | ||||
|     , loneAnonymousOperationRule | ||||
|     , uniqueOperationNamesRule | ||||
|     -- Fields | ||||
|     , fieldsOnCorrectTypeRule | ||||
|     -- Arguments. | ||||
|     , uniqueArgumentNamesRule | ||||
|     -- Fragments. | ||||
| @@ -297,7 +302,7 @@ isSpreadTarget _ _ = False | ||||
| -- for both named and inline fragments. If they are not defined in the schema, | ||||
| -- the query does not validate. | ||||
| fragmentSpreadTypeExistenceRule :: forall m. Rule m | ||||
| fragmentSpreadTypeExistenceRule = SelectionRule $ \case | ||||
| fragmentSpreadTypeExistenceRule = SelectionRule $ const $ \case | ||||
|     FragmentSpreadSelection fragmentSelection | ||||
|         | FragmentSpread fragmentName _ location <- fragmentSelection -> do | ||||
|             ast' <- asks ast | ||||
| @@ -672,3 +677,36 @@ uniqueInputFieldNamesRule = ValueRule (lift . go) (lift . constGo) | ||||
|         <> filterFieldDuplicates fields | ||||
|     constGo (ConstList values) = foldMap constGo values | ||||
|     constGo _ = mempty | ||||
|  | ||||
| -- | The target field of a field selection must be defined on the scoped type of | ||||
| -- the selection set. There are no limitations on alias names. | ||||
| fieldsOnCorrectTypeRule :: forall m. Rule m | ||||
| fieldsOnCorrectTypeRule = SelectionRule go | ||||
|   where | ||||
|     go (Just objectType) (FieldSelection fieldSelection) = | ||||
|        fieldRule objectType fieldSelection | ||||
|     go _ _ = lift mempty | ||||
|     fieldRule objectType (Field _ fieldName _ _ _ location) | ||||
|         | isJust (lookupTypeField fieldName objectType) = lift mempty | ||||
|         | otherwise = pure $ Error | ||||
|             { message = errorMessage fieldName objectType | ||||
|             , locations = [location] | ||||
|             } | ||||
|     errorMessage fieldName objectType = concat | ||||
|         [ "Cannot query field \"" | ||||
|         , Text.unpack fieldName | ||||
|         , "\" on type \"" | ||||
|         , Text.unpack $ outputTypeName objectType | ||||
|         , "\"." | ||||
|         ] | ||||
|     outputTypeName (Out.ObjectBaseType (Out.ObjectType typeName _ _ _)) = | ||||
|         typeName | ||||
|     outputTypeName (Out.InterfaceBaseType (Out.InterfaceType typeName _ _ _)) = | ||||
|         typeName | ||||
|     outputTypeName (Out.UnionBaseType (Out.UnionType typeName _ _)) = | ||||
|         typeName | ||||
|     outputTypeName (Out.ScalarBaseType (Definition.ScalarType typeName _)) = | ||||
|         typeName | ||||
|     outputTypeName (Out.EnumBaseType (Definition.EnumType typeName _ _)) = | ||||
|         typeName | ||||
|     outputTypeName (Out.ListBaseType wrappedType) = outputTypeName wrappedType | ||||
|   | ||||
| @@ -14,6 +14,7 @@ import Control.Monad.Trans.Reader (ReaderT) | ||||
| import Data.HashMap.Strict (HashMap) | ||||
| import Data.Sequence (Seq) | ||||
| import Language.GraphQL.AST.Document | ||||
| import qualified Language.GraphQL.Type.Out as Out | ||||
| import Language.GraphQL.Type.Schema (Schema) | ||||
| import qualified Language.GraphQL.Type.Schema as Schema | ||||
|  | ||||
| @@ -37,10 +38,9 @@ data Rule m | ||||
|     = DefinitionRule (Definition -> RuleT m) | ||||
|     | OperationDefinitionRule (OperationDefinition -> RuleT m) | ||||
|     | FragmentDefinitionRule (FragmentDefinition -> RuleT m) | ||||
|     | SelectionRule (Selection -> RuleT m) | ||||
|     | SelectionRule (Maybe (Out.Type m) -> Selection -> RuleT m) | ||||
|     | FragmentRule (FragmentDefinition -> RuleT m) (InlineFragment -> RuleT m) | ||||
|     | FragmentSpreadRule (FragmentSpread -> RuleT m) | ||||
|     | FieldRule (Field -> RuleT m) | ||||
|     | ArgumentsRule (Field -> RuleT m) (Directive -> RuleT m) | ||||
|     | DirectivesRule ([Directive] -> RuleT m) | ||||
|     | VariablesRule ([VariableDefinition] -> RuleT m) | ||||
|   | ||||
| @@ -9,8 +9,7 @@ module Language.GraphQL.ValidateSpec | ||||
|     ( spec | ||||
|     ) where | ||||
|  | ||||
| import Data.Sequence (Seq(..)) | ||||
| import qualified Data.Sequence as Seq | ||||
| import Data.Foldable (toList) | ||||
| import qualified Data.HashMap.Strict as HashMap | ||||
| import Data.Text (Text) | ||||
| import qualified Language.GraphQL.AST as AST | ||||
| @@ -18,7 +17,7 @@ import Language.GraphQL.Type | ||||
| import qualified Language.GraphQL.Type.In as In | ||||
| import qualified Language.GraphQL.Type.Out as Out | ||||
| import Language.GraphQL.Validate | ||||
| import Test.Hspec (Spec, describe, it, shouldBe) | ||||
| import Test.Hspec (Spec, describe, it, shouldBe, shouldContain) | ||||
| import Text.Megaparsec (parse) | ||||
| import Text.RawString.QQ (r) | ||||
|  | ||||
| @@ -30,11 +29,17 @@ schema = Schema | ||||
|     }  | ||||
|  | ||||
| queryType :: ObjectType IO | ||||
| queryType = ObjectType "Query" Nothing [] | ||||
|     $ HashMap.singleton "dog" dogResolver | ||||
| queryType = ObjectType "Query" Nothing [] $ HashMap.fromList | ||||
|     [ ("dog", dogResolver) | ||||
|     , ("findDog", findDogResolver) | ||||
|     ] | ||||
|   where | ||||
|     dogField = Field Nothing (Out.NamedObjectType dogType) mempty | ||||
|     dogResolver = ValueResolver dogField $ pure Null | ||||
|     findDogArguments = HashMap.singleton "complex" | ||||
|         $ In.Argument Nothing (In.NonNullInputObjectType dogDataType) Nothing | ||||
|     findDogField = Field Nothing (Out.NamedObjectType dogType) findDogArguments | ||||
|     findDogResolver = ValueResolver findDogField $ pure Null | ||||
|  | ||||
| dogCommandType :: EnumType | ||||
| dogCommandType = EnumType "DogCommand" Nothing $ HashMap.fromList | ||||
| @@ -72,6 +77,12 @@ dogType = ObjectType "Dog" Nothing [petType] $ HashMap.fromList | ||||
|     ownerField = Field Nothing (Out.NamedObjectType humanType) mempty | ||||
|     ownerResolver = ValueResolver ownerField $ pure Null | ||||
|  | ||||
| dogDataType :: InputObjectType | ||||
| dogDataType = InputObjectType "DogData" Nothing | ||||
|     $ HashMap.singleton "name" nameInputField | ||||
|   where | ||||
|     nameInputField = InputField Nothing (In.NonNullScalarType string) Nothing | ||||
|  | ||||
| sentientType :: InterfaceType IO | ||||
| sentientType = InterfaceType "Sentient" Nothing [] | ||||
|     $ HashMap.singleton "name" | ||||
| @@ -114,39 +125,14 @@ humanType = ObjectType "Human" Nothing [sentientType] $ HashMap.fromList | ||||
|         Field Nothing (Out.ListType $ Out.NonNullInterfaceType petType) mempty | ||||
|     petsResolver = ValueResolver petsField $ pure $ List [] | ||||
| {- | ||||
| catCommandType :: EnumType | ||||
| catCommandType = EnumType "CatCommand" Nothing $ HashMap.fromList | ||||
|     [ ("JUMP", EnumValue Nothing) | ||||
|     ] | ||||
|  | ||||
| catType :: ObjectType IO | ||||
| catType = ObjectType "Cat" Nothing [petType] $ HashMap.fromList | ||||
|     [ ("name", nameResolver) | ||||
|     , ("nickname", nicknameResolver) | ||||
|     , ("doesKnowCommand", doesKnowCommandResolver) | ||||
|     , ("meowVolume", meowVolumeResolver) | ||||
|     ] | ||||
|   where | ||||
|     nameField = Field Nothing (Out.NonNullScalarType string) mempty | ||||
|     nameResolver = ValueResolver nameField $ pure "Name" | ||||
|     nicknameField = Field Nothing (Out.NamedScalarType string) mempty | ||||
|     nicknameResolver = ValueResolver nicknameField $ pure "Nickname" | ||||
|     doesKnowCommandField = Field Nothing (Out.NonNullScalarType boolean) | ||||
|         $ HashMap.singleton "catCommand" | ||||
|         $ In.Argument Nothing (In.NonNullEnumType catCommandType) Nothing | ||||
|     doesKnowCommandResolver = ValueResolver doesKnowCommandField | ||||
|         $ pure $ Boolean True | ||||
|     meowVolumeField = Field Nothing (Out.NamedScalarType int) mempty | ||||
|     meowVolumeResolver = ValueResolver meowVolumeField $ pure $ Int 2 | ||||
|  | ||||
| catOrDogType :: UnionType IO | ||||
| catOrDogType = UnionType "CatOrDog" Nothing [catType, dogType] | ||||
| -} | ||||
| validate :: Text -> Seq Error | ||||
| validate :: Text -> [Error] | ||||
| validate queryString = | ||||
|     case parse AST.document "" queryString of | ||||
|         Left _ -> Seq.empty | ||||
|         Right ast -> document schema specifiedRules ast | ||||
|         Left _ -> [] | ||||
|         Right ast -> toList $ document schema specifiedRules ast | ||||
|  | ||||
| spec :: Spec | ||||
| spec = | ||||
| @@ -169,7 +155,7 @@ spec = | ||||
|                         "Definition must be OperationDefinition or FragmentDefinition." | ||||
|                     , locations = [AST.Location 9 15] | ||||
|                     } | ||||
|              in validate queryString `shouldBe` Seq.singleton expected | ||||
|                in validate queryString `shouldContain` [expected] | ||||
|  | ||||
|         it "rejects multiple subscription root fields" $ | ||||
|             let queryString = [r| | ||||
| @@ -186,7 +172,7 @@ spec = | ||||
|                         "Subscription sub must select only one top level field." | ||||
|                     , locations = [AST.Location 2 15] | ||||
|                     } | ||||
|              in validate queryString `shouldBe` Seq.singleton expected | ||||
|              in validate queryString `shouldContain` [expected] | ||||
|  | ||||
|         it "rejects multiple subscription root fields coming from a fragment" $ | ||||
|             let queryString = [r| | ||||
| @@ -207,7 +193,7 @@ spec = | ||||
|                         "Subscription sub must select only one top level field." | ||||
|                     , locations = [AST.Location 2 15] | ||||
|                     } | ||||
|              in validate queryString `shouldBe` Seq.singleton expected | ||||
|              in validate queryString `shouldContain` [expected] | ||||
|  | ||||
|         it "rejects multiple anonymous operations" $ | ||||
|             let queryString = [r| | ||||
| @@ -230,7 +216,7 @@ spec = | ||||
|                         "This anonymous operation must be the only defined operation." | ||||
|                     , locations = [AST.Location 2 15] | ||||
|                     } | ||||
|              in validate queryString `shouldBe` Seq.singleton expected | ||||
|              in validate queryString `shouldBe` [expected] | ||||
|  | ||||
|         it "rejects operations with the same name" $ | ||||
|             let queryString = [r| | ||||
| @@ -251,7 +237,7 @@ spec = | ||||
|                         "There can be only one operation named \"dogOperation\"." | ||||
|                     , locations = [AST.Location 2 15, AST.Location 8 15] | ||||
|                     } | ||||
|              in validate queryString `shouldBe` Seq.singleton expected | ||||
|              in validate queryString `shouldBe` [expected] | ||||
|  | ||||
|         it "rejects fragments with the same name" $ | ||||
|             let queryString = [r| | ||||
| @@ -276,7 +262,7 @@ spec = | ||||
|                         "There can be only one fragment named \"fragmentOne\"." | ||||
|                     , locations = [AST.Location 8 15, AST.Location 12 15] | ||||
|                     } | ||||
|              in validate queryString `shouldBe` Seq.singleton expected | ||||
|              in validate queryString `shouldBe` [expected] | ||||
|  | ||||
|         it "rejects the fragment spread without a target" $ | ||||
|             let queryString = [r| | ||||
| @@ -291,7 +277,7 @@ spec = | ||||
|                         "Fragment target \"undefinedFragment\" is undefined." | ||||
|                     , locations = [AST.Location 4 19] | ||||
|                     } | ||||
|              in validate queryString `shouldBe` Seq.singleton expected | ||||
|              in validate queryString `shouldBe` [expected] | ||||
|  | ||||
|         it "rejects fragment spreads without an unknown target type" $ | ||||
|             let queryString = [r| | ||||
| @@ -310,7 +296,7 @@ spec = | ||||
|                         \\"NotInSchema\" which doesn't exist in the schema." | ||||
|                     , locations = [AST.Location 4 19] | ||||
|                     } | ||||
|              in validate queryString `shouldBe` Seq.singleton expected | ||||
|              in validate queryString `shouldBe` [expected] | ||||
|  | ||||
|         it "rejects inline fragments without a target" $ | ||||
|             let queryString = [r| | ||||
| @@ -326,7 +312,7 @@ spec = | ||||
|                         \which doesn't exist in the schema." | ||||
|                     , locations = [AST.Location 3 17] | ||||
|                     } | ||||
|              in validate queryString `shouldBe` Seq.singleton expected | ||||
|              in validate queryString `shouldBe` [expected] | ||||
|  | ||||
|         it "rejects fragments on scalar types" $ | ||||
|             let queryString = [r| | ||||
| @@ -345,7 +331,7 @@ spec = | ||||
|                         \\"Int\"." | ||||
|                     , locations = [AST.Location 7 15] | ||||
|                     } | ||||
|              in validate queryString `shouldBe` Seq.singleton expected | ||||
|              in validate queryString `shouldContain` [expected] | ||||
|  | ||||
|         it "rejects inline fragments on scalar types" $ | ||||
|             let queryString = [r| | ||||
| @@ -361,7 +347,7 @@ spec = | ||||
|                         \\"Boolean\"." | ||||
|                     , locations = [AST.Location 3 17] | ||||
|                     } | ||||
|              in validate queryString `shouldBe` Seq.singleton expected | ||||
|              in validate queryString `shouldContain` [expected] | ||||
|  | ||||
|         it "rejects unused fragments" $ | ||||
|             let queryString = [r| | ||||
| @@ -380,7 +366,7 @@ spec = | ||||
|                         "Fragment \"nameFragment\" is never used." | ||||
|                     , locations = [AST.Location 2 15] | ||||
|                     } | ||||
|              in validate queryString `shouldBe` Seq.singleton expected | ||||
|              in validate queryString `shouldBe` [expected] | ||||
|  | ||||
|         it "rejects spreads that form cycles" $ | ||||
|             let queryString = [r| | ||||
| @@ -412,7 +398,7 @@ spec = | ||||
|                         \nameFragment)." | ||||
|                     , locations = [AST.Location 7 15] | ||||
|                     } | ||||
|              in validate queryString `shouldBe` Seq.fromList [error1, error2] | ||||
|              in validate queryString `shouldBe` [error1, error2] | ||||
|  | ||||
|         it "rejects duplicate field arguments" $ do | ||||
|             let queryString = [r| | ||||
| @@ -427,20 +413,22 @@ spec = | ||||
|                         "There can be only one argument named \"atOtherHomes\"." | ||||
|                     , locations = [AST.Location 4 34, AST.Location 4 54] | ||||
|                     } | ||||
|              in validate queryString `shouldBe` Seq.singleton expected | ||||
|              in validate queryString `shouldBe` [expected] | ||||
|  | ||||
|         it "rejects more than one directive per location" $ do | ||||
|             let queryString = [r| | ||||
|               query ($foo: Boolean = true, $bar: Boolean = false) { | ||||
|                 field @skip(if: $foo) @skip(if: $bar) | ||||
|                 dog @skip(if: $foo) @skip(if: $bar) { | ||||
|                   name | ||||
|                 } | ||||
|               } | ||||
|             |] | ||||
|                 expected = Error | ||||
|                     { message = | ||||
|                         "There can be only one directive named \"skip\"." | ||||
|                     , locations = [AST.Location 3 23, AST.Location 3 39] | ||||
|                     , locations = [AST.Location 3 21, AST.Location 3 37] | ||||
|                     } | ||||
|              in validate queryString `shouldBe` Seq.singleton expected | ||||
|              in validate queryString `shouldBe` [expected] | ||||
|  | ||||
|         it "rejects duplicate variables" $ | ||||
|             let queryString = [r| | ||||
| @@ -455,7 +443,7 @@ spec = | ||||
|                         "There can be only one variable named \"atOtherHomes\"." | ||||
|                     , locations = [AST.Location 2 39, AST.Location 2 63] | ||||
|                     } | ||||
|              in validate queryString `shouldBe` Seq.singleton expected | ||||
|              in validate queryString `shouldBe` [expected] | ||||
|  | ||||
|         it "rejects non-input types as variables" $ | ||||
|             let queryString = [r| | ||||
| @@ -470,7 +458,7 @@ spec = | ||||
|                         "Variable \"$dog\" cannot be non-input type \"Dog\"." | ||||
|                     , locations = [AST.Location 2 34] | ||||
|                     } | ||||
|              in validate queryString `shouldBe` Seq.singleton expected | ||||
|              in validate queryString `shouldBe` [expected] | ||||
|  | ||||
|         it "rejects undefined variables" $ | ||||
|             let queryString = [r| | ||||
| @@ -491,7 +479,7 @@ spec = | ||||
|                         \\"variableIsNotDefinedUsedInSingleFragment\"." | ||||
|                     , locations = [AST.Location 9 46] | ||||
|                     } | ||||
|              in validate queryString `shouldBe` Seq.singleton expected | ||||
|              in validate queryString `shouldBe` [expected] | ||||
|  | ||||
|         it "rejects unused variables" $ | ||||
|             let queryString = [r| | ||||
| @@ -507,7 +495,7 @@ spec = | ||||
|                         \\"variableUnused\"." | ||||
|                     , locations = [AST.Location 2 36] | ||||
|                     } | ||||
|              in validate queryString `shouldBe` Seq.singleton expected | ||||
|              in validate queryString `shouldBe` [expected] | ||||
|  | ||||
|         it "rejects duplicate fields in input objects" $ | ||||
|             let queryString = [r| | ||||
| @@ -520,4 +508,4 @@ spec = | ||||
|                         "There can be only one input field named \"name\"." | ||||
|                     , locations = [AST.Location 3 36, AST.Location 3 50] | ||||
|                     } | ||||
|              in validate queryString `shouldBe` Seq.singleton expected | ||||
|              in validate queryString `shouldBe` [expected] | ||||
|   | ||||
| @@ -108,20 +108,16 @@ spec = do | ||||
|  | ||||
|         it "embeds inline fragments without type" $ do | ||||
|             let sourceQuery = [r|{ | ||||
|               garment { | ||||
|                 circumference | ||||
|                 ... { | ||||
|                   size | ||||
|                 } | ||||
|               circumference | ||||
|               ... { | ||||
|                 size | ||||
|               } | ||||
|             }|] | ||||
|             actual <- graphql (toSchema "garment" $ garment "Hat") sourceQuery | ||||
|             actual <- graphql (toSchema "circumference" circumference) sourceQuery | ||||
|             let expected = HashMap.singleton "data" | ||||
|                     $ Aeson.object | ||||
|                         [ "garment" .= Aeson.object | ||||
|                             [ "circumference" .= (60 :: Int) | ||||
|                             , "size" .= ("L" :: Text) | ||||
|                             ] | ||||
|                         [ "circumference" .= (60 :: Int) | ||||
|                         , "size" .= ("L" :: Text) | ||||
|                         ] | ||||
|              in actual `shouldResolveTo` expected | ||||
|  | ||||
|   | ||||
| @@ -23,9 +23,9 @@ spec = describe "Star Wars Query Tests" $ do | ||||
|       it "R2-D2 hero" $ testQuery | ||||
|         [r| query HeroNameQuery { | ||||
|             hero { | ||||
|                 id | ||||
|             } | ||||
|               id | ||||
|             } | ||||
|           } | ||||
|         |] | ||||
|         $ Aeson.object | ||||
|             [ "data" .= Aeson.object | ||||
| @@ -35,13 +35,13 @@ spec = describe "Star Wars Query Tests" $ do | ||||
|       it "R2-D2 ID and friends" $ testQuery | ||||
|         [r| query HeroNameAndFriendsQuery { | ||||
|             hero { | ||||
|                 id | ||||
|               id | ||||
|               name | ||||
|               friends { | ||||
|                 name | ||||
|                 friends { | ||||
|                   name | ||||
|                 } | ||||
|             } | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|         |] | ||||
|         $ Aeson.object [ "data" .= Aeson.object [ | ||||
|             "hero" .= Aeson.object | ||||
| @@ -266,7 +266,7 @@ spec = describe "Star Wars Query Tests" $ do | ||||
|             query HeroNameQuery { | ||||
|               hero { | ||||
|                 name | ||||
|                   secretBackstory | ||||
|                 secretBackstory | ||||
|               } | ||||
|             } | ||||
|           |] | ||||
|   | ||||
| @@ -41,72 +41,88 @@ schema = Schema | ||||
|     droidFieldResolver = ValueResolver droidField droid | ||||
|  | ||||
| heroObject :: Out.ObjectType (Either SomeException) | ||||
| heroObject = Out.ObjectType "Human" Nothing [] $ HashMap.fromList | ||||
| heroObject = Out.ObjectType "Human" Nothing [characterType] $ HashMap.fromList | ||||
|     [ ("id", idFieldType) | ||||
|     , ("name", nameFieldType) | ||||
|     , ("friends", friendsFieldType) | ||||
|     , ("appearsIn", appearsInField) | ||||
|     , ("friends", friendsFieldResolver) | ||||
|     , ("appearsIn", appearsInFieldResolver) | ||||
|     , ("homePlanet", homePlanetFieldType) | ||||
|     , ("secretBackstory", secretBackstoryFieldType) | ||||
|     , ("__typename", typenameFieldType) | ||||
|     , ("secretBackstory", secretBackstoryFieldResolver) | ||||
|     , ("__typename", typenameFieldResolver) | ||||
|     ] | ||||
|   where | ||||
|     homePlanetFieldType | ||||
|       = ValueResolver (Out.Field Nothing (Out.NamedScalarType string) mempty) | ||||
|       $ idField "homePlanet" | ||||
|       $ defaultResolver "homePlanet" | ||||
|  | ||||
| droidObject :: Out.ObjectType (Either SomeException) | ||||
| droidObject = Out.ObjectType "Droid" Nothing [] $ HashMap.fromList | ||||
| droidObject = Out.ObjectType "Droid" Nothing [characterType] $ HashMap.fromList | ||||
|     [ ("id", idFieldType) | ||||
|     , ("name", nameFieldType) | ||||
|     , ("friends", friendsFieldType) | ||||
|     , ("appearsIn", appearsInField) | ||||
|     , ("friends", friendsFieldResolver) | ||||
|     , ("appearsIn", appearsInFieldResolver) | ||||
|     , ("primaryFunction", primaryFunctionFieldType) | ||||
|     , ("secretBackstory", secretBackstoryFieldType) | ||||
|     , ("__typename", typenameFieldType) | ||||
|     , ("secretBackstory", secretBackstoryFieldResolver) | ||||
|     , ("__typename", typenameFieldResolver) | ||||
|     ] | ||||
|   where | ||||
|     primaryFunctionFieldType | ||||
|         = ValueResolver (Out.Field Nothing (Out.NamedScalarType string) mempty) | ||||
|         $ idField "primaryFunction" | ||||
|         $ defaultResolver "primaryFunction" | ||||
|  | ||||
| typenameFieldType :: Resolver (Either SomeException) | ||||
| typenameFieldType | ||||
| typenameFieldResolver :: Resolver (Either SomeException) | ||||
| typenameFieldResolver | ||||
|     = ValueResolver (Out.Field Nothing (Out.NamedScalarType string) mempty) | ||||
|     $ idField "__typename" | ||||
|     $ defaultResolver "__typename" | ||||
|  | ||||
| idFieldType :: Resolver (Either SomeException) | ||||
| idFieldType | ||||
|     = ValueResolver (Out.Field Nothing (Out.NamedScalarType id) mempty) | ||||
|     $ idField "id" | ||||
| idFieldType = ValueResolver idField $ defaultResolver "id" | ||||
|  | ||||
| nameFieldType :: Resolver (Either SomeException) | ||||
| nameFieldType | ||||
|     = ValueResolver (Out.Field Nothing (Out.NamedScalarType string) mempty) | ||||
|     $ idField "name" | ||||
| nameFieldType = ValueResolver nameField $ defaultResolver "name" | ||||
|  | ||||
| friendsFieldType :: Resolver (Either SomeException) | ||||
| friendsFieldType | ||||
|     = ValueResolver (Out.Field Nothing fieldType mempty) | ||||
|     $ idField "friends" | ||||
| friendsFieldResolver :: Resolver (Either SomeException) | ||||
| friendsFieldResolver = ValueResolver friendsField $ defaultResolver "friends" | ||||
|  | ||||
| characterType :: InterfaceType (Either SomeException) | ||||
| characterType = InterfaceType "Character" Nothing [] $ HashMap.fromList | ||||
|     [ ("id", idField) | ||||
|     , ("name", nameField) | ||||
|     , ("friends", friendsField) | ||||
|     , ("appearsIn", appearsInField) | ||||
|     , ("secretBackstory", secretBackstoryField) | ||||
|     ] | ||||
|  | ||||
| idField :: Field (Either SomeException) | ||||
| idField = Field Nothing (Out.NonNullScalarType id) mempty | ||||
|  | ||||
| nameField :: Field (Either SomeException) | ||||
| nameField = Field Nothing (Out.NamedScalarType string) mempty | ||||
|  | ||||
| friendsField :: Field (Either SomeException) | ||||
| friendsField = Field Nothing friendsFieldType mempty | ||||
|   where | ||||
|     fieldType = Out.ListType $ Out.NamedObjectType droidObject | ||||
|     friendsFieldType = Out.ListType (Out.NamedInterfaceType characterType) | ||||
|  | ||||
| appearsInField :: Resolver (Either SomeException) | ||||
| appearsInField | ||||
|     = ValueResolver (Out.Field (Just description) fieldType mempty) | ||||
|     $ idField "appearsIn" | ||||
| appearsInField :: Field (Either SomeException) | ||||
| appearsInField = Field appearsInDescription appearsInFieldType mempty | ||||
|   where | ||||
|     fieldType = Out.ListType $ Out.NamedEnumType episodeEnum | ||||
|     description = "Which movies they appear in." | ||||
|     appearsInDescription = Just "Which movies they appear in." | ||||
|     appearsInFieldType = Out.ListType $ Out.NamedEnumType episodeEnum | ||||
|  | ||||
| secretBackstoryFieldType :: Resolver (Either SomeException) | ||||
| secretBackstoryFieldType = ValueResolver field secretBackstory | ||||
|   where | ||||
|     field = Out.Field Nothing (Out.NamedScalarType string) mempty | ||||
| secretBackstoryField :: Field (Either SomeException) | ||||
| secretBackstoryField = | ||||
|     Out.Field Nothing (Out.NamedScalarType string) mempty | ||||
|  | ||||
| idField :: Text -> Resolve (Either SomeException) | ||||
| idField f = do | ||||
| appearsInFieldResolver :: Resolver (Either SomeException) | ||||
| appearsInFieldResolver = ValueResolver appearsInField | ||||
|     $ defaultResolver "appearsIn" | ||||
|  | ||||
| secretBackstoryFieldResolver :: Resolver (Either SomeException) | ||||
| secretBackstoryFieldResolver = ValueResolver secretBackstoryField secretBackstory | ||||
|  | ||||
| defaultResolver :: Text -> Resolve (Either SomeException) | ||||
| defaultResolver f = do | ||||
|     v <- asks values | ||||
|     let (Object v') = v | ||||
|     pure $ v' HashMap.! f | ||||
|   | ||||
		Reference in New Issue
	
	Block a user