mu-graphql-0.5.0.0: src/Mu/GraphQL/Query/Run.hs
{-# language ConstraintKinds #-}
{-# language DataKinds #-}
{-# language FlexibleContexts #-}
{-# language FlexibleInstances #-}
{-# language GADTs #-}
{-# language MultiParamTypeClasses #-}
{-# language OverloadedLists #-}
{-# language OverloadedStrings #-}
{-# language PolyKinds #-}
{-# language RankNTypes #-}
{-# language ScopedTypeVariables #-}
{-# language TupleSections #-}
{-# language TypeApplications #-}
{-# language TypeFamilies #-}
{-# language TypeOperators #-}
{-# language UndecidableInstances #-}
{-# OPTIONS_GHC -fprint-explicit-foralls #-}
module Mu.GraphQL.Query.Run (
GraphQLApp
, runPipeline
, runSubscriptionPipeline
, runDocument
, runQuery
, runSubscription
-- * Typeclass to be able to run query handlers
, RunQueryFindHandler
) where
import Control.Concurrent.STM.TMQueue
import Control.Monad.Except (MonadError, runExceptT)
import Control.Monad.Writer
import qualified Data.Aeson as Aeson
import qualified Data.Aeson.Types as Aeson
import Data.Conduit
import Data.Conduit.Combinators (sinkList, yieldMany)
import Data.Conduit.TQueue
import qualified Data.HashMap.Strict as HM
import Data.Maybe
import qualified Data.Text as T
import Data.Typeable
import GHC.TypeLits
import qualified Language.GraphQL.AST as GQL
import Network.HTTP.Types.Header
import Unsafe.Coerce (unsafeCoerce)
import Mu.GraphQL.Query.Definition
import qualified Mu.GraphQL.Query.Introspection as Intro
import Mu.GraphQL.Query.Parse
import Mu.Rpc
import Mu.Schema
import Mu.Server
data GraphQLError
= GraphQLError ServerError [T.Text]
type GraphQLApp p qr mut sub m chn hs
= (ParseTypedDoc p qr mut sub, RunDocument p qr mut sub m chn hs)
runPipeline
:: forall qr mut sub p m chn hs. GraphQLApp p qr mut sub m chn hs
=> (forall a. m a -> ServerErrorIO a)
-> RequestHeaders
-> ServerT chn GQL.Field p m hs
-> Proxy qr -> Proxy mut -> Proxy sub
-> Maybe T.Text -> VariableMapC -> [GQL.Definition]
-> IO Aeson.Value
runPipeline f req svr _ _ _ opName vmap doc
= case parseDoc @qr @mut @sub opName vmap doc of
Left e -> pure $ singleErrValue e
Right (d :: Document p qr mut sub) -> do
(data_, errors) <- runWriterT (runDocument f req svr d)
case errors of
[] -> pure $ Aeson.object [ ("data", data_) ]
_ -> pure $ Aeson.object [ ("data", data_), ("errors", Aeson.listValue errValue errors) ]
runSubscriptionPipeline
:: forall qr mut sub p m chn hs. GraphQLApp p qr mut sub m chn hs
=> (forall a. m a -> ServerErrorIO a)
-> RequestHeaders
-> ServerT chn GQL.Field p m hs
-> Proxy qr -> Proxy mut -> Proxy sub
-> Maybe T.Text -> VariableMapC -> [GQL.Definition]
-> ConduitT Aeson.Value Void IO ()
-> IO ()
runSubscriptionPipeline f req svr _ _ _ opName vmap doc sink
= case parseDoc @qr @mut @sub opName vmap doc of
Left e
-> yieldSingleError e sink
Right (d :: Document p qr mut sub)
-> runDocumentSubscription f req svr d sink
singleErrValue :: T.Text -> Aeson.Value
singleErrValue e
= Aeson.object [ ("errors", Aeson.Array [
Aeson.object [ ("message", Aeson.String e) ] ])]
errValue :: GraphQLError -> Aeson.Value
errValue (GraphQLError (ServerError _ msg) path)
= Aeson.object [
("message", Aeson.String $ T.pack msg)
, ("path", Aeson.toJSON path)
]
yieldSingleError :: Monad m
=> T.Text -> ConduitM Aeson.Value Void m () -> m ()
yieldSingleError e sink =
runConduit $ yieldMany ([singleErrValue e] :: [Aeson.Value]) .| sink
yieldError :: Monad m
=> ServerError -> [T.Text]
-> ConduitM Aeson.Value Void m () -> m ()
yieldError e path sink = do
let val = Aeson.object [ ("errors", Aeson.listValue errValue [GraphQLError e path]) ]
runConduit $ yieldMany ([val] :: [Aeson.Value]) .| sink
class RunDocument (p :: Package')
(qr :: Maybe Symbol)
(mut :: Maybe Symbol)
(sub :: Maybe Symbol)
m chn hs where
runDocument ::
(forall a. m a -> ServerErrorIO a)
-> RequestHeaders
-> ServerT chn GQL.Field p m hs
-> Document p qr mut sub
-> WriterT [GraphQLError] IO Aeson.Value
runDocumentSubscription ::
(forall a. m a -> ServerErrorIO a)
-> RequestHeaders
-> ServerT chn GQL.Field p m hs
-> Document p qr mut sub
-> ConduitT Aeson.Value Void IO ()
-> IO ()
instance
( p ~ 'Package pname ss
, KnownSymbol qr
, RunQueryFindHandler m p hs chn ss (LookupService ss qr) hs
, MappingRight chn qr ~ ()
, KnownSymbol mut
, RunQueryFindHandler m p hs chn ss (LookupService ss mut) hs
, MappingRight chn mut ~ ()
, KnownSymbol sub
, RunQueryFindHandler m p hs chn ss (LookupService ss sub) hs
, MappingRight chn sub ~ ()
, Intro.Introspect p ('Just qr) ('Just mut) ('Just sub)
) => RunDocument p ('Just qr) ('Just mut) ('Just sub) m chn hs where
runDocument f req svr d
= let i = Intro.introspect (Proxy @p) (Proxy @('Just qr)) (Proxy @('Just mut)) (Proxy @('Just sub))
in case d of
QueryDoc q
-> runQuery f req i svr [] () q
MutationDoc q
-> runQuery f req i svr [] () q
SubscriptionDoc _
-> pure $ singleErrValue "cannot execute subscriptions in this wire"
runDocumentSubscription f req svr (SubscriptionDoc d)
= runSubscription f req svr [] () d
runDocumentSubscription f req svr d = yieldDocument f req svr d
instance
( p ~ 'Package pname ss
, KnownSymbol qr
, RunQueryFindHandler m p hs chn ss (LookupService ss qr) hs
, MappingRight chn qr ~ ()
, KnownSymbol mut
, RunQueryFindHandler m p hs chn ss (LookupService ss mut) hs
, MappingRight chn mut ~ ()
, Intro.Introspect p ('Just qr) ('Just mut) 'Nothing
) => RunDocument p ('Just qr) ('Just mut) 'Nothing m chn hs where
runDocument f req svr d
= let i = Intro.introspect (Proxy @p) (Proxy @('Just qr)) (Proxy @('Just mut)) (Proxy @'Nothing)
in case d of
QueryDoc q
-> runQuery f req i svr [] () q
MutationDoc q
-> runQuery f req i svr [] () q
runDocumentSubscription = yieldDocument
instance
( p ~ 'Package pname ss
, KnownSymbol qr
, RunQueryFindHandler m p hs chn ss (LookupService ss qr) hs
, MappingRight chn qr ~ ()
, KnownSymbol sub
, RunQueryFindHandler m p hs chn ss (LookupService ss sub) hs
, MappingRight chn sub ~ ()
, Intro.Introspect p ('Just qr) 'Nothing ('Just sub)
) => RunDocument p ('Just qr) 'Nothing ('Just sub) m chn hs where
runDocument f req svr d
= let i = Intro.introspect (Proxy @p) (Proxy @('Just qr)) (Proxy @'Nothing) (Proxy @('Just sub))
in case d of
QueryDoc q
-> runQuery f req i svr [] () q
SubscriptionDoc _
-> pure $ singleErrValue "cannot execute subscriptions in this wire"
runDocumentSubscription f req svr (SubscriptionDoc d)
= runSubscription f req svr [] () d
runDocumentSubscription f req svr d = yieldDocument f req svr d
instance
( p ~ 'Package pname ss
, KnownSymbol qr
, RunQueryFindHandler m p hs chn ss (LookupService ss qr) hs
, MappingRight chn qr ~ ()
, Intro.Introspect p ('Just qr) 'Nothing 'Nothing
) => RunDocument p ('Just qr) 'Nothing 'Nothing m chn hs where
runDocument f req svr d
= let i = Intro.introspect (Proxy @p) (Proxy @('Just qr)) (Proxy @'Nothing) (Proxy @'Nothing)
in case d of
QueryDoc q
-> runQuery f req i svr [] () q
runDocumentSubscription = yieldDocument
instance
( TypeError ('Text "you need to have a query in your schema")
) => RunDocument p 'Nothing mut sub m chn hs where
runDocument _ = error "this should never be called"
runDocumentSubscription _ = error "this should never be called"
yieldDocument ::
forall p qr mut sub m chn hs.
RunDocument p qr mut sub m chn hs
=> (forall a. m a -> ServerErrorIO a)
-> RequestHeaders
-> ServerT chn GQL.Field p m hs
-> Document p qr mut sub
-> ConduitT Aeson.Value Void IO ()
-> IO ()
yieldDocument f req svr doc sink = do
(data_, errors) <- runWriterT (runDocument @p @qr @mut @sub @m @chn @hs f req svr doc)
let (val :: Aeson.Value)
= case errors of
[] -> Aeson.object [ ("data", data_) ]
_ -> Aeson.object [ ("data", data_), ("errors", Aeson.listValue errValue errors) ]
runConduit $ yieldMany ([val] :: [Aeson.Value]) .| sink
runQuery
:: forall m p s pname ss hs chn inh.
( RunQueryFindHandler m p hs chn ss s hs
, p ~ 'Package pname ss
, inh ~ MappingRight chn (ServiceName s) )
=> (forall a. m a -> ServerErrorIO a)
-> RequestHeaders
-> Intro.Schema -> ServerT chn GQL.Field p m hs
-> [T.Text]
-> inh
-> ServiceQuery p s
-> WriterT [GraphQLError] IO Aeson.Value
runQuery f req sch whole@(Services ss) path = runQueryFindHandler f req sch whole path ss
runSubscription
:: forall m p s pname ss hs chn inh.
( RunQueryFindHandler m p hs chn ss s hs
, p ~ 'Package pname ss
, inh ~ MappingRight chn (ServiceName s) )
=> (forall a. m a -> ServerErrorIO a)
-> RequestHeaders
-> ServerT chn GQL.Field p m hs
-> [T.Text]
-> inh
-> OneMethodQuery p s
-> ConduitT Aeson.Value Void IO ()
-> IO ()
runSubscription f req whole@(Services ss) path
= runSubscriptionFindHandler f req whole path ss
class RunQueryFindHandler m p whole chn ss s hs where
runQueryFindHandler
:: ( p ~ 'Package pname wholess
, inh ~ MappingRight chn (ServiceName s) )
=> (forall a. m a -> ServerErrorIO a)
-> RequestHeaders
-> Intro.Schema -> ServerT chn GQL.Field p m whole
-> [T.Text]
-> ServicesT chn GQL.Field ss m hs
-> inh
-> ServiceQuery p s
-> WriterT [GraphQLError] IO Aeson.Value
runSubscriptionFindHandler
:: ( p ~ 'Package pname wholess
, inh ~ MappingRight chn (ServiceName s) )
=> (forall a. m a -> ServerErrorIO a)
-> RequestHeaders
-> ServerT chn GQL.Field p m whole
-> [T.Text]
-> ServicesT chn GQL.Field ss m hs
-> inh
-> OneMethodQuery p s
-> ConduitT Aeson.Value Void IO ()
-> IO ()
class RunQueryOnFoundHandler m p whole chn (s :: Service snm mnm anm (TypeRef snm)) hs where
type ServiceName s :: snm
runQueryOnFoundHandler
:: ( p ~ 'Package pname wholess
, inh ~ MappingRight chn (ServiceName s) )
=> (forall a. m a -> ServerErrorIO a)
-> RequestHeaders
-> Intro.Schema -> ServerT chn GQL.Field p m whole
-> [T.Text]
-> ServiceT chn GQL.Field s m hs
-> inh
-> ServiceQuery p s
-> WriterT [GraphQLError] IO Aeson.Value
runSubscriptionOnFoundHandler
:: ( p ~ 'Package pname wholess
, inh ~ MappingRight chn (ServiceName s) )
=> (forall a. m a -> ServerErrorIO a)
-> RequestHeaders
-> ServerT chn GQL.Field p m whole
-> [T.Text]
-> ServiceT chn GQL.Field s m hs
-> inh
-> OneMethodQuery p s
-> ConduitT Aeson.Value Void IO ()
-> IO ()
instance TypeError ('Text "Could not find handler for " ':<>: 'ShowType s)
=> RunQueryFindHandler m p whole chn '[] s '[] where
runQueryFindHandler _ = error "this should never be called"
runSubscriptionFindHandler _ = error "this should never be called"
instance {-# OVERLAPPABLE #-}
RunQueryFindHandler m p whole chn ss s hs
=> RunQueryFindHandler m p whole chn (other ': ss) s (h ': hs) where
runQueryFindHandler f req sch whole path (_ :<&>: that)
= runQueryFindHandler f req sch whole path that
runSubscriptionFindHandler f req whole path (_ :<&>: that)
= runSubscriptionFindHandler f req whole path that
instance {-# OVERLAPS #-}
(RunQueryOnFoundHandler m p whole chn s h)
=> RunQueryFindHandler m p whole chn (s ': ss) s (h ': hs) where
runQueryFindHandler f req sch whole path (s :<&>: _)
= runQueryOnFoundHandler f req sch whole path s
runSubscriptionFindHandler f req whole path (s :<&>: _)
= runSubscriptionOnFoundHandler f req whole path s
instance ( KnownName sname, RunMethod m p whole chn ('Service sname ms) ms h )
=> RunQueryOnFoundHandler m p whole chn ('Service sname ms) h where
type ServiceName ('Service sname ms) = sname
runQueryOnFoundHandler f req sch whole path (ProperSvc this) inh (ServiceQuery queries)
= Aeson.object . catMaybes <$> mapM runOneQuery queries
where
-- if we include the signature we have to write
-- an explicit type signature for 'runQueryFindHandler'
runOneQuery (OneMethodQuery nm args)
= runMethod f req whole (Proxy @('Service sname ms)) path nm inh this args
-- handle __typename
runOneQuery (TypeNameQuery nm)
= let realName = fromMaybe "__typename" nm
in pure $ Just (realName, Aeson.String $ T.pack $ nameVal (Proxy @sname))
-- handle __schema
runOneQuery (SchemaQuery nm ss)
= do let realName = fromMaybe "__schema" nm
Just . (realName, ) <$> runIntroSchema path sch ss
-- handle __type
runOneQuery (TypeQuery nm ty ss)
= do let realName = fromMaybe "__schema" nm
res <- runIntroType path sch (Intro.TypeRef ty) ss
case res of
Just val -> pure $ Just (realName, val)
Nothing -> do tell [GraphQLError
(ServerError Invalid
$ "cannot find type '" <> T.unpack ty <> "'")
path]
pure $ Just (realName, Aeson.Null)
-- subscriptions should only have one element
runSubscriptionOnFoundHandler f req whole path (ProperSvc this) inh (OneMethodQuery nm args) sink
= runMethodSubscription f req whole (Proxy @('Service sname ms)) path nm inh this args sink
runSubscriptionOnFoundHandler _ _ _ _ _ _ (TypeNameQuery nm) sink
= let realName = fromMaybe "__typename" nm
o = Aeson.object [(realName, Aeson.String $ T.pack $ nameVal (Proxy @sname))]
in runConduit $ yieldMany ([o] :: [Aeson.Value]) .| sink
runSubscriptionOnFoundHandler _ _ _ _ _ _ _ sink
= runConduit $ yieldMany
([singleErrValue "__schema and __type are not supported in subscriptions"]
:: [Aeson.Value])
.| sink
instance ( KnownName sname, RunUnion m p whole chn elts )
=> RunQueryOnFoundHandler m p whole chn ('OneOf sname elts) h where
type ServiceName ('OneOf sname elts) = sname
runQueryOnFoundHandler f req sch whole path (OneOfSvc this) inh (OneOfQuery queries)
= do res <- liftIO $ runExceptT $ f $ this inh
case res of
Left e -> tell [GraphQLError e path] >> pure Aeson.Null
Right x -> runUnion f req sch whole path queries x
runSubscriptionOnFoundHandler _ _ _ _ (OneOfSvc _) _ _ _
= error "this should never happen"
class RunUnion m p whole chn elts where
runUnion
:: (forall a. m a -> ServerErrorIO a)
-> RequestHeaders
-> Intro.Schema -> ServerT chn GQL.Field p m whole
-> [T.Text]
-> NP (ChosenOneOfQuery p) elts
-> UnionChoice chn elts
-> WriterT [GraphQLError] IO Aeson.Value
instance RunUnion m p whole chn '[] where
runUnion _ = error "this should never happen"
instance forall m p pname s sname whole ss chn elts.
( RunQueryFindHandler m p whole chn ss s whole
, p ~ 'Package pname ss
, s ~ LookupService ss sname
, ServiceName s ~ sname
, RunUnion m p whole chn elts )
=> RunUnion m p whole chn (sname ': elts) where
runUnion f req sch whole path
(ChosenOneOfQuery (Proxy :: Proxy sname) q :* rest)
choice@(UnionChoice (Proxy :: Proxy other) v)
= case eqT @sname @other of
Nothing -> runUnion f req sch whole path rest (unsafeCoerce choice)
Just Refl -> runQuery @m @('Package pname ss) @(LookupService ss sname) @pname @ss @whole f req sch whole path v q
class RunMethod m p whole chn s ms hs where
runMethod
:: ( p ~ 'Package pname wholess
, inh ~ MappingRight chn (ServiceName s) )
=> (forall a. m a -> ServerErrorIO a)
-> RequestHeaders
-> ServerT chn GQL.Field p m whole
-> Proxy s -> [T.Text] -> Maybe T.Text -> inh
-> HandlersT chn GQL.Field inh ms m hs
-> NS (ChosenMethodQuery p) ms
-> WriterT [GraphQLError] IO (Maybe (T.Text, Aeson.Value))
runMethodSubscription
:: ( p ~ 'Package pname wholess
, inh ~ MappingRight chn (ServiceName s) )
=> (forall a. m a -> ServerErrorIO a)
-> RequestHeaders
-> ServerT chn GQL.Field p m whole
-> Proxy s -> [T.Text] -> Maybe T.Text -> inh
-> HandlersT chn GQL.Field inh ms m hs
-> NS (ChosenMethodQuery p) ms
-> ConduitT Aeson.Value Void IO ()
-> IO ()
instance RunMethod m p whole chn s '[] '[] where
runMethod _ = error "this should never be called"
runMethodSubscription _ = error "this should never be called"
instance ( RunMethod m p whole chn s ms hs
, KnownName mname
, RunHandler m p whole chn args r h
, ReflectRpcInfo p s ('Method mname args r) )
=> RunMethod m p whole chn s ('Method mname args r ': ms) (h ': hs) where
-- handle normal methods
runMethod f req whole _ path nm inh (h :<||>: _) (Z (ChosenMethodQuery fld args ret))
= ((realName ,) <$>) <$> runHandler f req whole (path ++ [realName]) (h rpcInfo inh) args ret
where realName = fromMaybe (T.pack $ nameVal (Proxy @mname)) nm
rpcInfo = reflectRpcInfo (Proxy @p) (Proxy @s) (Proxy @('Method mname args r)) req fld
runMethod f req whole p path nm inh (_ :<||>: r) (S cont)
= runMethod f req whole p path nm inh r cont
runMethod _ _ _ _ _ _ _ _ _ = error "this should never happen"
-- handle subscriptions
runMethodSubscription f req whole _ path nm inh (h :<||>: _) (Z (ChosenMethodQuery fld args ret)) sink
= runHandlerSubscription f req whole (path ++ [realName]) (h rpcInfo inh) args ret sink
where realName = fromMaybe (T.pack $ nameVal (Proxy @mname)) nm
rpcInfo = reflectRpcInfo (Proxy @p) (Proxy @s) (Proxy @('Method mname args r)) req fld
runMethodSubscription f req whole p path nm inh (_ :<||>: r) (S cont) sink
= runMethodSubscription f req whole p path nm inh r cont sink
runMethodSubscription _ _ _ _ _ _ _ _ _ _ = error "this should never happen"
class Handles chn args r m h
=> RunHandler m p whole chn args r h where
runHandler
:: (forall a. m a -> ServerErrorIO a)
-> RequestHeaders
-> ServerT chn GQL.Field p m whole
-> [T.Text]
-> h
-> NP (ArgumentValue p) args
-> ReturnQuery p r
-> WriterT [GraphQLError] IO (Maybe Aeson.Value)
runHandlerSubscription
:: (forall a. m a -> ServerErrorIO a)
-> RequestHeaders
-> ServerT chn GQL.Field p m whole
-> [T.Text]
-> h
-> NP (ArgumentValue p) args
-> ReturnQuery p r
-> ConduitT Aeson.Value Void IO ()
-> IO ()
instance (ArgumentConversion chn ref t, RunHandler m p whole chn rest r h)
=> RunHandler m p whole chn ('ArgSingle aname ref ': rest) r (t -> h) where
runHandler f req whole path h (ArgumentValue one :* rest)
= runHandler f req whole path (h (convertArg (Proxy @chn) one)) rest
runHandlerSubscription f req whole path h (ArgumentValue one :* rest)
= runHandlerSubscription f req whole path (h (convertArg (Proxy @chn) one)) rest
instance ( MonadError ServerError m
, FromRef chn ref t
, ArgumentConversion chn ('ListRef ref) [t]
, RunHandler m p whole chn rest r h )
=> RunHandler m p whole chn ('ArgStream aname ref ': rest) r (ConduitT () t m () -> h) where
runHandler f req whole path h (ArgumentStream lst :* rest)
= let converted :: [t] = convertArg (Proxy @chn) lst
in runHandler f req whole path (h (yieldMany converted)) rest
runHandlerSubscription f req whole path h (ArgumentStream lst :* rest) sink
= let converted :: [t] = convertArg (Proxy @chn) lst
in runHandlerSubscription f req whole path (h (yieldMany converted)) rest sink
instance (MonadError ServerError m)
=> RunHandler m p whole chn '[] 'RetNothing (m ()) where
runHandler f _req _ path h Nil _ = do
res <- liftIO $ runExceptT (f h)
case res of
Right _ -> pure $ Just Aeson.Null
Left e -> tell [GraphQLError e path] >> pure Nothing
runHandlerSubscription f _req _ path h Nil _ sink = do
res <- liftIO $ runExceptT (f h)
case res of
Right _ -> runConduit $ yieldMany ([] :: [Aeson.Value]) .| sink
Left e -> yieldError e path sink
instance (MonadError ServerError m, ResultConversion m p whole chn r l)
=> RunHandler m p whole chn '[] ('RetSingle r) (m l) where
runHandler f req whole path h Nil (RSingle q) = do
res <- liftIO $ runExceptT (f h)
case res of
Right v -> convertResult f req whole path q v
Left e -> tell [GraphQLError e path] >> pure Nothing
runHandlerSubscription f req whole path h Nil (RSingle q) sink = do
res <- liftIO $ runExceptT (f h)
val <- case res of
Right v -> do
(data_, errors) <- runWriterT (convertResult f req whole path q v)
case errors of
[] -> pure $ Aeson.object [ ("data", fromMaybe Aeson.Null data_) ]
_ -> pure $ Aeson.object [ ("data", fromMaybe Aeson.Null data_)
, ("errors", Aeson.listValue errValue errors) ]
Left e -> pure $ Aeson.object [ ("errors", Aeson.listValue errValue [GraphQLError e path]) ]
runConduit $ yieldMany ([val] :: [Aeson.Value]) .| sink
instance (MonadIO m, MonadError ServerError m, ResultConversion m p whole chn r l)
=> RunHandler m p whole chn '[] ('RetStream r) (ConduitT l Void m () -> m ()) where
runHandler f req whole path h Nil (RStream q) = do
queue <- liftIO newTMQueueIO
res <- liftIO $ runExceptT $ f $ h (sinkTMQueue queue)
case res of
Right _ -> do
info <- runConduit $ sourceTMQueue queue .| sinkList
Just . Aeson.toJSON . catMaybes <$> traverse (convertResult f req whole path q) info
Left e -> tell [GraphQLError e []] >> pure Nothing
runHandlerSubscription f req whole path h Nil (RStream q) sink = do
res <- liftIO $ runExceptT $ f $ h
(transPipe liftIO (mapInputM convert (error "this should not be called") sink))
case res of
Right _ -> return ()
Left e -> yieldError e path sink
where
convert :: l -> IO Aeson.Value
convert v = do
(data_, errors) <- runWriterT (convertResult f req whole path q v)
case errors of
[] -> pure $ Aeson.object [ ("data", fromMaybe Aeson.Null data_) ]
_ -> pure $ Aeson.object [ ("data", fromMaybe Aeson.Null data_)
, ("errors", Aeson.listValue errValue errors) ]
class FromRef chn ref t
=> ArgumentConversion chn ref t where
convertArg :: Proxy chn -> ArgumentValue' p ref -> t
instance ArgumentConversion chn ('PrimitiveRef s) s where
convertArg _ (ArgPrimitive x) = x
instance FromSchema sch sty t
=> ArgumentConversion chn ('SchemaRef sch sty) t where
convertArg _ (ArgSchema x) = fromSchema x
instance ArgumentConversion chn ref t
=> ArgumentConversion chn ('ListRef ref) [t] where
convertArg p (ArgList x) = convertArg p <$> x
instance ArgumentConversion chn ref t
=> ArgumentConversion chn ('OptionalRef ref) (Maybe t) where
convertArg p (ArgOptional x) = convertArg p <$> x
class ToRef chn r l => ResultConversion m p whole chn r l where
convertResult :: (forall a. m a -> ServerErrorIO a)
-> RequestHeaders
-> ServerT chn GQL.Field p m whole
-> [T.Text]
-> ReturnQuery' p r
-> l -> WriterT [GraphQLError] IO (Maybe Aeson.Value)
instance Aeson.ToJSON t => ResultConversion m p whole chn ('PrimitiveRef t) t where
convertResult _ _ _ _ RetPrimitive = pure . Just . Aeson.toJSON
instance ( ToSchema sch l r
, RunSchemaQuery sch (sch :/: l) )
=> ResultConversion m p whole chn ('SchemaRef sch l) r where
convertResult _ _ _ _ (RetSchema r) t
= pure $ Just $ runSchemaQuery (toSchema' @_ @_ @sch @r t) r
instance ( MappingRight chn ref ~ t
, MappingRight chn (ServiceName svc) ~ t
, LookupService ss ref ~ svc
, RunQueryFindHandler m ('Package pname ss) whole chn ss svc whole)
=> ResultConversion m ('Package pname ss) whole chn ('ObjectRef ref) t where
convertResult f req whole path (RetObject q) h
= Just <$> runQuery @m @('Package pname ss) @(LookupService ss ref) f req
(error "cannot inspect schema inside a field")
whole path h q
instance ResultConversion m p whole chn r s
=> ResultConversion m p whole chn ('OptionalRef r) (Maybe s) where
convertResult _ _ _ _ _ Nothing
= pure Nothing
convertResult f req whole path (RetOptional q) (Just x)
= convertResult f req whole path q x
instance ResultConversion m p whole chn r s
=> ResultConversion m p whole chn ('ListRef r) [s] where
convertResult f req whole path (RetList q) xs
= Just . Aeson.toJSON . catMaybes <$> mapM (convertResult f req whole path q) xs
class RunSchemaQuery sch r where
runSchemaQuery
:: Term sch r
-> SchemaQuery sch r
-> Aeson.Value
instance ( Aeson.ToJSON (Term sch ('DEnum name choices)) )
=> RunSchemaQuery sch ('DEnum name choices) where
runSchemaQuery t _ = Aeson.toJSON t
instance ( KnownName rname, RunSchemaField sch fields )
=> RunSchemaQuery sch ('DRecord rname fields) where
runSchemaQuery (TRecord args) (QueryRecord rs)
= Aeson.object $ mapMaybe runOneQuery rs
where
runOneQuery (OneFieldQuery nm choice)
= let (val, fname) = runSchemaField args choice
realName = fromMaybe fname nm
in (realName,) <$> val
runOneQuery (TypeNameFieldQuery nm)
= let realName = fromMaybe "__typename" nm
-- add the 'R' because it's on return position
in pure (realName, Aeson.String $ T.pack $ nameVal (Proxy @rname) ++ "R")
class RunSchemaField sch args where
runSchemaField
:: NP (Field sch) args
-> NS (ChosenFieldQuery sch) args
-> (Maybe Aeson.Value, T.Text)
instance RunSchemaField sch '[] where
runSchemaField = error "this should never be called"
instance (KnownName fname, RunSchemaType sch t, RunSchemaField sch fs)
=> RunSchemaField sch ('FieldDef fname t ': fs) where
runSchemaField (Field x :* _) (Z (ChosenFieldQuery c))
= (runSchemaType x c, T.pack $ nameVal (Proxy @fname))
runSchemaField (_ :* xs) (S rest)
= runSchemaField xs rest
class RunSchemaType sch t where
runSchemaType
:: FieldValue sch t
-> ReturnSchemaQuery sch t
-> Maybe Aeson.Value
instance ( Aeson.ToJSON t )
=> RunSchemaType sch ('TPrimitive t) where
runSchemaType (FPrimitive x) _
= Just $ Aeson.toJSON x
instance RunSchemaType sch r
=> RunSchemaType sch ('TList r) where
runSchemaType (FList xs) (RetSchList r)
= Just . Aeson.toJSON $ mapMaybe (`runSchemaType` r) xs
instance RunSchemaType sch r
=> RunSchemaType sch ('TOption r) where
runSchemaType (FOption xs) (RetSchOptional r)
= xs >>= flip runSchemaType r
instance RunSchemaQuery sch (sch :/: l)
=> RunSchemaType sch ('TSchematic l) where
runSchemaType (FSchematic t) (RetSchSchema r)
= Just $ runSchemaQuery t r
runIntroSchema
:: [T.Text] -> Intro.Schema -> [GQL.Selection]
-> WriterT [GraphQLError] IO Aeson.Value
runIntroSchema path s@(Intro.Schema qr mut sub ts) ss
= do things <- catMaybes <$> traverse runOne ss
pure $ Aeson.object things
where
runOne (GQL.FieldSelection (GQL.Field alias nm _ _ innerss _))
= let realName :: T.Text = fromMaybe nm alias
path' = path ++ [realName]
in fmap (realName,) <$> case nm of
"description"
-> pure $ Just Aeson.Null
"directives"
-> pure $ Just $ Aeson.Array []
"queryType"
-> case qr >>= flip HM.lookup ts of
Nothing -> pure Nothing
Just ty -> runIntroType path' s ty innerss
"mutationType"
-> case mut >>= flip HM.lookup ts of
Nothing -> pure Nothing
Just ty -> runIntroType path' s ty innerss
"subscriptionType"
-> case sub >>= flip HM.lookup ts of
Nothing -> pure Nothing
Just ty -> runIntroType path' s ty innerss
"types"
-> do tys <- catMaybes <$> mapM (\t -> runIntroType path' s t innerss) (HM.elems ts)
pure $ Just $ Aeson.toJSON tys
_ -> do tell [GraphQLError
(ServerError Invalid
$ "field '" <> T.unpack nm <> "' was not found on type '__Schema'")
path]
pure Nothing
-- we do not support spreads here
runOne _ = pure Nothing
runIntroType
:: [T.Text] -> Intro.Schema -> Intro.Type -> [GQL.Selection]
-> WriterT [GraphQLError] IO (Maybe Aeson.Value)
runIntroType path s@(Intro.Schema _ _ _ ts) (Intro.TypeRef t) ss
= case HM.lookup t ts of
Nothing -> pure Nothing
Just ty -> runIntroType path s ty ss
runIntroType path s (Intro.Type k tnm fs vals posTys ofT) ss
= do things <- catMaybes <$> traverse runOne ss
pure $ Just $ Aeson.object things
where
runOne (GQL.FieldSelection (GQL.Field alias nm _ _ innerss _))
= let realName :: T.Text = fromMaybe nm alias
path' = path ++ [realName]
in fmap (realName,) <$> case (nm, innerss) of
("kind", [])
-> pure $ Just $ Aeson.String $ T.pack (show k)
("name", [])
-> pure $ Just $ maybe Aeson.Null Aeson.String tnm
("description", [])
-> pure $ Just Aeson.Null
("fields", _)
-> case k of
Intro.OBJECT
-> do things <- mapM (\f -> runIntroFields path' f innerss) fs
pure $ Just $ Aeson.toJSON things
_ -> pure $ Just Aeson.Null
("inputFields", _)
-> case k of
Intro.INPUT_OBJECT
-> do things <- mapM (\f -> runIntroFields path' f innerss) fs
pure $ Just $ Aeson.toJSON things
_ -> pure $ Just Aeson.Null
("enumValues", _)
-> do things <- mapM (\e -> runIntroEnums path' e innerss) vals
pure $ Just $ Aeson.toJSON things
("ofType", _)
-> case ofT of
Nothing -> pure $ Just Aeson.Null
Just o -> runIntroType path' s o innerss
-- unions and interfaces are not supported
("interfaces", _)
-> pure $ Just $ Aeson.Array []
("possibleTypes", _)
-> case k of
Intro.UNION
-> do res <- catMaybes <$>
mapM (\o -> runIntroType path' s o innerss) posTys
pure $ Just $ Aeson.toJSON res
_ -> pure $ Just Aeson.Null
_ -> do tell [GraphQLError
(ServerError Invalid
$ "field '" <> T.unpack nm <> "' was not found on type '__Type'")
path]
pure Nothing
-- we do not support spreads here
runOne _ = pure Nothing
runIntroFields
:: [T.Text] -> Intro.Field -> [GQL.Selection]
-> WriterT [GraphQLError] IO (Maybe Aeson.Value)
runIntroFields fpath fld fss
= do things <- catMaybes <$> traverse (runIntroField fpath fld) fss
pure $ Just $ Aeson.object things
runIntroField fpath (Intro.Field fnm fargs fty)
(GQL.FieldSelection (GQL.Field alias nm _ _ innerss _))
= let realName :: T.Text = fromMaybe nm alias
fpath' = fpath ++ [realName]
in fmap (realName,) <$> case (nm, innerss) of
("name", [])
-> pure $ Just $ Aeson.String fnm
("description", [])
-> pure $ Just Aeson.Null
("isDeprecated", [])
-> pure $ Just $ Aeson.Bool False
("deprecationReason", [])
-> pure $ Just Aeson.Null
-- this is used by __InputValue,
-- which is required when the field
-- is inside an INPUT_OBJECT
("defaultValue", [])
-> pure $ Just Aeson.Null
("type", _)
-> runIntroType fpath' s fty innerss
("args", _)
-> do things <- mapM (\i -> runIntroInputs fpath' i innerss) fargs
pure $ Just $ Aeson.toJSON things
_ -> do tell [GraphQLError
(ServerError Invalid
$ "field '" <> T.unpack nm <> "' was not found on type '__Field'")
fpath]
pure Nothing
-- we do not support spreads here
runIntroField _ _ _ = pure Nothing
runIntroEnums
:: [T.Text] -> Intro.EnumValue -> [GQL.Selection]
-> WriterT [GraphQLError] IO (Maybe Aeson.Value)
runIntroEnums epath enm ess
= do things <- catMaybes <$> traverse (runIntroEnum epath enm) ess
pure $ Just $ Aeson.object things
runIntroEnum epath (Intro.EnumValue enm)
(GQL.FieldSelection (GQL.Field alias nm _ _ innerss _))
= let realName :: T.Text = fromMaybe nm alias
in fmap (realName,) <$> case (nm, innerss) of
("name", [])
-> pure $ Just $ Aeson.String enm
("description", [])
-> pure $ Just Aeson.Null
("isDeprecated", [])
-> pure $ Just $ Aeson.Bool False
("deprecationReason", [])
-> pure $ Just Aeson.Null
_ -> do tell [GraphQLError
(ServerError Invalid
$ "field '" <> T.unpack nm <> "' was not found on type '__EnumValue'")
epath]
pure Nothing
-- we do not support spreads here
runIntroEnum _ _ _ = pure Nothing
runIntroInputs
:: [T.Text] -> Intro.Input -> [GQL.Selection]
-> WriterT [GraphQLError] IO (Maybe Aeson.Value)
runIntroInputs ipath inm iss
= do things <- catMaybes <$> traverse (runIntroInput ipath inm) iss
pure $ Just $ Aeson.object things
runIntroInput ipath (Intro.Input inm def ty)
(GQL.FieldSelection (GQL.Field alias nm _ _ innerss _))
= let realName :: T.Text = fromMaybe nm alias
ipath' = ipath ++ [realName]
in fmap (realName,) <$> case (nm, innerss) of
("name", [])
-> pure $ Just $ Aeson.String inm
("description", [])
-> pure $ Just Aeson.Null
("defaultValue", [])
-> pure $ Just $ maybe Aeson.Null Aeson.String def
("type", _)
-> runIntroType ipath' s ty innerss
_ -> do tell [GraphQLError
(ServerError Invalid
$ "field '" <> T.unpack nm <> "' was not found on type '__Field'")
ipath]
pure Nothing
-- we do not support spreads here
runIntroInput _ _ _ = pure Nothing