mirror of
https://github.com/zhigang1992/graphql-engine.git
synced 2026-05-26 03:17:40 +08:00
This commit is contained in:
committed by
Shahidh K Muhammed
parent
39bc3acffd
commit
32387ba964
@@ -245,7 +245,7 @@ const analyzeFetcher = (url, headers, analyzeApiChange) => {
|
||||
|
||||
// Check if x-hasura-role is available in some form in the headers
|
||||
const totalHeaders = Object.keys(reqHeaders);
|
||||
totalHeaders.forEach((t) => {
|
||||
totalHeaders.forEach(t => {
|
||||
// If header has x-hasura-*
|
||||
const lHead = t.toLowerCase();
|
||||
if (lHead.slice(0, 'x-hasura-'.length) === 'x-hasura-') {
|
||||
|
||||
@@ -38,9 +38,11 @@ const migrationNameTip = (
|
||||
'run_sql_migration'
|
||||
</Tooltip>
|
||||
);
|
||||
const trackTableTip = (hasFunctionSupport) => (
|
||||
const trackTableTip = hasFunctionSupport => (
|
||||
<Tooltip id="tooltip-tracktable">
|
||||
{ `If you are creating a table/view${hasFunctionSupport ? '/function' : ''}, you can track them to query them
|
||||
{`If you are creating a table/view${
|
||||
hasFunctionSupport ? '/function' : ''
|
||||
}, you can track them to query them
|
||||
with GraphQL`}
|
||||
</Tooltip>
|
||||
);
|
||||
@@ -299,7 +301,10 @@ const RawSQL = ({
|
||||
data-test="raw-sql-track-check"
|
||||
/>
|
||||
Track {placeholderText}
|
||||
<OverlayTrigger placement="right" overlay={trackTableTip(!!functionText)}>
|
||||
<OverlayTrigger
|
||||
placement="right"
|
||||
overlay={trackTableTip(!!functionText)}
|
||||
>
|
||||
<i
|
||||
className={`${styles.padd_small_left} fa fa-info-circle`}
|
||||
aria-hidden="true"
|
||||
|
||||
@@ -121,14 +121,9 @@ Current limitations
|
||||
- Nodes from different GraphQL servers cannot be used in the same query/mutation. All top-level fields have to be
|
||||
from the same GraphQL server.
|
||||
- Subscriptions on remote GraphQL servers are not supported.
|
||||
- Interfaces_ and Unions_ are not supported - if a remote schema has interfaces/unions, an error will be thrown if
|
||||
you try to merge it.
|
||||
|
||||
These limitations will be addressed in upcoming versions.
|
||||
|
||||
.. _Interfaces: https://graphql.github.io/learn/schema/#interfaces
|
||||
.. _Unions: https://graphql.github.io/learn/schema/#union-types
|
||||
|
||||
Extending the auto-generated GraphQL schema fields
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
||||
@@ -133,10 +133,11 @@ mkHsraObjFldInfo descM name params ty =
|
||||
mkHsraObjTyInfo
|
||||
:: Maybe G.Description
|
||||
-> G.NamedType
|
||||
-> IFacesSet
|
||||
-> ObjFieldMap
|
||||
-> ObjTyInfo
|
||||
mkHsraObjTyInfo descM ty flds =
|
||||
mkObjTyInfo descM ty flds HasuraType
|
||||
mkHsraObjTyInfo descM ty implIFaces flds =
|
||||
mkObjTyInfo descM ty implIFaces flds HasuraType
|
||||
|
||||
mkHsraInpTyInfo
|
||||
:: Maybe G.Description
|
||||
@@ -318,7 +319,7 @@ defaultTypes = $(fromSchemaDocQ defaultSchema HasuraType)
|
||||
mkGCtx :: TyAgg -> RootFlds -> InsCtxMap -> GCtx
|
||||
mkGCtx (TyAgg tyInfos fldInfos ordByEnums funcArgCtx) (RootFlds flds) insCtxMap =
|
||||
let queryRoot = mkHsraObjTyInfo (Just "query root")
|
||||
(G.NamedType "query_root") $
|
||||
(G.NamedType "query_root") Set.empty $
|
||||
mapFromL _fiName (schemaFld:typeFld:qFlds)
|
||||
scalarTys = map (TIScalar . mkHsraScalarTyInfo) colTys
|
||||
compTys = map (TIInpObj . mkCompExpInp) colTys
|
||||
@@ -338,12 +339,12 @@ mkGCtx (TyAgg tyInfos fldInfos ordByEnums funcArgCtx) (RootFlds flds) insCtxMap
|
||||
colTys = Set.toList $ Set.fromList $ map pgiType $
|
||||
lefts $ Map.elems fldInfos
|
||||
mkMutRoot =
|
||||
mkHsraObjTyInfo (Just "mutation root") (G.NamedType "mutation_root") .
|
||||
mkHsraObjTyInfo (Just "mutation root") (G.NamedType "mutation_root") Set.empty .
|
||||
mapFromL _fiName
|
||||
mutRootM = bool (Just $ mkMutRoot mFlds) Nothing $ null mFlds
|
||||
mkSubRoot =
|
||||
mkHsraObjTyInfo (Just "subscription root")
|
||||
(G.NamedType "subscription_root") . mapFromL _fiName
|
||||
(G.NamedType "subscription_root") Set.empty . mapFromL _fiName
|
||||
subRootM = bool (Just $ mkSubRoot qFlds) Nothing $ null qFlds
|
||||
(qFlds, mFlds) = partitionEithers $ map snd $ Map.elems flds
|
||||
schemaFld = mkHsraObjFldInfo Nothing "__schema" Map.empty $
|
||||
|
||||
@@ -11,6 +11,7 @@ import qualified Data.Aeson as J
|
||||
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.Text as T
|
||||
import qualified Data.Text.Encoding as T
|
||||
import qualified Language.GraphQL.Draft.Syntax as G
|
||||
@@ -49,12 +50,11 @@ fetchRemoteSchema manager name def@(RemoteSchemaInfo url headerConf _) = do
|
||||
|
||||
introspectRes :: (FromIntrospection IntrospectionResult) <-
|
||||
either schemaErr return $ J.eitherDecode respData
|
||||
let (G.SchemaDocument tyDefs, qRootN, mRootN, sRootN) =
|
||||
let (sDoc, qRootN, mRootN, sRootN) =
|
||||
fromIntrospection introspectRes
|
||||
let etTypeInfos = mapM fromRemoteTyDef tyDefs
|
||||
typeInfos <- either schemaErr return etTypeInfos
|
||||
let typMap = VT.mkTyInfoMap typeInfos
|
||||
mQrTyp = Map.lookup qRootN typMap
|
||||
typMap <- either remoteSchemaErr return $ VT.fromSchemaDoc sDoc $
|
||||
VT.RemoteType name def
|
||||
let mQrTyp = Map.lookup qRootN typMap
|
||||
mMrTyp = maybe Nothing (\mr -> Map.lookup mr typMap) mRootN
|
||||
mSrTyp = maybe Nothing (\sr -> Map.lookup sr typMap) sRootN
|
||||
qrTyp <- liftMaybe noQueryRoot mQrTyp
|
||||
@@ -66,8 +66,10 @@ fetchRemoteSchema manager name def@(RemoteSchemaInfo url headerConf _) = do
|
||||
|
||||
where
|
||||
noQueryRoot = err400 Unexpected "query root not found in remote schema"
|
||||
fromRemoteTyDef ty = VT.fromTyDef ty $ VT.RemoteType name def
|
||||
schemaErr err = throw400 RemoteSchemaError (T.pack $ show err)
|
||||
remoteSchemaErr :: (MonadError QErr m) => T.Text -> m a
|
||||
remoteSchemaErr = throw400 RemoteSchemaError
|
||||
|
||||
schemaErr err = remoteSchemaErr (T.pack $ show err)
|
||||
|
||||
throwHttpErr :: (MonadError QErr m) => HTTP.HttpException -> m a
|
||||
throwHttpErr = schemaErr
|
||||
@@ -150,11 +152,11 @@ mergeMutRoot a b =
|
||||
|
||||
mkNewEmptyMutRoot :: VT.ObjTyInfo
|
||||
mkNewEmptyMutRoot = VT.ObjTyInfo (Just "mutation root")
|
||||
(G.NamedType "mutation_root") Map.empty
|
||||
(G.NamedType "mutation_root") Set.empty Map.empty
|
||||
|
||||
mkNewMutRoot :: VT.ObjFieldMap -> VT.ObjTyInfo
|
||||
mkNewMutRoot flds = VT.ObjTyInfo (Just "mutation root")
|
||||
(G.NamedType "mutation_root") flds
|
||||
(G.NamedType "mutation_root") Set.empty flds
|
||||
|
||||
mergeSubRoot :: GS.GCtx -> GS.GCtx -> Maybe VT.ObjTyInfo
|
||||
mergeSubRoot a b =
|
||||
@@ -171,7 +173,7 @@ mergeSubRoot a b =
|
||||
|
||||
mkNewEmptySubRoot :: VT.ObjTyInfo
|
||||
mkNewEmptySubRoot = VT.ObjTyInfo (Just "subscription root")
|
||||
(G.NamedType "subscription_root") Map.empty
|
||||
(G.NamedType "subscription_root") Set.empty Map.empty
|
||||
|
||||
|
||||
mergeTyMaps
|
||||
|
||||
@@ -8,6 +8,7 @@ import Hasura.Prelude
|
||||
|
||||
import qualified Data.Aeson as J
|
||||
import qualified Data.HashMap.Strict as Map
|
||||
import qualified Data.HashSet as Set
|
||||
import qualified Data.Text as T
|
||||
import qualified Language.GraphQL.Draft.Syntax as G
|
||||
|
||||
@@ -75,14 +76,14 @@ objectTypeR
|
||||
=> ObjTyInfo
|
||||
-> Field
|
||||
-> m J.Object
|
||||
objectTypeR (ObjTyInfo descM n flds) fld =
|
||||
objectTypeR (ObjTyInfo descM n iFaces flds) fld =
|
||||
withSubFields (_fSelSet fld) $ \subFld ->
|
||||
case _fName subFld of
|
||||
"__typename" -> retJT "__Type"
|
||||
"kind" -> retJ TKOBJECT
|
||||
"name" -> retJ $ namedTyToTxt n
|
||||
"description" -> retJ $ fmap G.unDescription descM
|
||||
"interfaces" -> retJ ([] :: [()])
|
||||
"interfaces" -> fmap J.toJSON $ mapM (`ifaceR` subFld) $ Set.toList iFaces
|
||||
"fields" -> fmap J.toJSON $ mapM (`fieldR` subFld) $
|
||||
sortBy (comparing _fiName) $
|
||||
filter notBuiltinFld $ Map.elems flds
|
||||
@@ -94,6 +95,55 @@ notBuiltinFld f =
|
||||
where
|
||||
fldName = _fiName f
|
||||
|
||||
getImplTypes :: (MonadReader t m, Has TypeMap t) => AsObjType -> m [ObjTyInfo]
|
||||
getImplTypes aot = do
|
||||
tyInfo :: TypeMap <- asks getter
|
||||
return $ sortBy (comparing _otiName) $ Map.elems $ getPossibleObjTypes' tyInfo $ aot
|
||||
|
||||
-- 4.5.2.3
|
||||
unionR :: (MonadReader t m, MonadError QErr m, Has TypeMap t) => UnionTyInfo -> Field -> m J.Object
|
||||
unionR u@(UnionTyInfo descM n _) fld =
|
||||
withSubFields (_fSelSet fld) $ \subFld ->
|
||||
case _fName subFld of
|
||||
"__typename" -> retJT "__Field"
|
||||
"kind" -> retJ TKUNION
|
||||
"name" -> retJ $ namedTyToTxt n
|
||||
"description" -> retJ $ fmap G.unDescription descM
|
||||
"possibleTypes" -> fmap J.toJSON $ mapM (`objectTypeR` subFld) =<< getImplTypes (AOTUnion u)
|
||||
_ -> return J.Null
|
||||
|
||||
-- 4.5.2.4
|
||||
ifaceR
|
||||
:: ( MonadReader r m, Has TypeMap r
|
||||
, MonadError QErr m)
|
||||
=> G.NamedType
|
||||
-> Field
|
||||
-> m J.Object
|
||||
ifaceR n fld = do
|
||||
tyInfo <- getTyInfo n
|
||||
case tyInfo of
|
||||
TIIFace ifaceTyInfo -> ifaceR' ifaceTyInfo fld
|
||||
_ -> throw500 $ "Unknown interface " <> G.unName (G.unNamedType n)
|
||||
|
||||
ifaceR'
|
||||
:: ( MonadReader r m, Has TypeMap r
|
||||
, MonadError QErr m)
|
||||
=> IFaceTyInfo
|
||||
-> Field
|
||||
-> m J.Object
|
||||
ifaceR' i@(IFaceTyInfo descM n flds) fld =
|
||||
withSubFields (_fSelSet fld) $ \subFld ->
|
||||
case _fName subFld of
|
||||
"__typename" -> retJT "__Type"
|
||||
"kind" -> retJ TKINTERFACE
|
||||
"name" -> retJ $ namedTyToTxt n
|
||||
"description" -> retJ $ fmap G.unDescription descM
|
||||
"fields" -> fmap J.toJSON $ mapM (`fieldR` subFld) $
|
||||
sortBy (comparing _fiName) $
|
||||
filter notBuiltinFld $ Map.elems flds
|
||||
"possibleTypes" -> fmap J.toJSON $ mapM (`objectTypeR` subFld) =<< getImplTypes (AOTIFace i)
|
||||
_ -> return J.Null
|
||||
|
||||
-- 4.5.2.5
|
||||
enumTypeR
|
||||
:: ( Monad m )
|
||||
@@ -179,6 +229,8 @@ namedTypeR' fld = \case
|
||||
TIObj objTyInfo -> objectTypeR objTyInfo fld
|
||||
TIEnum enumTypeInfo -> enumTypeR enumTypeInfo fld
|
||||
TIInpObj inpObjTyInfo -> inputObjR inpObjTyInfo fld
|
||||
TIIFace iFaceTyInfo -> ifaceR' iFaceTyInfo fld
|
||||
TIUnion unionTyInfo -> unionR unionTyInfo fld
|
||||
|
||||
-- 4.5.3
|
||||
fieldR
|
||||
|
||||
@@ -417,7 +417,7 @@ mkTableObj
|
||||
-> [SelField]
|
||||
-> ObjTyInfo
|
||||
mkTableObj tn allowedFlds =
|
||||
mkObjTyInfo (Just desc) (mkTableTy tn) (mapFromL _fiName flds) HasuraType
|
||||
mkObjTyInfo (Just desc) (mkTableTy tn) Set.empty (mapFromL _fiName flds) HasuraType
|
||||
where
|
||||
flds = concatMap (either (pure . mkPGColFld) mkRelFld') allowedFlds
|
||||
mkRelFld' (relInfo, allowAgg, _, _, isNullable) =
|
||||
@@ -433,7 +433,7 @@ type table_aggregate {
|
||||
mkTableAggObj
|
||||
:: QualifiedTable -> ObjTyInfo
|
||||
mkTableAggObj tn =
|
||||
mkHsraObjTyInfo (Just desc) (mkTableAggTy tn) $ mapFromL _fiName
|
||||
mkHsraObjTyInfo (Just desc) (mkTableAggTy tn) Set.empty $ mapFromL _fiName
|
||||
[aggFld, nodesFld]
|
||||
where
|
||||
desc = G.Description $
|
||||
@@ -460,7 +460,7 @@ type table_aggregate_fields{
|
||||
mkTableAggFldsObj
|
||||
:: QualifiedTable -> [PGCol] -> [PGCol] -> ObjTyInfo
|
||||
mkTableAggFldsObj tn numCols compCols =
|
||||
mkHsraObjTyInfo (Just desc) (mkTableAggFldsTy tn) $ mapFromL _fiName $
|
||||
mkHsraObjTyInfo (Just desc) (mkTableAggFldsTy tn) Set.empty $ mapFromL _fiName $
|
||||
countFld : (numFlds <> compFlds)
|
||||
where
|
||||
desc = G.Description $
|
||||
@@ -496,7 +496,7 @@ mkTableColAggFldsObj
|
||||
-> [PGColInfo]
|
||||
-> ObjTyInfo
|
||||
mkTableColAggFldsObj tn op f cols =
|
||||
mkHsraObjTyInfo (Just desc) (mkTableColAggFldsTy op tn) $ mapFromL _fiName $
|
||||
mkHsraObjTyInfo (Just desc) (mkTableColAggFldsTy op tn) Set.empty $ mapFromL _fiName $
|
||||
map mkColObjFld cols
|
||||
where
|
||||
desc = G.Description $ "aggregate " <> G.unName op <> " on columns"
|
||||
@@ -649,7 +649,7 @@ mkMutRespObj
|
||||
-> Bool -- is sel perm defined
|
||||
-> ObjTyInfo
|
||||
mkMutRespObj tn sel =
|
||||
mkHsraObjTyInfo (Just objDesc) (mkMutRespTy tn) $ mapFromL _fiName
|
||||
mkHsraObjTyInfo (Just objDesc) (mkMutRespTy tn) Set.empty $ mapFromL _fiName
|
||||
$ affectedRowsFld : bool [] [returningFld] sel
|
||||
where
|
||||
objDesc = G.Description $
|
||||
|
||||
@@ -237,8 +237,11 @@ validateNamedTypeVal inpValParser nt val = do
|
||||
case tyInfo of
|
||||
-- this should never happen
|
||||
TIObj _ ->
|
||||
throw500 $ "unexpected object type info for: "
|
||||
<> showNamedTy nt
|
||||
throwUnexpTypeErr "object"
|
||||
TIIFace _ ->
|
||||
throwUnexpTypeErr "interface"
|
||||
TIUnion _ ->
|
||||
throwUnexpTypeErr "union"
|
||||
TIInpObj ioti ->
|
||||
withParsed (getObject inpValParser) val $
|
||||
fmap (AGObject nt) . mapM (validateObject inpValParser ioti)
|
||||
@@ -249,6 +252,8 @@ validateNamedTypeVal inpValParser nt val = do
|
||||
withParsed (getScalar inpValParser) val $
|
||||
fmap (AGScalar pgColTy) . mapM (validateScalar pgColTy)
|
||||
where
|
||||
throwUnexpTypeErr ty = throw500 $ "unexpected " <> ty <> " type info for: "
|
||||
<> showNamedTy nt
|
||||
validateEnum enumTyInfo enumVal =
|
||||
if Map.member enumVal (_etiValues enumTyInfo)
|
||||
then return enumVal
|
||||
|
||||
@@ -5,6 +5,9 @@ module Hasura.GraphQL.Validate.Types
|
||||
, ObjFieldMap
|
||||
, ObjTyInfo(..)
|
||||
, mkObjTyInfo
|
||||
, IFaceTyInfo(..)
|
||||
, IFacesSet
|
||||
, UnionTyInfo(..)
|
||||
, FragDef(..)
|
||||
, FragDefMap
|
||||
, AnnVarVals
|
||||
@@ -14,12 +17,16 @@ module Hasura.GraphQL.Validate.Types
|
||||
, InpObjTyInfo(..)
|
||||
, ScalarTyInfo(..)
|
||||
, DirectiveInfo(..)
|
||||
, AsObjType(..)
|
||||
, defaultDirectives
|
||||
, defDirectivesMap
|
||||
, defaultSchema
|
||||
, TypeInfo(..)
|
||||
, isObjTy
|
||||
, isIFaceTy
|
||||
, getPossibleObjTypes'
|
||||
, getObjTyM
|
||||
, getUnionTyM
|
||||
, mkScalarTy
|
||||
, pgColTyToScalar
|
||||
, pgColValToAnnGVal
|
||||
@@ -27,6 +34,7 @@ module Hasura.GraphQL.Validate.Types
|
||||
, mkTyInfoMap
|
||||
, fromTyDef
|
||||
, fromTyDefQ
|
||||
, fromSchemaDoc
|
||||
, fromSchemaDocQ
|
||||
, TypeMap
|
||||
, TypeLoc (..)
|
||||
@@ -45,6 +53,7 @@ import Instances.TH.Lift ()
|
||||
import qualified Data.Aeson as J
|
||||
import qualified Data.HashMap.Strict as Map
|
||||
import qualified Data.HashMap.Strict.InsOrd as OMap
|
||||
import qualified Data.HashSet as Set
|
||||
import qualified Data.Text as T
|
||||
import qualified Language.GraphQL.Draft.Syntax as G
|
||||
import qualified Language.GraphQL.Draft.TH as G
|
||||
@@ -56,7 +65,6 @@ import Hasura.RQL.Types.RemoteSchema
|
||||
import Hasura.SQL.Types
|
||||
import Hasura.SQL.Value
|
||||
|
||||
|
||||
-- | Typeclass for equating relevant properties of various GraphQL types
|
||||
-- | defined below
|
||||
class EquatableGType a where
|
||||
@@ -143,30 +151,39 @@ fromFldDef (G.FieldDefinition descM n args ty _) loc =
|
||||
|
||||
type ObjFieldMap = Map.HashMap G.Name ObjFldInfo
|
||||
|
||||
type IFacesSet = Set.HashSet G.NamedType
|
||||
|
||||
data ObjTyInfo
|
||||
= ObjTyInfo
|
||||
{ _otiDesc :: !(Maybe G.Description)
|
||||
, _otiName :: !G.NamedType
|
||||
, _otiFields :: !ObjFieldMap
|
||||
{ _otiDesc :: !(Maybe G.Description)
|
||||
, _otiName :: !G.NamedType
|
||||
, _otiImplIFaces :: !IFacesSet
|
||||
, _otiFields :: !ObjFieldMap
|
||||
} deriving (Show, Eq, TH.Lift)
|
||||
|
||||
instance EquatableGType ObjTyInfo where
|
||||
type EqProps ObjTyInfo =
|
||||
(G.NamedType, Map.HashMap G.Name (G.Name, G.GType, ParamMap))
|
||||
getEqProps a = (,) (_otiName a) (Map.map getEqProps (_otiFields a))
|
||||
(G.NamedType, Set.HashSet G.NamedType, Map.HashMap G.Name (G.Name, G.GType, ParamMap))
|
||||
getEqProps a = (,,) (_otiName a) (_otiImplIFaces a) (Map.map getEqProps (_otiFields a))
|
||||
|
||||
instance Monoid ObjTyInfo where
|
||||
mempty = ObjTyInfo Nothing (G.NamedType "") Map.empty
|
||||
mempty = ObjTyInfo Nothing (G.NamedType "") Set.empty Map.empty
|
||||
|
||||
instance Semigroup ObjTyInfo where
|
||||
objA <> objB =
|
||||
objA { _otiFields = Map.union (_otiFields objA) (_otiFields objB)
|
||||
, _otiImplIFaces = _otiImplIFaces objA `Set.union` _otiImplIFaces objB
|
||||
}
|
||||
|
||||
mkObjTyInfo
|
||||
:: Maybe G.Description -> G.NamedType -> ObjFieldMap -> TypeLoc -> ObjTyInfo
|
||||
mkObjTyInfo descM ty flds loc =
|
||||
ObjTyInfo descM ty $ Map.insert (_fiName newFld) newFld flds
|
||||
:: Maybe G.Description -> G.NamedType -> IFacesSet -> ObjFieldMap -> TypeLoc -> ObjTyInfo
|
||||
mkObjTyInfo descM ty iFaces flds loc =
|
||||
ObjTyInfo descM ty iFaces $ Map.insert (_fiName newFld) newFld flds
|
||||
where newFld = typenameFld loc
|
||||
|
||||
mkIFaceTyInfo :: Maybe G.Description -> G.NamedType -> Map.HashMap G.Name ObjFldInfo -> TypeLoc -> IFaceTyInfo
|
||||
mkIFaceTyInfo descM ty flds loc =
|
||||
IFaceTyInfo descM ty $ Map.insert (_fiName newFld) newFld flds
|
||||
where newFld = typenameFld loc
|
||||
|
||||
typenameFld :: TypeLoc -> ObjFldInfo
|
||||
@@ -177,11 +194,62 @@ typenameFld loc =
|
||||
desc = "The name of the current Object type at runtime"
|
||||
|
||||
fromObjTyDef :: G.ObjectTypeDefinition -> TypeLoc -> ObjTyInfo
|
||||
fromObjTyDef (G.ObjectTypeDefinition descM n _ _ flds) loc =
|
||||
mkObjTyInfo descM (G.NamedType n) fldMap loc
|
||||
fromObjTyDef (G.ObjectTypeDefinition descM n ifaces _ flds) loc =
|
||||
mkObjTyInfo descM (G.NamedType n) (Set.fromList ifaces) fldMap loc
|
||||
where
|
||||
fldMap = Map.fromList [(G._fldName fld, fromFldDef fld loc) | fld <- flds]
|
||||
|
||||
data IFaceTyInfo
|
||||
= IFaceTyInfo
|
||||
{ _ifDesc :: !(Maybe G.Description)
|
||||
, _ifName :: !G.NamedType
|
||||
, _ifFields :: !ObjFieldMap
|
||||
} deriving (Show, Eq, TH.Lift)
|
||||
|
||||
instance EquatableGType IFaceTyInfo where
|
||||
type EqProps IFaceTyInfo =
|
||||
(G.NamedType, Map.HashMap G.Name (G.Name, G.GType, ParamMap))
|
||||
getEqProps a = (,) (_ifName a) (Map.map getEqProps (_ifFields a))
|
||||
|
||||
instance Monoid IFaceTyInfo where
|
||||
mempty = IFaceTyInfo Nothing (G.NamedType "") Map.empty
|
||||
|
||||
instance Semigroup IFaceTyInfo where
|
||||
objA <> objB =
|
||||
objA { _ifFields = Map.union (_ifFields objA) (_ifFields objB)
|
||||
}
|
||||
|
||||
fromIFaceDef :: G.InterfaceTypeDefinition -> TypeLoc -> IFaceTyInfo
|
||||
fromIFaceDef (G.InterfaceTypeDefinition descM n _ flds) loc =
|
||||
mkIFaceTyInfo descM (G.NamedType n) fldMap loc
|
||||
where
|
||||
fldMap = Map.fromList [(G._fldName fld, fromFldDef fld loc) | fld <- flds]
|
||||
|
||||
type MemberTypes = Set.HashSet G.NamedType
|
||||
|
||||
data UnionTyInfo
|
||||
= UnionTyInfo
|
||||
{ _utiDesc :: !(Maybe G.Description)
|
||||
, _utiName :: !(G.NamedType)
|
||||
, _utiMemberTypes :: !MemberTypes
|
||||
} deriving (Show, Eq, TH.Lift)
|
||||
|
||||
instance EquatableGType UnionTyInfo where
|
||||
type EqProps UnionTyInfo =
|
||||
(G.NamedType, Set.HashSet G.NamedType)
|
||||
getEqProps a = (,) (_utiName a) (_utiMemberTypes a)
|
||||
|
||||
instance Monoid UnionTyInfo where
|
||||
mempty = UnionTyInfo Nothing (G.NamedType "") Set.empty
|
||||
|
||||
instance Semigroup UnionTyInfo where
|
||||
objA <> objB =
|
||||
objA { _utiMemberTypes = Set.union (_utiMemberTypes objA) (_utiMemberTypes objB)
|
||||
}
|
||||
|
||||
fromUnionTyDef :: G.UnionTypeDefinition -> UnionTyInfo
|
||||
fromUnionTyDef (G.UnionTypeDefinition descM n _ mt) = UnionTyInfo descM (G.NamedType n) $ Set.fromList mt
|
||||
|
||||
type InpObjFldMap = Map.HashMap G.Name InpValInfo
|
||||
|
||||
data InpObjTyInfo
|
||||
@@ -233,8 +301,29 @@ data TypeInfo
|
||||
| TIObj !ObjTyInfo
|
||||
| TIEnum !EnumTyInfo
|
||||
| TIInpObj !InpObjTyInfo
|
||||
| TIIFace !IFaceTyInfo
|
||||
| TIUnion !UnionTyInfo
|
||||
deriving (Show, Eq, TH.Lift)
|
||||
|
||||
data AsObjType
|
||||
= AOTObj ObjTyInfo
|
||||
| AOTIFace IFaceTyInfo
|
||||
| AOTUnion UnionTyInfo
|
||||
|
||||
getPossibleObjTypes' :: TypeMap -> AsObjType -> Map.HashMap G.NamedType ObjTyInfo
|
||||
getPossibleObjTypes' _ (AOTObj obj) = toObjMap [obj]
|
||||
getPossibleObjTypes' tyMap (AOTIFace i) = toObjMap $ mapMaybe previewImplTypeM $ Map.elems tyMap
|
||||
where
|
||||
previewImplTypeM = \case
|
||||
TIObj objTyInfo -> bool Nothing (Just objTyInfo) $
|
||||
_ifName i `elem` _otiImplIFaces objTyInfo
|
||||
_ -> Nothing
|
||||
getPossibleObjTypes' tyMap (AOTUnion u) = toObjMap $ mapMaybe (extrObjTyInfoM tyMap) $ Set.toList $ _utiMemberTypes u
|
||||
|
||||
toObjMap :: [ObjTyInfo] -> Map.HashMap G.NamedType ObjTyInfo
|
||||
toObjMap objs = foldr (\o -> Map.insert (_otiName o) o) Map.empty objs
|
||||
|
||||
|
||||
isObjTy :: TypeInfo -> Bool
|
||||
isObjTy = \case
|
||||
(TIObj _) -> True
|
||||
@@ -245,6 +334,164 @@ getObjTyM = \case
|
||||
(TIObj t) -> return t
|
||||
_ -> Nothing
|
||||
|
||||
getUnionTyM :: TypeInfo -> Maybe UnionTyInfo
|
||||
getUnionTyM = \case
|
||||
(TIUnion u) -> return u
|
||||
_ -> Nothing
|
||||
|
||||
isIFaceTy :: TypeInfo -> Bool
|
||||
isIFaceTy = \case
|
||||
(TIIFace _) -> True
|
||||
_ -> False
|
||||
|
||||
data SchemaPath
|
||||
= SchemaPath
|
||||
{ _spTypeName :: !(Maybe G.NamedType)
|
||||
, _spFldName :: !(Maybe G.Name)
|
||||
, _spArgName :: !(Maybe G.Name)
|
||||
, _spType :: !(Maybe T.Text)
|
||||
}
|
||||
|
||||
setFldNameSP :: SchemaPath -> G.Name -> SchemaPath
|
||||
setFldNameSP sp fn = sp { _spFldName = Just fn}
|
||||
|
||||
setArgNameSP :: SchemaPath -> G.Name -> SchemaPath
|
||||
setArgNameSP sp an = sp { _spArgName = Just an}
|
||||
|
||||
showSP :: SchemaPath -> Text
|
||||
showSP (SchemaPath t f a _) = maybe "" (\x -> showNamedTy x <> fN) t
|
||||
where
|
||||
fN = maybe "" (\x -> "." <> showName x <> aN) f
|
||||
aN = maybe "" showArg a
|
||||
showArg x = "(" <> showName x <> ":)"
|
||||
|
||||
showSPTxt' :: SchemaPath -> Text
|
||||
showSPTxt' (SchemaPath _ f a t) = maybe "" (<> " "<> fld) t
|
||||
where
|
||||
fld = maybe "" (const $ "field " <> arg) f
|
||||
arg = maybe "" (const "argument ") a
|
||||
|
||||
showSPTxt :: SchemaPath -> Text
|
||||
showSPTxt p = showSPTxt' p <> showSP p
|
||||
|
||||
validateIFace :: MonadError Text f => IFaceTyInfo -> f ()
|
||||
validateIFace (IFaceTyInfo _ n flds) = do
|
||||
when (isFldListEmpty flds) $ throwError $ "List of fields cannot be empty for interface " <> showNamedTy n
|
||||
|
||||
validateObj :: TypeMap -> ObjTyInfo -> Either Text ()
|
||||
validateObj tyMap objTyInfo@(ObjTyInfo _ n _ flds) = do
|
||||
when (isFldListEmpty flds) $ throwError $ "List of fields cannot be empty for " <> objTxt
|
||||
mapM_ (extrIFaceTyInfo' >=> validateIFaceImpl objTyInfo) $ _otiImplIFaces objTyInfo
|
||||
where
|
||||
extrIFaceTyInfo' t = withObjTxt $ extrIFaceTyInfo tyMap t
|
||||
withObjTxt x = x `catchError` \e -> throwError $ e <> " implemented by " <> objTxt
|
||||
objTxt = "Object type " <> showNamedTy n
|
||||
validateIFaceImpl = implmntsIFace tyMap
|
||||
|
||||
isFldListEmpty :: ObjFieldMap -> Bool
|
||||
isFldListEmpty = Map.null . Map.delete "__typename"
|
||||
|
||||
validateUnion :: MonadError Text m => TypeMap -> UnionTyInfo -> m ()
|
||||
validateUnion tyMap (UnionTyInfo _ un mt) = do
|
||||
when (Set.null mt) $ throwError $ "List of member types cannot be empty for union type " <> showNamedTy un
|
||||
mapM_ valIsObjTy $ Set.toList mt
|
||||
where
|
||||
valIsObjTy mn = case Map.lookup mn tyMap of
|
||||
Just (TIObj t) -> return t
|
||||
Nothing -> throwError $ "Could not find type " <> showNamedTy mn <> ", which is defined as a member type of Union " <> showNamedTy un
|
||||
_ -> throwError $ "Union type " <> showNamedTy un <> " can only include object types. It cannot include " <> showNamedTy mn
|
||||
|
||||
implmntsIFace :: TypeMap -> ObjTyInfo -> IFaceTyInfo -> Either Text ()
|
||||
implmntsIFace tyMap objTyInfo iFaceTyInfo = do
|
||||
let path =
|
||||
( SchemaPath (Just $ _otiName objTyInfo) Nothing Nothing (Just "Object")
|
||||
, SchemaPath (Just $ _ifName iFaceTyInfo) Nothing Nothing (Just "Interface")
|
||||
)
|
||||
mapM_ (includesIFaceFld path) $ _ifFields iFaceTyInfo
|
||||
where
|
||||
includesIFaceFld (spO,spIF) ifFld = do
|
||||
let pathA@(spOA, spIFA) = (spO, setFldNameSP spIF $ _fiName ifFld)
|
||||
objFld <- sameNameFld pathA ifFld
|
||||
let pathB = (setFldNameSP spOA $ _fiName objFld, spIFA)
|
||||
validateIsSubType' pathB (_fiTy objFld) (_fiTy ifFld)
|
||||
hasAllArgs pathB objFld ifFld
|
||||
isExtraArgsNullable pathB objFld ifFld
|
||||
|
||||
validateIsSubType' (spO,spIF) oFld iFld = validateIsSubType tyMap oFld iFld `catchError` \_ ->
|
||||
throwError $ "The type of " <> showSPTxt spO <> " (" <> G.showGT oFld <>
|
||||
") is not the same type/sub type of " <> showSPTxt spIF <> " (" <> G.showGT iFld <> ")"
|
||||
|
||||
sameNameFld (spO, spIF) ifFld = do
|
||||
let spIFN = setFldNameSP spIF $ _fiName ifFld
|
||||
onNothing (Map.lookup (_fiName ifFld) objFlds)
|
||||
$ throwError $ showSPTxt spIFN <> " expected, but " <> showSP spO <> " does not provide it"
|
||||
|
||||
hasAllArgs (spO, spIF) objFld ifFld = forM_ (_fiParams ifFld) $ \ifArg -> do
|
||||
objArg <- sameNameArg ifArg
|
||||
let (spON, spIFN) = (setArgNameSP spO $ _iviName objArg, setArgNameSP spIF $ _iviName ifArg)
|
||||
unless (_iviType objArg == _iviType ifArg) $ throwError $
|
||||
showSPTxt spIFN <> " expects type " <> G.showGT (_iviType ifArg) <> ", but " <>
|
||||
showSP spON <> " has type " <> G.showGT (_iviType objArg)
|
||||
where
|
||||
sameNameArg ivi = do
|
||||
let spIFN = setArgNameSP spIF $ _iviName ivi
|
||||
onNothing (Map.lookup (_iviName ivi) objArgs) $ throwError $ showSPTxt spIFN <> " required, but " <>
|
||||
showSPTxt spO <> " does not provide it"
|
||||
objArgs = _fiParams objFld
|
||||
|
||||
isExtraArgsNullable (spO, spIF) objFld ifFld = forM_ extraArgs isInpValNullable
|
||||
where
|
||||
extraArgs = Map.difference (_fiParams objFld) (_fiParams ifFld)
|
||||
isInpValNullable ivi = unless (G.isNullable $ _iviType ivi) $ throwError $
|
||||
showSPTxt (setArgNameSP spO $ _iviName ivi) <> " is of required type "
|
||||
<> G.showGT (_iviType ivi) <> ", but is not provided by " <> showSPTxt spIF
|
||||
|
||||
objFlds = _otiFields objTyInfo
|
||||
|
||||
extrTyInfo :: TypeMap -> G.NamedType -> Either Text TypeInfo
|
||||
extrTyInfo tyMap tn = maybe
|
||||
(throwError $ "Could not find type with name " <> showNamedTy tn)
|
||||
return
|
||||
$ Map.lookup tn tyMap
|
||||
|
||||
extrIFaceTyInfo :: MonadError Text m => Map.HashMap G.NamedType TypeInfo -> G.NamedType -> m IFaceTyInfo
|
||||
extrIFaceTyInfo tyMap tn = case Map.lookup tn tyMap of
|
||||
Just (TIIFace i) -> return i
|
||||
_ -> throwError $ "Could not find interface " <> showNamedTy tn
|
||||
|
||||
extrObjTyInfoM :: TypeMap -> G.NamedType -> Maybe ObjTyInfo
|
||||
extrObjTyInfoM tyMap tn = case Map.lookup tn tyMap of
|
||||
Just (TIObj o) -> return o
|
||||
_ -> Nothing
|
||||
|
||||
validateIsSubType :: Map.HashMap G.NamedType TypeInfo -> G.GType -> G.GType -> Either Text ()
|
||||
validateIsSubType tyMap subFldTy supFldTy = do
|
||||
checkNullMismatch subFldTy supFldTy
|
||||
case (subFldTy,supFldTy) of
|
||||
(G.TypeNamed _ subTy, G.TypeNamed _ supTy) -> do
|
||||
subTyInfo <- extrTyInfo tyMap subTy
|
||||
supTyInfo <- extrTyInfo tyMap supTy
|
||||
isSubTypeBase subTyInfo supTyInfo
|
||||
(G.TypeList _ (G.ListType sub), G.TypeList _ (G.ListType sup) ) -> do
|
||||
validateIsSubType tyMap sub sup
|
||||
_ -> throwError $ showIsListTy subFldTy <> " Type " <> G.showGT subFldTy <>
|
||||
" cannot be a sub-type of " <> showIsListTy supFldTy <> " Type " <> G.showGT supFldTy
|
||||
where
|
||||
checkNullMismatch subTy supTy = when (G.isNotNull supTy && G.isNullable subTy ) $
|
||||
throwError $ "Nullable Type " <> G.showGT subFldTy <> " cannot be a sub-type of Non-Null Type " <> G.showGT supFldTy
|
||||
showIsListTy = \case
|
||||
G.TypeList {} -> "List"
|
||||
G.TypeNamed {} -> "Named"
|
||||
|
||||
-- TODO Should we check the schema location as well?
|
||||
isSubTypeBase :: (MonadError Text m) => TypeInfo -> TypeInfo -> m ()
|
||||
isSubTypeBase subTyInfo supTyInfo = case (subTyInfo,supTyInfo) of
|
||||
(TIObj obj, TIIFace iFace) -> unless (_ifName iFace `elem` _otiImplIFaces obj) notSubTyErr
|
||||
_ -> unless (subTyInfo == supTyInfo) notSubTyErr
|
||||
where
|
||||
showTy = showNamedTy . getNamedTy
|
||||
notSubTyErr = throwError $ "Type " <> showTy subTyInfo <> " is not a sub type of " <> showTy supTyInfo
|
||||
|
||||
-- map postgres types to builtin scalars
|
||||
pgColTyToScalar :: PGColType -> Text
|
||||
pgColTyToScalar = \case
|
||||
@@ -263,8 +510,10 @@ getNamedTy :: TypeInfo -> G.NamedType
|
||||
getNamedTy = \case
|
||||
TIScalar t -> mkScalarTy $ _stiType t
|
||||
TIObj t -> _otiName t
|
||||
TIIFace i -> _ifName i
|
||||
TIEnum t -> _etiName t
|
||||
TIInpObj t -> _iotiName t
|
||||
TIUnion u -> _utiName u
|
||||
|
||||
mkTyInfoMap :: [TypeInfo] -> TypeMap
|
||||
mkTyInfoMap tyInfos =
|
||||
@@ -275,20 +524,34 @@ fromTyDef tyDef loc = case tyDef of
|
||||
G.TypeDefinitionScalar t -> TIScalar <$> fromScalarTyDef t loc
|
||||
G.TypeDefinitionObject t -> return $ TIObj $ fromObjTyDef t loc
|
||||
G.TypeDefinitionInterface t ->
|
||||
throwError $ "unexpected interface: " <> showName (G._itdName t)
|
||||
G.TypeDefinitionUnion t ->
|
||||
throwError $ "unexpected union: " <> showName (G._utdName t)
|
||||
return $ TIIFace $ fromIFaceDef t loc
|
||||
G.TypeDefinitionUnion t -> return $ TIUnion $ fromUnionTyDef t
|
||||
G.TypeDefinitionEnum t -> return $ TIEnum $ fromEnumTyDef t loc
|
||||
G.TypeDefinitionInputObject t -> return $ TIInpObj $ fromInpObjTyDef t loc
|
||||
|
||||
fromSchemaDoc :: G.SchemaDocument -> TypeLoc -> Either Text TypeMap
|
||||
fromSchemaDoc (G.SchemaDocument tyDefs) loc = do
|
||||
tyMap <- fmap mkTyInfoMap $ mapM (flip fromTyDef loc) tyDefs
|
||||
validateTypeMap tyMap
|
||||
return tyMap
|
||||
|
||||
validateTypeMap :: TypeMap -> Either Text ()
|
||||
validateTypeMap tyMap = mapM_ validateTy $ Map.elems tyMap
|
||||
where
|
||||
validateTy (TIObj o) = validateObj tyMap o
|
||||
validateTy (TIUnion u) = validateUnion tyMap u
|
||||
validateTy (TIIFace i) = validateIFace i
|
||||
validateTy _ = return ()
|
||||
|
||||
fromTyDefQ :: G.TypeDefinition -> TypeLoc -> TH.Q TH.Exp
|
||||
fromTyDefQ tyDef loc = case fromTyDef tyDef loc of
|
||||
Left e -> fail $ T.unpack e
|
||||
Right t -> TH.lift t
|
||||
|
||||
fromSchemaDocQ :: G.SchemaDocument -> TypeLoc -> TH.Q TH.Exp
|
||||
fromSchemaDocQ (G.SchemaDocument tyDefs) loc =
|
||||
TH.ListE <$> mapM (flip fromTyDefQ loc) tyDefs
|
||||
fromSchemaDocQ sd loc = case fromSchemaDoc sd loc of
|
||||
Left e -> fail $ T.unpack e
|
||||
Right tyMap -> TH.ListE <$> mapM TH.lift (Map.elems tyMap)
|
||||
|
||||
defaultSchema :: G.SchemaDocument
|
||||
defaultSchema = $(G.parseSchemaDocQ "src-rsr/schema.graphql")
|
||||
|
||||
@@ -8,6 +8,10 @@ import Instances.TH.Lift ()
|
||||
import qualified Language.Haskell.TH.Syntax as TH
|
||||
|
||||
import qualified Data.HashMap.Strict as M
|
||||
import qualified Data.HashSet as S
|
||||
|
||||
instance (TH.Lift k, TH.Lift v) => TH.Lift (M.HashMap k v) where
|
||||
lift m = [| M.fromList $(TH.lift $ M.toList m) |]
|
||||
|
||||
instance TH.Lift a => TH.Lift (S.HashSet a) where
|
||||
lift s = [| S.fromList $(TH.lift $ S.toList s) |]
|
||||
|
||||
@@ -4,6 +4,8 @@ from http import HTTPStatus
|
||||
|
||||
import graphene
|
||||
|
||||
import copy
|
||||
|
||||
from webserver import RequestHandler, WebServer, MkHandlers, Response
|
||||
|
||||
from enum import Enum
|
||||
@@ -167,6 +169,344 @@ class PersonGraphQL(RequestHandler):
|
||||
res = person_schema.execute(req.json['query'])
|
||||
return mkJSONResp(res)
|
||||
|
||||
#GraphQL server with interfaces
|
||||
|
||||
class Character(graphene.Interface):
|
||||
id = graphene.ID(required=True)
|
||||
name = graphene.String(required=True)
|
||||
|
||||
def __init__(self, id, name):
|
||||
self.id = id
|
||||
self.name = name
|
||||
|
||||
class Human(graphene.ObjectType):
|
||||
class Meta:
|
||||
interfaces = (Character, )
|
||||
|
||||
home_planet = graphene.String()
|
||||
|
||||
def __init__(self, home_planet, character):
|
||||
self.home_planet = home_planet
|
||||
self.character = character
|
||||
|
||||
def resolve_id(self, info):
|
||||
return self.character.id
|
||||
|
||||
def resolve_name(self, info):
|
||||
return self.character.name
|
||||
|
||||
def refolve_primary_function(self, info):
|
||||
return self.home_planet
|
||||
|
||||
class Droid(graphene.ObjectType):
|
||||
class Meta:
|
||||
interfaces = (Character, )
|
||||
|
||||
primary_function = graphene.String()
|
||||
|
||||
def __init__(self, primary_function, character):
|
||||
self.primary_function = primary_function
|
||||
self.character = character
|
||||
|
||||
def resolve_id(self, info):
|
||||
return self.character.id
|
||||
|
||||
def resolve_name(self, info):
|
||||
return self.character.name
|
||||
|
||||
def resolve_primary_function(self, info):
|
||||
return self.primary_function
|
||||
|
||||
class CharacterSearchResult(graphene.Union):
|
||||
class Meta:
|
||||
types = (Human,Droid)
|
||||
|
||||
all_characters = {
|
||||
4: Droid("Astromech", Character(1,'R2-D2')),
|
||||
5: Human("Tatooine", Character(2, "Luke Skywalker")),
|
||||
}
|
||||
|
||||
character_search_results = {
|
||||
1: Droid("Astromech", Character(6,'R2-D2')),
|
||||
2: Human("Tatooine", Character(7, "Luke Skywalker")),
|
||||
}
|
||||
|
||||
class CharacterIFaceQuery(graphene.ObjectType):
|
||||
hero = graphene.Field(
|
||||
Character,
|
||||
required=False,
|
||||
episode=graphene.Int(required=True)
|
||||
)
|
||||
|
||||
def resolve_hero(_, info, episode):
|
||||
return all_characters.get(episode)
|
||||
|
||||
schema = graphene.Schema(query=CharacterIFaceQuery, types=[Human, Droid])
|
||||
|
||||
character_interface_schema = graphene.Schema(query=CharacterIFaceQuery, types=[Human, Droid])
|
||||
|
||||
class CharacterInterfaceGraphQL(RequestHandler):
|
||||
def get(self, req):
|
||||
return Response(HTTPStatus.METHOD_NOT_ALLOWED)
|
||||
def post(self, req):
|
||||
if not req.json:
|
||||
return Response(HTTPStatus.BAD_REQUEST)
|
||||
res = character_interface_schema.execute(req.json['query'])
|
||||
return mkJSONResp(res)
|
||||
|
||||
class InterfaceGraphQLErrEmptyFieldList(RequestHandler):
|
||||
def get(self, req):
|
||||
return Response(HTTPStatus.METHOD_NOT_ALLOWED)
|
||||
def post(self, req):
|
||||
if not req.json:
|
||||
return Response(HTTPStatus.BAD_REQUEST)
|
||||
res = character_interface_schema.execute(req.json['query'])
|
||||
respDict = res.to_dict()
|
||||
typesList = respDict.get('data',{}).get('__schema',{}).get('types',None)
|
||||
if typesList is not None:
|
||||
for t in typesList:
|
||||
if t['kind'] == 'INTERFACE':
|
||||
t['fields'] = []
|
||||
return Response(HTTPStatus.OK, respDict,
|
||||
{'Content-Type': 'application/json'})
|
||||
|
||||
class InterfaceGraphQLErrUnknownInterface(RequestHandler):
|
||||
def get(self, req):
|
||||
return Response(HTTPStatus.METHOD_NOT_ALLOWED)
|
||||
def post(self, req):
|
||||
if not req.json:
|
||||
return Response(HTTPStatus.BAD_REQUEST)
|
||||
res = character_interface_schema.execute(req.json['query'])
|
||||
respDict = res.to_dict()
|
||||
typesList = respDict.get('data',{}).get('__schema',{}).get('types',None)
|
||||
if typesList is not None:
|
||||
for t in typesList:
|
||||
if t['kind'] == 'OBJECT' and t['name'] == 'Droid':
|
||||
t['interfaces'][0]['name'] = 'UnknownIFace'
|
||||
return Response(HTTPStatus.OK, respDict,
|
||||
{'Content-Type': 'application/json'})
|
||||
|
||||
class InterfaceGraphQLErrWrongFieldType(RequestHandler):
|
||||
def get(self, req):
|
||||
return Response(HTTPStatus.METHOD_NOT_ALLOWED)
|
||||
def post(self, req):
|
||||
if not req.json:
|
||||
return Response(HTTPStatus.BAD_REQUEST)
|
||||
res = character_interface_schema.execute(req.json['query'])
|
||||
respDict = res.to_dict()
|
||||
typesList = respDict.get('data',{}).get('__schema',{}).get('types',None)
|
||||
if typesList is not None:
|
||||
for t in typesList:
|
||||
#Remove id field from Droid
|
||||
if t['kind'] == 'OBJECT' and t['name'] == 'Droid':
|
||||
for f in t['fields'].copy():
|
||||
if f['name'] == 'id':
|
||||
f['type']['ofType']['name'] = 'String'
|
||||
return Response(HTTPStatus.OK, respDict,
|
||||
{'Content-Type': 'application/json'})
|
||||
|
||||
class InterfaceGraphQLErrMissingField(RequestHandler):
|
||||
def get(self, req):
|
||||
return Response(HTTPStatus.METHOD_NOT_ALLOWED)
|
||||
def post(self, req):
|
||||
if not req.json:
|
||||
return Response(HTTPStatus.BAD_REQUEST)
|
||||
res = character_interface_schema.execute(req.json['query'])
|
||||
respDict = res.to_dict()
|
||||
typesList = respDict.get('data',{}).get('__schema',{}).get('types',None)
|
||||
if typesList is not None:
|
||||
for t in typesList:
|
||||
#Remove id field from Droid
|
||||
if t['kind'] == 'OBJECT' and t['name'] == 'Droid':
|
||||
for f in t['fields'].copy():
|
||||
if f['name'] == 'id':
|
||||
t['fields'].remove(f)
|
||||
return Response(HTTPStatus.OK, respDict,
|
||||
{'Content-Type': 'application/json'})
|
||||
ifaceArg = {
|
||||
"name": "ifaceArg",
|
||||
"description": None,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": None,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": None
|
||||
}
|
||||
},
|
||||
"defaultValue": None
|
||||
}
|
||||
|
||||
class InterfaceGraphQLErrMissingArg(RequestHandler):
|
||||
def get(self, req):
|
||||
return Response(HTTPStatus.METHOD_NOT_ALLOWED)
|
||||
def post(self, req):
|
||||
if not req.json:
|
||||
return Response(HTTPStatus.BAD_REQUEST)
|
||||
res = character_interface_schema.execute(req.json['query'])
|
||||
respDict = res.to_dict()
|
||||
typesList = respDict.get('data',{}).get('__schema',{}).get('types',None)
|
||||
if typesList is not None:
|
||||
for t in typesList:
|
||||
if t['kind'] == 'INTERFACE':
|
||||
for f in t['fields']:
|
||||
if f['name'] == 'id':
|
||||
f['args'].append(ifaceArg)
|
||||
return Response(HTTPStatus.OK, respDict,
|
||||
{'Content-Type': 'application/json'})
|
||||
|
||||
class InterfaceGraphQLErrWrongArgType(RequestHandler):
|
||||
def get(self, req):
|
||||
return Response(HTTPStatus.METHOD_NOT_ALLOWED)
|
||||
def post(self, req):
|
||||
if not req.json:
|
||||
return Response(HTTPStatus.BAD_REQUEST)
|
||||
res = character_interface_schema.execute(req.json['query'])
|
||||
respDict = res.to_dict()
|
||||
objArg = copy.deepcopy(ifaceArg)
|
||||
objArg['type']['ofType']['name'] = 'String'
|
||||
|
||||
typesList = respDict.get('data',{}).get('__schema',{}).get('types',None)
|
||||
if typesList is not None:
|
||||
for t in filter(lambda ty : ty['kind'] == 'INTERFACE', typesList):
|
||||
for f in filter(lambda fld: fld['name'] == 'id', t['fields']):
|
||||
f['args'].append(ifaceArg)
|
||||
|
||||
for t in filter(lambda ty: ty['name'] in ['Droid','Human'], typesList):
|
||||
for f in filter(lambda fld: fld['name'] == 'id', t['fields']):
|
||||
f['args'].append(ifaceArg if t['name'] == 'Droid' else objArg)
|
||||
|
||||
return Response(HTTPStatus.OK, respDict,
|
||||
{'Content-Type': 'application/json'})
|
||||
|
||||
class InterfaceGraphQLErrExtraNonNullArg(RequestHandler):
|
||||
def get(self, req):
|
||||
return Response(HTTPStatus.METHOD_NOT_ALLOWED)
|
||||
def post(self, req):
|
||||
if not req.json:
|
||||
return Response(HTTPStatus.BAD_REQUEST)
|
||||
res = character_interface_schema.execute(req.json['query'])
|
||||
respDict = res.to_dict()
|
||||
typesList = respDict.get('data',{}).get('__schema',{}).get('types',None)
|
||||
if typesList is not None:
|
||||
for t in typesList:
|
||||
if t['kind'] == 'OBJECT' and t['name'] == 'Droid':
|
||||
for f in t['fields']:
|
||||
if f['name'] == 'id':
|
||||
f['args'].append({
|
||||
"name": "extraArg",
|
||||
"description": None,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": None,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": None
|
||||
}
|
||||
},
|
||||
"defaultValue": None
|
||||
})
|
||||
return Response(HTTPStatus.OK, respDict,
|
||||
{'Content-Type': 'application/json'})
|
||||
|
||||
#GraphQL server involving union type
|
||||
|
||||
class UnionQuery(graphene.ObjectType):
|
||||
search = graphene.Field(
|
||||
CharacterSearchResult,
|
||||
required=False,
|
||||
episode=graphene.Int(required=True)
|
||||
)
|
||||
|
||||
def resolve_search(_, info, episode):
|
||||
return character_search_results.get(episode)
|
||||
|
||||
union_schema = graphene.Schema(query=UnionQuery, types=[Human, Droid])
|
||||
|
||||
class UnionGraphQL(RequestHandler):
|
||||
def get(self, req):
|
||||
return Response(HTTPStatus.METHOD_NOT_ALLOWED)
|
||||
def post(self, req):
|
||||
if not req.json:
|
||||
return Response(HTTPStatus.BAD_REQUEST)
|
||||
res = union_schema.execute(req.json['query'])
|
||||
return mkJSONResp(res)
|
||||
|
||||
class UnionGraphQLSchemaErrUnknownTypes(RequestHandler):
|
||||
def get(self, req):
|
||||
return Response(HTTPStatus.METHOD_NOT_ALLOWED)
|
||||
def post(self, req):
|
||||
if not req.json:
|
||||
return Response(HTTPStatus.BAD_REQUEST)
|
||||
res = union_schema.execute(req.json['query'])
|
||||
respDict = res.to_dict()
|
||||
typesList = respDict.get('data',{}).get('__schema',{}).get('types',None)
|
||||
if typesList is not None:
|
||||
for t in typesList:
|
||||
if t['kind'] == 'UNION':
|
||||
for i, p in enumerate(t['possibleTypes']):
|
||||
p['name'] = 'Unknown' + str(i)
|
||||
return Response(HTTPStatus.OK, respDict,
|
||||
{'Content-Type': 'application/json'})
|
||||
|
||||
class UnionGraphQLSchemaErrSubTypeInterface(RequestHandler):
|
||||
def get(self, req):
|
||||
return Response(HTTPStatus.METHOD_NOT_ALLOWED)
|
||||
def post(self, req):
|
||||
if not req.json:
|
||||
return Response(HTTPStatus.BAD_REQUEST)
|
||||
res = union_schema.execute(req.json['query'])
|
||||
respDict = res.to_dict()
|
||||
typesList = respDict.get('data',{}).get('__schema',{}).get('types',None)
|
||||
if typesList is not None:
|
||||
for t in typesList:
|
||||
if t['kind'] == 'UNION':
|
||||
for p in t['possibleTypes']:
|
||||
p['name'] = 'Character'
|
||||
return Response(HTTPStatus.OK, respDict,
|
||||
{'Content-Type': 'application/json'})
|
||||
|
||||
class UnionGraphQLSchemaErrNoMemberTypes(RequestHandler):
|
||||
def get(self, req):
|
||||
return Response(HTTPStatus.METHOD_NOT_ALLOWED)
|
||||
def post(self, req):
|
||||
if not req.json:
|
||||
return Response(HTTPStatus.BAD_REQUEST)
|
||||
res = union_schema.execute(req.json['query'])
|
||||
respDict = res.to_dict()
|
||||
typesList = respDict.get('data',{}).get('__schema',{}).get('types',None)
|
||||
if typesList is not None:
|
||||
for t in typesList:
|
||||
if t['kind'] == 'UNION':
|
||||
t['possibleTypes'] = []
|
||||
return Response(HTTPStatus.OK, respDict,
|
||||
{'Content-Type': 'application/json'})
|
||||
|
||||
class UnionGraphQLSchemaErrWrappedType(RequestHandler):
|
||||
def get(self, req):
|
||||
return Response(HTTPStatus.METHOD_NOT_ALLOWED)
|
||||
def post(self, req):
|
||||
if not req.json:
|
||||
return Response(HTTPStatus.BAD_REQUEST)
|
||||
res = union_schema.execute(req.json['query'])
|
||||
respDict = res.to_dict()
|
||||
typesList = respDict.get('data',{}).get('__schema',{}).get('types',None)
|
||||
if typesList is not None:
|
||||
for t in typesList:
|
||||
if t['kind'] == 'UNION':
|
||||
for i, p in enumerate(t['possibleTypes']):
|
||||
t['possibleTypes'][i] = {
|
||||
"kind": "NON_NULL",
|
||||
"name": None,
|
||||
"ofType": p
|
||||
}
|
||||
return Response(HTTPStatus.OK, respDict,
|
||||
{'Content-Type': 'application/json'})
|
||||
|
||||
#GraphQL server with default values for inputTypes
|
||||
|
||||
class InpObjType(graphene.InputObjectType):
|
||||
|
||||
@classmethod
|
||||
@@ -229,6 +569,7 @@ class EchoGraphQL(RequestHandler):
|
||||
res = echo_schema.execute(req.json['query'])
|
||||
respDict = res.to_dict()
|
||||
typesList = respDict.get('data',{}).get('__schema',{}).get('types',None)
|
||||
#Hack around enum default_value serialization issue: https://github.com/graphql-python/graphql-core/issues/166
|
||||
if typesList is not None:
|
||||
for t in filter(lambda ty: ty['name'] == 'EchoQuery', typesList):
|
||||
for f in filter(lambda fld: fld['name'] == 'echo', t['fields']):
|
||||
@@ -242,6 +583,19 @@ handlers = MkHandlers({
|
||||
'/hello-graphql': HelloGraphQL,
|
||||
'/user-graphql': UserGraphQL,
|
||||
'/country-graphql': CountryGraphQL,
|
||||
'/character-iface-graphql' : CharacterInterfaceGraphQL,
|
||||
'/iface-graphql-err-empty-field-list' : InterfaceGraphQLErrEmptyFieldList,
|
||||
'/iface-graphql-err-unknown-iface' : InterfaceGraphQLErrUnknownInterface,
|
||||
'/iface-graphql-err-missing-field' : InterfaceGraphQLErrMissingField,
|
||||
'/iface-graphql-err-wrong-field-type' : InterfaceGraphQLErrWrongFieldType,
|
||||
'/iface-graphql-err-missing-arg' : InterfaceGraphQLErrMissingArg,
|
||||
'/iface-graphql-err-wrong-arg-type' : InterfaceGraphQLErrWrongArgType,
|
||||
'/iface-graphql-err-extra-non-null-arg' : InterfaceGraphQLErrExtraNonNullArg,
|
||||
'/union-graphql' : UnionGraphQL,
|
||||
'/union-graphql-err-unknown-types' : UnionGraphQLSchemaErrUnknownTypes,
|
||||
'/union-graphql-err-subtype-iface' : UnionGraphQLSchemaErrSubTypeInterface,
|
||||
'/union-graphql-err-no-member-types' : UnionGraphQLSchemaErrNoMemberTypes,
|
||||
'/union-graphql-err-wrapped-type' : UnionGraphQLSchemaErrWrappedType,
|
||||
'/default-value-echo-graphql' : EchoGraphQL,
|
||||
'/person-graphql': PersonGraphQL
|
||||
})
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
description: Add a remote schema with a field of an object not having the argument of the same field in interface
|
||||
url: /v1/query
|
||||
status: 400
|
||||
response:
|
||||
path: $.args
|
||||
error: |-
|
||||
Interface field argument 'Character'."id"("ifaceArg":) required, but Object field 'Droid'."id" does not provide it
|
||||
code: remote-schema-error
|
||||
query:
|
||||
type: add_remote_schema
|
||||
args:
|
||||
name: err-missing-arg
|
||||
definition:
|
||||
url: http://localhost:5000/iface-graphql-err-missing-arg
|
||||
headers: []
|
||||
forward_client_headers: true
|
||||
@@ -0,0 +1,16 @@
|
||||
description: Add a remote schema with an object not having a field defined in the interface that it implements
|
||||
url: /v1/query
|
||||
status: 400
|
||||
response:
|
||||
path: $.args
|
||||
error: |-
|
||||
Interface field 'Character'."id" expected, but 'Droid' does not provide it
|
||||
code: remote-schema-error
|
||||
query:
|
||||
type: add_remote_schema
|
||||
args:
|
||||
name: err-missing-arg
|
||||
definition:
|
||||
url: http://localhost:5000/iface-graphql-err-missing-field
|
||||
headers: []
|
||||
forward_client_headers: true
|
||||
@@ -0,0 +1,16 @@
|
||||
description: Add a remote schema with an object implementing unknown interface
|
||||
url: /v1/query
|
||||
status: 400
|
||||
response:
|
||||
path: $.args
|
||||
error: |-
|
||||
Could not find interface 'UnknownIFace' implemented by Object type 'Droid'
|
||||
code: remote-schema-error
|
||||
query:
|
||||
type: add_remote_schema
|
||||
args:
|
||||
name: err-unknown-iface
|
||||
definition:
|
||||
url: http://localhost:5000/iface-graphql-err-unknown-iface
|
||||
headers: []
|
||||
forward_client_headers: true
|
||||
@@ -0,0 +1,16 @@
|
||||
description: Add a remote schema with a argument of a field of an object not having the same type as the same argument of the same field in interface
|
||||
url: /v1/query
|
||||
status: 400
|
||||
response:
|
||||
path: $.args
|
||||
error: |-
|
||||
Interface field argument 'Character'."id"("ifaceArg":) expects type Int!, but 'Human'."id"("ifaceArg":) has type String!
|
||||
code: remote-schema-error
|
||||
query:
|
||||
type: add_remote_schema
|
||||
args:
|
||||
name: err-missing-arg
|
||||
definition:
|
||||
url: http://localhost:5000/iface-graphql-err-wrong-arg-type
|
||||
headers: []
|
||||
forward_client_headers: true
|
||||
@@ -0,0 +1,16 @@
|
||||
description: Add a remote schema with an interface with field list empty
|
||||
url: /v1/query
|
||||
status: 400
|
||||
response:
|
||||
path: $.args
|
||||
error: |-
|
||||
List of fields cannot be empty for interface 'Character'
|
||||
code: remote-schema-error
|
||||
query:
|
||||
type: add_remote_schema
|
||||
args:
|
||||
name: err-unknown-types
|
||||
definition:
|
||||
url: http://localhost:5000/iface-graphql-err-empty-field-list
|
||||
headers: []
|
||||
forward_client_headers: true
|
||||
@@ -0,0 +1,16 @@
|
||||
description: Add a remote schema with a field of object implementing the interface having an extra non-null arg
|
||||
url: /v1/query
|
||||
status: 400
|
||||
response:
|
||||
path: $.args
|
||||
error: |-
|
||||
Object field argument 'Droid'."id"("extraArg":) is of required type Int!, but is not provided by Interface field 'Character'."id"
|
||||
code: remote-schema-error
|
||||
query:
|
||||
type: add_remote_schema
|
||||
args:
|
||||
name: err-unknown-types
|
||||
definition:
|
||||
url: http://localhost:5000/iface-graphql-err-extra-non-null-arg
|
||||
headers: []
|
||||
forward_client_headers: true
|
||||
@@ -0,0 +1,16 @@
|
||||
description: Add a remote schema with an object implementing the interface has a field with a different type when compared to the same field in the interface
|
||||
url: /v1/query
|
||||
status: 400
|
||||
response:
|
||||
path: $.args
|
||||
error: |-
|
||||
The type of Object field 'Droid'."id" (String!) is not the same type/sub type of Interface field 'Character'."id" (ID!)
|
||||
code: remote-schema-error
|
||||
query:
|
||||
type: add_remote_schema
|
||||
args:
|
||||
name: err-unknown-types
|
||||
definition:
|
||||
url: http://localhost:5000/iface-graphql-err-wrong-field-type
|
||||
headers: []
|
||||
forward_client_headers: true
|
||||
@@ -0,0 +1,16 @@
|
||||
description: Add a remote schema with a union which has unknown object types
|
||||
url: /v1/query
|
||||
status: 400
|
||||
response:
|
||||
path: $.args
|
||||
error: |-
|
||||
Union type 'CharacterSearchResult' can only include object types. It cannot include 'Character'
|
||||
code: remote-schema-error
|
||||
query:
|
||||
type: add_remote_schema
|
||||
args:
|
||||
name: err-unknown-types
|
||||
definition:
|
||||
url: http://localhost:5000/union-graphql-err-subtype-iface
|
||||
headers: []
|
||||
forward_client_headers: true
|
||||
@@ -0,0 +1,16 @@
|
||||
description: Add a remote schema with a union which has unknown object types
|
||||
url: /v1/query
|
||||
status: 400
|
||||
response:
|
||||
path: $.args
|
||||
error: |-
|
||||
List of member types cannot be empty for union type 'CharacterSearchResult'
|
||||
code: remote-schema-error
|
||||
query:
|
||||
type: add_remote_schema
|
||||
args:
|
||||
name: err-no-mem-types
|
||||
definition:
|
||||
url: http://localhost:5000/union-graphql-err-no-member-types
|
||||
headers: []
|
||||
forward_client_headers: true
|
||||
@@ -0,0 +1,16 @@
|
||||
description: Add a remote schema with a union which has unknown object types
|
||||
url: /v1/query
|
||||
status: 400
|
||||
response:
|
||||
path: $.args
|
||||
error: |-
|
||||
Could not find type 'Unknown0', which is defined as a member type of Union 'CharacterSearchResult'
|
||||
code: remote-schema-error
|
||||
query:
|
||||
type: add_remote_schema
|
||||
args:
|
||||
name: err-unknown-types
|
||||
definition:
|
||||
url: http://localhost:5000/union-graphql-err-unknown-types
|
||||
headers: []
|
||||
forward_client_headers: true
|
||||
@@ -0,0 +1,16 @@
|
||||
description: Add a remote schema with a union which has wrapped type as a possible type
|
||||
url: /v1/query
|
||||
status: 400
|
||||
response:
|
||||
path: $.args
|
||||
error: |-
|
||||
"Error in $.types[1].possibleTypes[0].name: expected Text, encountered Null"
|
||||
code: remote-schema-error
|
||||
query:
|
||||
type: add_remote_schema
|
||||
args:
|
||||
name: err-unknown-types
|
||||
definition:
|
||||
url: http://localhost:5000/union-graphql-err-wrapped-type
|
||||
headers: []
|
||||
forward_client_headers: true
|
||||
@@ -0,0 +1,43 @@
|
||||
description: Query from the remote schema with interfaces
|
||||
url: /v1alpha1/graphql
|
||||
status: 200
|
||||
response:
|
||||
data:
|
||||
hero3: null
|
||||
hero4:
|
||||
__typename: Droid
|
||||
id: '1'
|
||||
name: R2-D2
|
||||
primaryFunction: Astromech
|
||||
hero5:
|
||||
__typename: Human
|
||||
name: Luke Skywalker
|
||||
homePlanet: Tatooine
|
||||
|
||||
query:
|
||||
query: |
|
||||
query getHeroes {
|
||||
hero3: hero (episode: 3) {
|
||||
id
|
||||
}
|
||||
|
||||
hero4: hero (episode: 4) {
|
||||
__typename
|
||||
id
|
||||
name
|
||||
... on Droid {
|
||||
primaryFunction
|
||||
}
|
||||
}
|
||||
|
||||
hero5: hero (episode: 5) {
|
||||
__typename
|
||||
name
|
||||
... on Droid {
|
||||
primaryFunction
|
||||
}
|
||||
... on Human {
|
||||
homePlanet
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
description: Query from the remote schema with union
|
||||
url: /v1alpha1/graphql
|
||||
status: 200
|
||||
|
||||
response:
|
||||
data:
|
||||
hero3: null
|
||||
hero2:
|
||||
__typename: Human
|
||||
id: '7'
|
||||
name: Luke Skywalker
|
||||
homePlanet: Tatooine
|
||||
hero1:
|
||||
__typename: Droid
|
||||
id: '6'
|
||||
name: R2-D2
|
||||
primaryFunction: Astromech
|
||||
|
||||
query:
|
||||
query: |
|
||||
query unionSearch {
|
||||
hero3: search (episode: 3) {
|
||||
__typename
|
||||
... on Droid {
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
hero2: search (episode: 2) {
|
||||
__typename
|
||||
... on Human {
|
||||
id
|
||||
name
|
||||
homePlanet
|
||||
}
|
||||
}
|
||||
|
||||
hero1: search (episode: 1) {
|
||||
__typename
|
||||
... on Droid {
|
||||
id
|
||||
name
|
||||
primaryFunction
|
||||
}
|
||||
... on Human {
|
||||
id
|
||||
homePlanet
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +84,68 @@ class TestRemoteSchemaBasic:
|
||||
hge_ctx.v1q({"type": "remove_remote_schema", "args": {"name": "my remote"}})
|
||||
assert st_code == 200, resp
|
||||
|
||||
def test_add_remote_schema_with_interfaces(self, hge_ctx):
|
||||
"""add a remote schema with interfaces in it"""
|
||||
q = mk_add_remote_q('my remote interface one', 'http://localhost:5000/character-iface-graphql')
|
||||
st_code, resp = hge_ctx.v1q(q)
|
||||
assert st_code == 200, resp
|
||||
check_query_f(hge_ctx, self.dir + '/character_interface_query.yaml')
|
||||
hge_ctx.v1q({"type": "remove_remote_schema", "args": {"name": "my remote interface one"}})
|
||||
assert st_code == 200, resp
|
||||
|
||||
def test_add_remote_schema_with_interface_err_empty_fields_list(self, hge_ctx):
|
||||
"""add a remote schema with an interface having no fields"""
|
||||
check_query_f(hge_ctx, self.dir + '/add_remote_schema_with_iface_err_empty_fields_list.yaml')
|
||||
|
||||
def test_add_remote_schema_err_unknown_interface(self, hge_ctx):
|
||||
"""add a remote schema with an interface having no fields"""
|
||||
check_query_f(hge_ctx, self.dir + '/add_remote_schema_err_unknown_interface.yaml')
|
||||
|
||||
def test_add_remote_schema_with_interface_err_missing_field(self, hge_ctx):
|
||||
"""add a remote schema where an object implementing an interface does not have a field defined in the interface"""
|
||||
check_query_f(hge_ctx, self.dir + '/add_remote_schema_err_missing_field.yaml')
|
||||
|
||||
def test_add_remote_schema_with_interface_err_wrong_field_type(self, hge_ctx):
|
||||
"""add a remote schema where an object implementing an interface have a field with the same name as in the interface, but of different type"""
|
||||
check_query_f(hge_ctx, self.dir + '/add_remote_schema_with_iface_err_wrong_field_type.yaml')
|
||||
|
||||
def test_add_remote_schema_with_interface_err_missing_arg(self, hge_ctx):
|
||||
"""add a remote schema where a field of an object implementing an interface does not have the argument defined in the same field of interface"""
|
||||
check_query_f(hge_ctx, self.dir + '/add_remote_schema_err_missing_arg.yaml')
|
||||
|
||||
def test_add_remote_schema_with_interface_err_wrong_arg_type(self, hge_ctx):
|
||||
"""add a remote schema where the argument of a field of an object implementing the interface does not have the same type as the argument defined in the field of interface"""
|
||||
check_query_f(hge_ctx, self.dir + '/add_remote_schema_iface_err_wrong_arg_type.yaml')
|
||||
|
||||
def test_add_remote_schema_with_interface_err_extra_non_null_arg(self, hge_ctx):
|
||||
"""add a remote schema with a field of an object implementing interface having extra non_null argument"""
|
||||
check_query_f(hge_ctx, self.dir + '/add_remote_schema_with_iface_err_extra_non_null_arg.yaml')
|
||||
|
||||
def test_add_remote_schema_with_union(self, hge_ctx):
|
||||
"""add a remote schema with union in it"""
|
||||
q = mk_add_remote_q('my remote union one', 'http://localhost:5000/union-graphql')
|
||||
st_code, resp = hge_ctx.v1q(q)
|
||||
assert st_code == 200, resp
|
||||
check_query_f(hge_ctx, self.dir + '/search_union_type_query.yaml')
|
||||
hge_ctx.v1q({"type": "remove_remote_schema", "args": {"name": "my remote union one"}})
|
||||
assert st_code == 200, resp
|
||||
|
||||
def test_add_remote_schema_with_union_err_no_member_types(self, hge_ctx):
|
||||
"""add a remote schema with a union having no member types"""
|
||||
check_query_f(hge_ctx, self.dir + '/add_remote_schema_with_union_err_no_member_types.yaml')
|
||||
|
||||
def test_add_remote_schema_with_union_err_unkown_types(self, hge_ctx):
|
||||
"""add a remote schema with a union having unknown types as memberTypes"""
|
||||
check_query_f(hge_ctx, self.dir + '/add_remote_schema_with_union_err_unknown_types.yaml')
|
||||
|
||||
def test_add_remote_schema_with_union_err_subtype_iface(self, hge_ctx):
|
||||
"""add a remote schema with a union having interface as a memberType"""
|
||||
check_query_f(hge_ctx, self.dir + '/add_remote_schema_with_union_err_member_type_interface.yaml')
|
||||
|
||||
def test_add_remote_schema_with_union_err_wrapped_type(self, hge_ctx):
|
||||
"""add a remote schema with error in spec for union"""
|
||||
check_query_f(hge_ctx, self.dir + '/add_remote_schema_with_union_err_wrapped_type.yaml')
|
||||
|
||||
def test_bulk_remove_add_remote_schema(self, hge_ctx):
|
||||
st_code, resp = hge_ctx.v1q_f(self.dir + '/basic_bulk_remove_add.yaml')
|
||||
assert st_code == 200, resp
|
||||
|
||||
Reference in New Issue
Block a user