mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-17 04:24:35 +03:00
ff62d5e0bf
This also seems to squash a stubborn space leak we see with subscriptions (linking to canonical #3388 for reference). This may also fix some of the "Unexpected exception" websockets exceptions we are now surfacing (see e.g. #4344) Also: dev.sh: fix hpc reporting Initial work on this done by Vamshi.
153 lines
5.7 KiB
Haskell
153 lines
5.7 KiB
Haskell
module Hasura.GraphQL.Schema.Merge
|
|
( mergeGCtx
|
|
, checkSchemaConflicts
|
|
, checkConflictingNode
|
|
) where
|
|
|
|
|
|
import qualified Data.HashMap.Strict as Map
|
|
import qualified Data.Text as T
|
|
import qualified Language.GraphQL.Draft.Syntax as G
|
|
|
|
import Hasura.GraphQL.Context
|
|
import Hasura.GraphQL.Validate.Types
|
|
import Hasura.Prelude
|
|
import Hasura.RQL.Types
|
|
|
|
mergeGCtx :: (MonadError QErr m) => GCtx -> GCtx -> m GCtx
|
|
mergeGCtx lGCtx rGCtx = do
|
|
checkSchemaConflicts lGCtx rGCtx
|
|
pure GCtx { _gTypes = mergedTypeMap
|
|
, _gFields = _gFields lGCtx <> _gFields rGCtx
|
|
, _gQueryRoot = mergedQueryRoot
|
|
, _gMutRoot = mergedMutationRoot
|
|
, _gSubRoot = mergedSubRoot
|
|
, _gOrdByCtx = _gOrdByCtx lGCtx <> _gOrdByCtx rGCtx
|
|
, _gQueryCtxMap = _gQueryCtxMap lGCtx <> _gQueryCtxMap rGCtx
|
|
, _gMutationCtxMap = _gMutationCtxMap lGCtx <> _gMutationCtxMap rGCtx
|
|
, _gInsCtxMap = _gInsCtxMap lGCtx <> _gInsCtxMap rGCtx
|
|
}
|
|
where
|
|
mergedQueryRoot = _gQueryRoot lGCtx <> _gQueryRoot rGCtx
|
|
mergedMutationRoot = _gMutRoot lGCtx <> _gMutRoot rGCtx
|
|
mergedSubRoot = _gSubRoot lGCtx <> _gSubRoot rGCtx
|
|
mergedTypeMap =
|
|
let mergedTypes = _gTypes lGCtx <> _gTypes rGCtx
|
|
modifyQueryRootField = Map.insert queryRootNamedType (TIObj mergedQueryRoot)
|
|
modifyMaybeRootField tyname maybeObj m = case maybeObj of
|
|
Nothing -> m
|
|
Just obj -> Map.insert tyname (TIObj obj) m
|
|
in modifyMaybeRootField subscriptionRootNamedType mergedSubRoot $
|
|
modifyMaybeRootField mutationRootNamedType mergedMutationRoot $
|
|
modifyQueryRootField mergedTypes
|
|
|
|
checkSchemaConflicts
|
|
:: (MonadError QErr m)
|
|
=> GCtx -> GCtx -> m ()
|
|
checkSchemaConflicts gCtx remoteCtx = do
|
|
let typeMap = _gTypes gCtx -- hasura typemap
|
|
-- check type conflicts
|
|
let hTypes = Map.elems typeMap
|
|
hTyNames = map G.unNamedType $ Map.keys typeMap
|
|
-- get the root names from the remote schema
|
|
rmQRootName = _otiName $ _gQueryRoot remoteCtx
|
|
rmMRootName = maybeToList $ _otiName <$> _gMutRoot remoteCtx
|
|
rmSRootName = maybeToList $ _otiName <$> _gSubRoot remoteCtx
|
|
rmRootNames = map G.unNamedType (rmQRootName:(rmMRootName ++ rmSRootName))
|
|
let rmTypes = Map.filterWithKey
|
|
(\k _ -> G.unNamedType k `notElem` builtinTy ++ rmRootNames)
|
|
$ _gTypes remoteCtx
|
|
|
|
isTyInfoSame ty = any (`tyinfoEq` ty) hTypes
|
|
-- name is same and structure is not same
|
|
isSame n ty = G.unNamedType n `elem` hTyNames &&
|
|
not (isTyInfoSame ty)
|
|
conflictedTypes = Map.filterWithKey isSame rmTypes
|
|
conflictedTyNames = map G.unNamedType $ Map.keys conflictedTypes
|
|
|
|
unless (Map.null conflictedTypes) $
|
|
throw400 RemoteSchemaConflicts $ tyMsg conflictedTyNames
|
|
|
|
-- check node conflicts
|
|
let rmQRoot = _otiFields $ _gQueryRoot remoteCtx
|
|
rmMRoot = _otiFields <$> _gMutRoot remoteCtx
|
|
rmRoots = filter (`notElem` builtinNodes ++ rmRootNames) . Map.keys <$>
|
|
mergeMaybeMaps (Just rmQRoot) rmMRoot
|
|
hQR = _otiFields <$>
|
|
(getObjTyM =<< Map.lookup hQRName typeMap)
|
|
hMR = _otiFields <$>
|
|
(getObjTyM =<< Map.lookup hMRName typeMap)
|
|
hRoots = Map.keys <$> mergeMaybeMaps hQR hMR
|
|
|
|
case (rmRoots, hRoots) of
|
|
(Just rmR, Just hR) -> do
|
|
let conflictedNodes = filter (`elem` hR) rmR
|
|
unless (null conflictedNodes) $
|
|
throw400 RemoteSchemaConflicts $ nodesMsg conflictedNodes
|
|
_ -> return ()
|
|
|
|
where
|
|
tyinfoEq a b = case (a, b) of
|
|
(TIScalar t1, TIScalar t2) -> typeEq t1 t2
|
|
(TIObj t1, TIObj t2) -> typeEq t1 t2
|
|
(TIEnum t1, TIEnum t2) -> typeEq t1 t2
|
|
(TIInpObj t1, TIInpObj t2) -> typeEq t1 t2
|
|
_ -> False
|
|
|
|
hQRName = queryRootNamedType
|
|
hMRName = mutationRootNamedType
|
|
tyMsg ty = "types: [ " <> namesToTxt ty <>
|
|
" ] have mismatch with current graphql schema. HINT: Types must be same."
|
|
nodesMsg n = "top-level nodes: [ " <> namesToTxt n <>
|
|
" ] already exist in current graphql schema. HINT: Top-level nodes can't be same."
|
|
namesToTxt = T.intercalate ", " . map G.unName
|
|
builtinNodes = ["__type", "__schema", "__typename"]
|
|
builtinTy = [ "__Directive"
|
|
, "__DirectiveLocation"
|
|
, "__EnumValue"
|
|
, "__Field"
|
|
, "__InputValue"
|
|
, "__Schema"
|
|
, "__Type"
|
|
, "__TypeKind"
|
|
, "Int"
|
|
, "Float"
|
|
, "String"
|
|
, "Boolean"
|
|
, "ID"
|
|
]
|
|
|
|
checkConflictingNode
|
|
:: (MonadError QErr m)
|
|
=> TypeMap
|
|
-- ^ See 'GCtx'.
|
|
-> G.Name
|
|
-> m ()
|
|
checkConflictingNode typeMap node = do
|
|
let hQR = _otiFields <$>
|
|
(getObjTyM =<< Map.lookup hQRName typeMap)
|
|
hMR = _otiFields <$>
|
|
(getObjTyM =<< Map.lookup hMRName typeMap)
|
|
hRoots = Map.keys <$> mergeMaybeMaps hQR hMR
|
|
case hRoots of
|
|
Just hR ->
|
|
when (node `elem` hR) $
|
|
throw400 RemoteSchemaConflicts msg
|
|
_ -> return ()
|
|
where
|
|
hQRName = queryRootNamedType
|
|
hMRName = mutationRootNamedType
|
|
msg = "node " <> G.unName node <>
|
|
" already exists in current graphql schema"
|
|
|
|
mergeMaybeMaps
|
|
:: (Eq k, Hashable k)
|
|
=> Maybe (Map.HashMap k v)
|
|
-> Maybe (Map.HashMap k v)
|
|
-> Maybe (Map.HashMap k v)
|
|
mergeMaybeMaps m1 m2 = case (m1, m2) of
|
|
(Nothing, Nothing) -> Nothing
|
|
(Just m1', Nothing) -> Just m1'
|
|
(Nothing, Just m2') -> Just m2'
|
|
(Just m1', Just m2') -> Just $ Map.union m1' m2'
|