{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Data.Morpheus.Types.Internal.Validation
( Validator
, SelectionValidator
, InputValidator
, BaseValidator
, InputSource(..)
, Context(..)
, SelectionContext(..)
, runValidator
, askSchema
, askContext
, askFragments
, askFieldType
, askTypeMember
, selectRequired
, selectKnown
, Constraint(..)
, constraint
, withScope
, withScopeType
, withScopePosition
, askScopeTypeName
, selectWithDefaultValue
, askScopePosition
, askInputFieldType
, askInputMember
, startInput
, withInputScope
, inputMessagePrefix
, checkUnused
, Prop(..)
, constraintInputUnion
)
where
import Data.Semigroup ( (<>)
, Semigroup(..)
)
import Control.Monad.Trans.Reader ( ReaderT(..)
, ask
, withReaderT
)
import Data.Morpheus.Types.Internal.Operation
( Failure(..)
, Selectable
, selectBy
, selectOr
, KeyOf(..)
, member
, size
)
import Data.Morpheus.Types.Internal.Resolving
( Eventless )
import Data.Morpheus.Types.Internal.AST
( Name
, Position
, Message
, Ref(..)
, TypeRef(..)
, Fragments
, Schema
, FieldDefinition(..)
, FieldsDefinition(..)
, TypeDefinition(..)
, TypeContent(..)
, isInputDataType
, isFieldNullable
, Value(..)
, Object
, entryValue
, __inputname
)
import Data.Morpheus.Types.Internal.Validation.Validator
( Validator(..)
, Constraint(..)
, Target(..)
, InputSource(..)
, InputContext(..)
, Context(..)
, Prop(..)
, renderInputPrefix
, Resolution
, SelectionValidator
, InputValidator
, BaseValidator
, SelectionContext(..)
)
import Data.Morpheus.Types.Internal.Validation.Error
( MissingRequired(..)
, KindViolation(..)
, Unknown(..)
, InternalError(..)
, Unused(..)
)
getUnused :: (KeyOf b ,Selectable ca a) => ca -> [b] -> [b]
getUnused uses = filter (not . (`member` uses) . keyOf)
failOnUnused :: Unused b => [b] -> Validator ctx ()
failOnUnused x
| null x = return ()
| otherwise = do
(gctx,_) <- Validator ask
failure $ map (unused gctx) x
checkUnused :: (KeyOf b ,Selectable ca a, Unused b) => ca -> [b] -> Validator ctx ()
checkUnused uses = failOnUnused . getUnused uses
constraint
:: forall (a :: Target) inp ctx. KindViolation a inp
=> Constraint ( a :: Target)
-> inp
-> TypeDefinition
-> Validator ctx (Resolution a)
constraint OBJECT _ TypeDefinition { typeContent = DataObject { objectFields } , typeName }
= pure (typeName, objectFields)
constraint INPUT _ x | isInputDataType x = pure x
constraint target ctx _ = failure [kindViolation target ctx]
selectRequired
:: ( Selectable c value
, MissingRequired c ctx
)
=> Ref
-> c
-> Validator ctx value
selectRequired selector container
= do
(gctx,ctx) <- Validator ask
selectBy
[missingRequired gctx ctx selector container]
(keyOf selector)
container
selectWithDefaultValue
:: ( Selectable values value
, MissingRequired values ctx
)
=> value
-> FieldDefinition
-> values
-> Validator ctx value
selectWithDefaultValue
fallbackValue
field@FieldDefinition { fieldName }
values
= selectOr
handleNullable
pure
fieldName
values
where
handleNullable
| isFieldNullable field = pure fallbackValue
| otherwise = failSelection
failSelection = do
(gctx, ctx) <- Validator ask
failure [missingRequired gctx ctx (Ref fieldName (scopePosition gctx)) values]
selectKnown
:: ( Selectable c a
, Unknown c ctx
, KeyOf (UnknownSelector c)
)
=> UnknownSelector c
-> c
-> Validator ctx a
selectKnown selector lib
= do
(gctx, ctx) <- Validator ask
selectBy
(unknown gctx ctx lib selector)
(keyOf selector)
lib
askFieldType
:: FieldDefinition
-> SelectionValidator TypeDefinition
askFieldType field@FieldDefinition{ fieldType = TypeRef { typeConName } }
= do
schema <- askSchema
selectBy
[internalError field]
typeConName
schema
askTypeMember
:: Name
-> SelectionValidator (Name, FieldsDefinition)
askTypeMember
name
= askSchema
>>= selectOr notFound pure name
>>= constraintOBJECT
where
notFound = do
scopeType <- askScopeTypeName
failure $
"Type \"" <> name
<> "\" referenced by union \"" <> scopeType
<> "\" can't found in Schema."
constraintOBJECT TypeDefinition { typeName , typeContent } = con typeContent
where
con DataObject { objectFields } = pure (typeName, objectFields)
con _ = do
scopeType <- askScopeTypeName
failure $
"Type \"" <> typeName
<> "\" referenced by union \"" <> scopeType
<> "\" must be an OBJECT."
askInputFieldType
:: FieldDefinition
-> InputValidator TypeDefinition
askInputFieldType field@FieldDefinition{ fieldName , fieldType = TypeRef { typeConName } }
= askSchema
>>= selectBy
[internalError field]
typeConName
>>= constraintINPUT
where
constraintINPUT x
| isInputDataType x = pure x
| otherwise = failure $
"Type \"" <> typeName x
<> "\" referenced by field \"" <> fieldName
<> "\" must be an input type."
askInputMember
:: Name
-> InputValidator TypeDefinition
askInputMember
name
= askSchema
>>= selectOr notFound pure name
>>= constraintINPUT_OBJECT
where
typeInfo tName
= "Type \"" <> tName <> "\" referenced by inputUnion "
notFound = do
scopeType <- askScopeTypeName
failure $ typeInfo name <> scopeType <> "\" can't found in Schema."
constraintINPUT_OBJECT tyDef@TypeDefinition { typeName , typeContent } = con typeContent
where
con DataInputObject { } = pure tyDef
con _ = do
scopeType <- askScopeTypeName
failure $ typeInfo typeName <> "\"" <> scopeType <> "\" must be an INPUT_OBJECT."
startInput :: InputSource -> InputValidator a -> Validator ctx a
startInput inputSource
= setContext
$ const InputContext
{ inputSource
, inputPath = []
}
withInputScope :: Prop -> InputValidator a -> InputValidator a
withInputScope prop = setContext update
where
update ctx@InputContext { inputPath = old }
= ctx { inputPath = old <> [prop] }
runValidator :: Validator ctx a -> Context -> ctx -> Eventless a
runValidator (Validator x) globalCTX ctx = runReaderT x (globalCTX,ctx)
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 -> Ref -> Validator ctx a -> Validator ctx a
withScope scopeTypeName (Ref scopeSelectionName scopePosition) = setGlobalContext update
where
update ctx = ctx { scopeTypeName , scopePosition , scopeSelectionName}
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
constraintInputUnion
:: forall stage. [(Name, Bool)]
-> Object stage
-> Either Message (Name, Maybe (Value stage))
constraintInputUnion tags hm = do
(enum :: Value stage) <- entryValue <$> selectBy
("valid input union should contain \"" <> __inputname <> "\" and actual value")
__inputname
hm
tyName <- isPosibeInputUnion tags enum
case size hm of
1 -> pure (tyName, Nothing)
2 -> do
value <- entryValue <$> selectBy
("value for Union \""<> tyName <> "\" was not Provided.")
tyName
hm
pure (tyName , Just value)
_ -> failure ("input union can have only one variant." :: Message)
isPosibeInputUnion :: [(Name, Bool)] -> Value stage -> Either Message Name
isPosibeInputUnion tags (Enum name) = case lookup name tags of
Nothing -> failure (name <> " is not posible union type" :: Message)
_ -> pure name
isPosibeInputUnion _ _ = failure $ "\""<> __inputname <> "\" must be Enum"