{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} module Data.Morpheus.Types.Internal.Validation.Validator ( Validator(..) , SelectionValidator , InputValidator , BaseValidator , runValidator , askSchema , askContext , askFragments , Constraint(..) , withScope , withScopeType , withScopePosition , askScopeTypeName , askScopePosition , withInputScope , inputMessagePrefix , Context(..) , InputSource(..) , InputContext(..) , SelectionContext(..) , renderInputPrefix , Target(..) , Prop(..) , Resolution ) where import Data.Semigroup ( (<>) , Semigroup(..) ) import Control.Monad.Trans.Reader ( ReaderT(..) , ask , withReaderT ) import Data.Text ( intercalate ) import Control.Monad.Trans.Class ( MonadTrans(..) ) -- MORPHEUS import Data.Morpheus.Types.Internal.Operation ( Failure(..) ) import Data.Morpheus.Types.Internal.Resolving ( Eventless ) import Data.Morpheus.Types.Internal.AST ( Name , Position , Message , GQLErrors , GQLError(..) , Fragments , Schema , FieldsDefinition(..) , TypeDefinition(..) , Argument(..) , Variable(..) , VariableDefinitions , RESOLVED , RAW , VALID ) data Prop = Prop { propName :: Name , propTypeName :: Name } deriving (Show) type Path = [Prop] renderPath :: Path -> Message renderPath [] = "" renderPath path = "in field \"" <> intercalate "." (fmap propName path) <> "\": " renderInputPrefix :: InputContext -> Message renderInputPrefix InputContext { inputPath , inputSource } = renderSource inputSource <> renderPath inputPath renderSource :: InputSource -> Message renderSource (SourceArgument Argument { argumentName }) = "Argument \"" <> argumentName <>"\" got invalid value. " renderSource (SourceVariable Variable { variableName }) = "Variable \"$" <> variableName <>"\" got invalid value. " data Context = Context { schema :: Schema , fragments :: Fragments , scopePosition :: Position , scopeTypeName :: Name , operationName :: Maybe Name , scopeSelectionName :: Name } deriving (Show) data InputContext = InputContext { inputSource :: InputSource , inputPath :: [Prop] } deriving (Show) data InputSource = SourceArgument (Argument RESOLVED) | SourceVariable (Variable RAW) deriving (Show) newtype SelectionContext = SelectionContext { variables :: VariableDefinitions VALID } deriving (Show) data Target = TARGET_OBJECT | TARGET_INPUT data Constraint (a :: Target) where OBJECT :: Constraint 'TARGET_OBJECT INPUT :: Constraint 'TARGET_INPUT -- UNION :: Constraint 'TARGET_UNION type family Resolution (a :: Target) type instance Resolution 'TARGET_OBJECT = (Name, FieldsDefinition) type instance Resolution 'TARGET_INPUT = TypeDefinition --type instance Resolution 'TARGET_UNION = DataUnion withInputScope :: Prop -> InputValidator a -> InputValidator a withInputScope prop = setContext update where update ctx@InputContext { inputPath = old } = ctx { inputPath = old <> [prop] } askContext :: Validator ctx ctx askContext = snd <$> Validator ask askSchema :: Validator ctx Schema askSchema = schema . fst <$> Validator ask askFragments :: Validator ctx Fragments askFragments = fragments . fst <$> Validator ask askScopeTypeName :: Validator ctx Name askScopeTypeName = scopeTypeName . fst <$> Validator ask askScopePosition :: Validator ctx Position askScopePosition = scopePosition . fst <$> Validator ask setContext :: (c' -> c) -> Validator c a -> Validator c' a setContext f = Validator . withReaderT ( \(x,y) -> (x,f y)) . _runValidator setGlobalContext :: (Context -> Context) -> Validator c a -> Validator c a setGlobalContext f = Validator . withReaderT ( \(x,y) -> (f x,y)) . _runValidator withScope :: Name -> Position -> Validator ctx a -> Validator ctx a withScope scopeTypeName scopePosition = setGlobalContext update where update ctx = ctx { scopeTypeName , scopePosition } withScopePosition :: Position -> Validator ctx a -> Validator ctx a withScopePosition scopePosition = setGlobalContext update where update ctx = ctx { scopePosition } withScopeType :: Name -> Validator ctx a -> Validator ctx a withScopeType scopeTypeName = setGlobalContext update where update ctx = ctx { scopeTypeName } inputMessagePrefix :: InputValidator Message inputMessagePrefix = renderInputPrefix <$> askContext runValidator :: Validator ctx a -> Context -> ctx -> Eventless a runValidator (Validator x) globalCTX ctx = runReaderT x (globalCTX,ctx) newtype Validator ctx a = Validator { _runValidator :: ReaderT (Context, ctx) Eventless a } deriving ( Functor , Applicative , Monad ) type BaseValidator = Validator () type SelectionValidator = Validator SelectionContext type InputValidator = Validator InputContext -- can be only used for internal errors instance Failure Message (Validator ctx) where failure inputMessage = do position <- askScopePosition failure [ GQLError { message = "INTERNAL: " <> inputMessage , locations = [position] } ] instance Failure GQLErrors (Validator ctx) where failure = Validator . lift . failure