mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-17 12:31:52 +03:00
131 lines
4.5 KiB
Haskell
131 lines
4.5 KiB
Haskell
|
module Hasura.GraphQL.Execute
|
||
|
( GQExecPlan(..)
|
||
|
, getExecPlan
|
||
|
, execRemoteGQ
|
||
|
) where
|
||
|
|
||
|
import Control.Exception (try)
|
||
|
import Control.Lens
|
||
|
|
||
|
import qualified Data.ByteString.Lazy as BL
|
||
|
import qualified Data.CaseInsensitive as CI
|
||
|
import qualified Data.HashMap.Strict as Map
|
||
|
import qualified Data.HashSet as Set
|
||
|
import qualified Data.String.Conversions as CS
|
||
|
import qualified Data.Text as T
|
||
|
import qualified Language.GraphQL.Draft.Syntax as G
|
||
|
import qualified Network.HTTP.Client as HTTP
|
||
|
import qualified Network.HTTP.Types as N
|
||
|
import qualified Network.Wreq as Wreq
|
||
|
|
||
|
import Hasura.EncJSON
|
||
|
import Hasura.GraphQL.Schema
|
||
|
import Hasura.GraphQL.Transport.HTTP.Protocol
|
||
|
import Hasura.HTTP
|
||
|
import Hasura.Prelude
|
||
|
import Hasura.RQL.DDL.Headers
|
||
|
import Hasura.RQL.Types
|
||
|
|
||
|
import qualified Hasura.GraphQL.Validate as VQ
|
||
|
import qualified Hasura.GraphQL.Validate.Types as VT
|
||
|
|
||
|
data GQExecPlan
|
||
|
= GExPHasura !GCtx !VQ.RootSelSet
|
||
|
| GExPRemote !RemoteSchemaInfo !G.TypedOperationDefinition
|
||
|
|
||
|
getExecPlan
|
||
|
:: (MonadError QErr m)
|
||
|
=> UserInfo
|
||
|
-> SchemaCache
|
||
|
-> GraphQLRequest
|
||
|
-> m GQExecPlan
|
||
|
getExecPlan userInfo sc req = do
|
||
|
|
||
|
(gCtx, _) <- flip runStateT sc $ getGCtx (userRole userInfo) gCtxRoleMap
|
||
|
queryParts <- flip runReaderT gCtx $ VQ.getQueryParts req
|
||
|
|
||
|
let opDef = VQ.qpOpDef queryParts
|
||
|
topLevelNodes = getTopLevelNodes opDef
|
||
|
-- gather TypeLoc of topLevelNodes
|
||
|
typeLocs = gatherTypeLocs gCtx topLevelNodes
|
||
|
|
||
|
-- see if they are all the same
|
||
|
typeLoc <- assertSameLocationNodes typeLocs
|
||
|
|
||
|
case typeLoc of
|
||
|
VT.HasuraType ->
|
||
|
GExPHasura gCtx <$> runReaderT (VQ.validateGQ queryParts) gCtx
|
||
|
VT.RemoteType _ rsi ->
|
||
|
return $ GExPRemote rsi opDef
|
||
|
where
|
||
|
gCtxRoleMap = scGCtxMap sc
|
||
|
|
||
|
execRemoteGQ
|
||
|
:: (MonadIO m, MonadError QErr m)
|
||
|
=> HTTP.Manager
|
||
|
-> UserInfo
|
||
|
-> [N.Header]
|
||
|
-> BL.ByteString
|
||
|
-- ^ the raw request string
|
||
|
-> RemoteSchemaInfo
|
||
|
-> G.TypedOperationDefinition
|
||
|
-> m EncJSON
|
||
|
execRemoteGQ manager userInfo reqHdrs q rsi opDef = do
|
||
|
let opTy = G._todType opDef
|
||
|
when (opTy == G.OperationTypeSubscription) $
|
||
|
throw400 NotSupported "subscription to remote server is not supported"
|
||
|
hdrs <- getHeadersFromConf hdrConf
|
||
|
let confHdrs = map (\(k, v) -> (CI.mk $ CS.cs k, CS.cs v)) hdrs
|
||
|
clientHdrs = bool [] filteredHeaders fwdClientHdrs
|
||
|
options = wreqOptions manager (userInfoToHdrs ++ clientHdrs ++ confHdrs)
|
||
|
|
||
|
res <- liftIO $ try $ Wreq.postWith options (show url) q
|
||
|
resp <- either httpThrow return res
|
||
|
return $ encJFromLBS $ resp ^. Wreq.responseBody
|
||
|
|
||
|
where
|
||
|
RemoteSchemaInfo url hdrConf fwdClientHdrs = rsi
|
||
|
httpThrow :: (MonadError QErr m) => HTTP.HttpException -> m a
|
||
|
httpThrow err = throw500 $ T.pack . show $ err
|
||
|
|
||
|
userInfoToHdrs = map (\(k, v) -> (CI.mk $ CS.cs k, CS.cs v)) $
|
||
|
userInfoToList userInfo
|
||
|
filteredHeaders = flip filter reqHdrs $ \(n, _) ->
|
||
|
n `notElem` [ "Content-Length", "Content-MD5", "User-Agent", "Host"
|
||
|
, "Origin", "Referer" , "Accept", "Accept-Encoding"
|
||
|
, "Accept-Language", "Accept-Datetime"
|
||
|
, "Cache-Control", "Connection", "DNT"
|
||
|
]
|
||
|
|
||
|
assertSameLocationNodes
|
||
|
:: (MonadError QErr m) => [VT.TypeLoc] -> m VT.TypeLoc
|
||
|
assertSameLocationNodes typeLocs =
|
||
|
case Set.toList (Set.fromList typeLocs) of
|
||
|
-- this shouldn't happen
|
||
|
[] -> return VT.HasuraType
|
||
|
[loc] -> return loc
|
||
|
_ -> throw400 NotSupported msg
|
||
|
where
|
||
|
msg = "cannot mix top level fields from two different graphql servers"
|
||
|
|
||
|
-- TODO: we should fix this function asap
|
||
|
-- as this will fail when there is a fragment at the top level
|
||
|
getTopLevelNodes :: G.TypedOperationDefinition -> [G.Name]
|
||
|
getTopLevelNodes opDef =
|
||
|
mapMaybe f $ G._todSelectionSet opDef
|
||
|
where
|
||
|
f = \case
|
||
|
G.SelectionField fld -> Just $ G._fName fld
|
||
|
G.SelectionFragmentSpread _ -> Nothing
|
||
|
G.SelectionInlineFragment _ -> Nothing
|
||
|
|
||
|
gatherTypeLocs :: GCtx -> [G.Name] -> [VT.TypeLoc]
|
||
|
gatherTypeLocs gCtx nodes =
|
||
|
catMaybes $ flip map nodes $ \node ->
|
||
|
VT._fiLoc <$> Map.lookup node schemaNodes
|
||
|
where
|
||
|
schemaNodes =
|
||
|
let qr = VT._otiFields $ _gQueryRoot gCtx
|
||
|
mr = VT._otiFields <$> _gMutRoot gCtx
|
||
|
in maybe qr (Map.union qr) mr
|