morpheus-graphql-app-0.27.0: src/Data/Morpheus/App/Internal/Resolving/ResolveValue.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Data.Morpheus.App.Internal.Resolving.ResolveValue
( resolveRef,
resolveObject,
ResolverMapContext (..),
)
where
import Control.Monad.Except (MonadError (throwError))
import qualified Data.HashMap.Lazy as HM
import Data.Morpheus.App.Internal.Resolving.Batching
( CacheKey (..),
ResolverMapContext (..),
ResolverMapT,
buildCacheWith,
runResMapT,
useCached,
)
import Data.Morpheus.App.Internal.Resolving.ResolverState
( ResolverContext (..),
askFieldTypeName,
updateCurrentType,
)
import Data.Morpheus.App.Internal.Resolving.Types
( NamedResolver (..),
NamedResolverRef (..),
NamedResolverResult (..),
ObjectTypeResolver (..),
ResolverMap,
ResolverValue (..),
mkEnum,
mkUnion,
)
import Data.Morpheus.Error (subfieldsNotSelected)
import Data.Morpheus.Internal.Utils
( KeyOf (keyOf),
empty,
selectOr,
traverseCollection,
(<:>),
)
import Data.Morpheus.Types.Internal.AST
( GQLError,
Msg (msg),
ObjectEntry (ObjectEntry),
ScalarValue (..),
Selection (..),
SelectionContent (..),
SelectionSet,
TypeDefinition (..),
TypeName,
UnionTag (unionTagSelection),
VALID,
ValidValue,
Value (..),
internal,
unitFieldName,
unitTypeName,
unpackName,
)
import Relude hiding (empty)
scanRefs :: (MonadError GQLError m, MonadReader ResolverContext m) => SelectionContent VALID -> ResolverValue m -> m [(SelectionContent VALID, NamedResolverRef)]
scanRefs sel (ResList xs) = concat <$> traverse (scanRefs sel) xs
scanRefs sel (ResLazy x) = x >>= scanRefs sel
scanRefs sel (ResObject tyName obj) = withObject tyName (objectRefs obj) sel
scanRefs sel (ResRef ref) = pure . (sel,) <$> ref
scanRefs _ ResEnum {} = pure []
scanRefs _ ResNull = pure []
scanRefs _ ResScalar {} = pure []
objectRefs ::
( MonadError GQLError m,
MonadReader ResolverContext m
) =>
ObjectTypeResolver m ->
Maybe (SelectionSet VALID) ->
m [(SelectionContent VALID, NamedResolverRef)]
objectRefs _ Nothing = pure []
objectRefs dr (Just sel) = concat <$> traverse (fieldRefs dr) (toList sel)
fieldRefs ::
(MonadError GQLError m, MonadReader ResolverContext m) =>
ObjectTypeResolver m ->
Selection VALID ->
m [(SelectionContent VALID, NamedResolverRef)]
fieldRefs ObjectTypeResolver {..} currentSelection@Selection {..}
| selectionName == "__typename" = pure []
| otherwise = do
t <- askFieldTypeName selectionName
updateCurrentType t $
local (\ctx -> ctx {currentSelection}) $ do
x <- maybe (pure []) (fmap pure) (HM.lookup selectionName objectFields)
concat <$> traverse (scanRefs selectionContent) x
resolveSelection ::
( Monad m,
MonadReader ResolverContext m,
MonadError GQLError m
) =>
ResolverValue m ->
SelectionContent VALID ->
ResolverMapT m ValidValue
resolveSelection res selection = do
ctx <- ask
newRmap <- lift (scanRefs selection res >>= buildCache ctx)
local (const newRmap) (__resolveSelection res selection)
buildCache :: (MonadError GQLError m, MonadReader ResolverContext m) => ResolverMapContext m -> [(SelectionContent VALID, NamedResolverRef)] -> m (ResolverMapContext m)
buildCache ctx@(ResolverMapContext cache rmap) entries = (`ResolverMapContext` rmap) <$> buildCacheWith (resolveRefsCached ctx) cache entries
__resolveSelection ::
( Monad m,
MonadReader ResolverContext m,
MonadError GQLError m
) =>
ResolverValue m ->
SelectionContent VALID ->
ResolverMapT m ValidValue
__resolveSelection (ResLazy x) selection = lift x >>= (`resolveSelection` selection)
__resolveSelection (ResList xs) selection = List <$> traverse (`resolveSelection` selection) xs
__resolveSelection (ResObject tyName obj) sel = do
ctx <- ask
lift $ withObject tyName (resolveObject ctx obj) sel
__resolveSelection (ResEnum name) SelectionField = pure $ Scalar $ String $ unpackName name
__resolveSelection (ResEnum name) unionSel@UnionSelection {} = resolveSelection (mkUnion name [(unitFieldName, pure $ mkEnum unitTypeName)]) unionSel
__resolveSelection ResEnum {} _ = throwError (internal "wrong selection on enum value")
__resolveSelection ResNull _ = pure Null
__resolveSelection (ResScalar x) SelectionField = pure $ Scalar x
__resolveSelection ResScalar {} _ = throwError (internal "scalar Resolver should only receive SelectionField")
__resolveSelection (ResRef ref) sel = do
ctx <- ask
lift (ref >>= flip (resolveRef ctx) sel)
withObject ::
( MonadError GQLError m,
MonadReader ResolverContext m
) =>
Maybe TypeName ->
(Maybe (SelectionSet VALID) -> m value) ->
SelectionContent VALID ->
m value
withObject __typename f = updateCurrentType __typename . checkContent
where
checkContent (SelectionSet selection) = f (Just selection)
checkContent (UnionSelection interface unionSel) = do
typename <- asks (typeName . currentType)
selection <- selectOr (pure interface) (fx interface) typename unionSel
f selection
where
fx (Just x) y = Just <$> (x <:> unionTagSelection y)
fx Nothing y = pure $ Just $ unionTagSelection y
checkContent SelectionField = noEmptySelection
noEmptySelection :: (MonadError GQLError m, MonadReader ResolverContext m) => m value
noEmptySelection = do
sel <- asks currentSelection
throwError $ subfieldsNotSelected (selectionName sel) "" (selectionPosition sel)
resolveRef ::
( MonadError GQLError m,
MonadReader ResolverContext m
) =>
ResolverMapContext m ->
NamedResolverRef ->
SelectionContent VALID ->
m ValidValue
resolveRef rmap ref selection = resolveRefsCached rmap ref selection >>= toOne
toOne :: (MonadError GQLError f, Show a) => [a] -> f a
toOne [x] = pure x
toOne x = throwError (internal ("expected only one resolved value for " <> msg (show x :: String)))
resolveRefsCached ::
( MonadError GQLError m,
MonadReader ResolverContext m
) =>
ResolverMapContext m ->
NamedResolverRef ->
SelectionContent VALID ->
m [ValidValue]
resolveRefsCached ctx (NamedResolverRef name args) selection = do
let keys = map (CacheKey selection name) args
let cached = map resolveCached keys
let cachedMap = HM.fromList (mapMaybe unp cached)
notCachedMap <- runResMapT (resolveUncached name selection $ map fst $ filter (isNothing . snd) cached) ctx
traverse (useCached (cachedMap <> notCachedMap)) args
where
unp (_, Nothing) = Nothing
unp (x, Just y) = Just (x, y)
resolveCached key = (cachedArg key, HM.lookup key $ localCache ctx)
processResult ::
(MonadError GQLError m, MonadReader ResolverContext m) =>
TypeName ->
SelectionContent VALID ->
NamedResolverResult m ->
ResolverMapT m ValidValue
processResult typename selection (NamedObjectResolver res) = do
ctx <- ask
lift $ withObject (Just typename) (resolveObject ctx res) selection
processResult _ selection (NamedUnionResolver unionRef) = resolveSelection (ResRef $ pure unionRef) selection
processResult _ selection (NamedEnumResolver value) = resolveSelection (ResEnum value) selection
processResult _ selection NamedNullResolver = resolveSelection ResNull selection
processResult _ selection (NamedScalarResolver v) = resolveSelection (ResScalar v) selection
resolveUncached ::
( MonadError GQLError m,
MonadReader ResolverContext m
) =>
TypeName ->
SelectionContent VALID ->
[ValidValue] ->
ResolverMapT m (HashMap ValidValue ValidValue)
resolveUncached _ _ [] = pure empty
resolveUncached typename selection xs = do
rmap <- asks resolverMap
vs <- lift (getNamedResolverBy (NamedResolverRef typename xs) rmap) >>= traverse (processResult typename selection)
pure $ HM.fromList (zip xs vs)
getNamedResolverBy ::
(MonadError GQLError m) =>
NamedResolverRef ->
ResolverMap m ->
m [NamedResolverResult m]
getNamedResolverBy NamedResolverRef {..} = selectOr cantFoundError ((resolverArgument &) . resolverFun) resolverTypeName
where
cantFoundError = throwError ("Resolver Type " <> msg resolverTypeName <> "can't found")
resolveObject ::
( MonadReader ResolverContext m,
MonadError GQLError m
) =>
ResolverMapContext m ->
ObjectTypeResolver m ->
Maybe (SelectionSet VALID) ->
m ValidValue
resolveObject rmap drv sel = do
newCache <- objectRefs drv sel >>= buildCache rmap
Object <$> maybe (pure empty) (traverseCollection (resolver newCache)) sel
where
resolver cacheCTX currentSelection = do
t <- askFieldTypeName (selectionName currentSelection)
updateCurrentType t $
local (\ctx -> ctx {currentSelection}) $
ObjectEntry (keyOf currentSelection)
<$> runResMapT (runFieldResolver currentSelection drv) cacheCTX
runFieldResolver ::
( Monad m,
MonadReader ResolverContext m,
MonadError GQLError m
) =>
Selection VALID ->
ObjectTypeResolver m ->
ResolverMapT m ValidValue
runFieldResolver Selection {selectionName, selectionContent}
| selectionName == "__typename" =
const (Scalar . String . unpackName <$> lift (asks (typeName . currentType)))
| otherwise =
maybe (pure Null) (lift >=> (`resolveSelection` selectionContent))
. HM.lookup selectionName
. objectFields